diff --git a/src/lib/mu/MuPy/MuPy/PyModule.h b/src/lib/mu/MuPy/MuPy/PyModule.h index 691ebbca3..3447c1199 100644 --- a/src/lib/mu/MuPy/MuPy/PyModule.h +++ b/src/lib/mu/MuPy/MuPy/PyModule.h @@ -54,6 +54,7 @@ namespace Mu static NODE_DECLARATION(nPyTuple_GetItem, Pointer); static NODE_DECLARATION(nPyString_Check, bool); static NODE_DECLARATION(nPyFunction_Check, bool); + static NODE_DECLARATION(nPyObject_Str, Pointer); static NODE_DECLARATION(nPyImport_Import, Pointer); static NODE_DECLARATION(nPy_TYPE, Pointer); static NODE_DECLARATION(nPyModule_GetName, Pointer); diff --git a/src/lib/mu/MuPy/PyModule.cpp b/src/lib/mu/MuPy/PyModule.cpp index 62caf9211..4a3bdcf5a 100644 --- a/src/lib/mu/MuPy/PyModule.cpp +++ b/src/lib/mu/MuPy/PyModule.cpp @@ -167,6 +167,9 @@ namespace Mu new Function(c, "PyFunction_Check", nPyFunction_Check, None, Return, "bool", Parameters, new ParameterVariable(c, "obj", "python.PyObject"), End), + new Function(c, "PyObject_Str", nPyObject_Str, None, Return, "python.PyObject", Parameters, + new ParameterVariable(c, "obj", "python.PyObject"), End), + new Function(c, "Py_TYPE", nPy_TYPE, None, Return, "python.PyTypeObject", Parameters, new ParameterVariable(c, "obj", "python.PyObject"), End), @@ -1046,6 +1049,13 @@ namespace Mu NODE_RETURN(PyBytes_Check(obj)); } + NODE_IMPLEMENTATION(PyModule::nPyObject_Str, Pointer) + { + PyLockObject locker; + PyObject* obj = NODE_ARG_OBJECT(0, PyObject); + NODE_RETURN(PyObject_Str(obj)); + } + NODE_IMPLEMENTATION(PyModule::nPyObject_CallObject, Pointer) { PyLockObject locker; diff --git a/src/plugins/rv-packages/annotate/annotate_mode.mu b/src/plugins/rv-packages/annotate/annotate_mode.mu index cd01f8e98..0ec3d4f6b 100644 --- a/src/plugins/rv-packages/annotate/annotate_mode.mu +++ b/src/plugins/rv-packages/annotate/annotate_mode.mu @@ -12,6 +12,7 @@ use glu; require glyph; require io; require system; +require python; class: DrawDockWidget : QDockWidget { @@ -199,6 +200,38 @@ class: AnnotateMinorMode : MinorMode return c * Color(s,s,s,c.w); } + method: generateUuid (string;) + { + let pyModule = python.PyImport_Import("uuid"); + python.PyObject pyMethod = python.PyObject_GetAttr(pyModule, "uuid4"); + let uuidObj = python.PyObject_CallObject(pyMethod, python.PyTuple_New(0)); + string uuid = to_string(python.PyObject_Str(uuidObj)); + + return uuid; + } + + method: findStrokeByUuid (string; string node, int frame, string uuid) + { + regex uuidPattern = regex("^" + regex.replace("\\.", node, "\\.") + "\\.(pen|text):[0-9]+:[0-9]+:.*\\.uuid$"); + + for_each (property; properties(node)) + { + if (uuidPattern.match(property)) + { + let storedId = getStringProperty(property).front(); + if (storedId == uuid) + { + let parts = property.split("."); + if (parts.size() >= 2) + { + return parts[1]; + } + } + } + } + return ""; + } + method: findPaintNodes (MetaEvalInfo[];) { let infos = metaEvaluate(frame(), viewNode()); @@ -532,6 +565,12 @@ class: AnnotateMinorMode : MinorMode setIntProperty(startFrameName, int[] {startFrame}, true); setIntProperty(durationName, int[] {duration}, true); + let uuid = generateUuid(); + let uuidProperty = "%s.uuid" % n; + + newProperty(uuidProperty, StringType, 1); + setStringProperty(uuidProperty, string[] {uuid}, true); + let stroke = n.split(".").back(); if (!propertyExists(orderName)) @@ -546,7 +585,7 @@ class: AnnotateMinorMode : MinorMode newProperty(undoName, StringType, 1); } - insertStringProperty(undoName, string[] {stroke, "create"}); + insertStringProperty(undoName, string[] {uuid, "create"}); let redoName = frameUserRedoStackName(node, frame); if (propertyExists(redoName)) @@ -664,6 +703,12 @@ class: AnnotateMinorMode : MinorMode setIntProperty(startFrameName, int[] {startFrame}, true); setIntProperty(durationName, int[] {duration}, true); + let uuid = generateUuid(); + let uuidProperty = "%s.uuid" % n; + + newProperty(uuidProperty, StringType, 1); + setStringProperty(uuidProperty, string[] {uuid}, true); + let stroke = n.split(".").back(); if (!propertyExists(orderName)) @@ -678,7 +723,7 @@ class: AnnotateMinorMode : MinorMode newProperty(undoName, StringType, 1); } - insertStringProperty(undoName, string[] {stroke, "create"}); + insertStringProperty(undoName, string[] {uuid, "create"}); let redoName = frameUserRedoStackName(node, frame); if (propertyExists(redoName)) @@ -1556,19 +1601,23 @@ class: AnnotateMinorMode : MinorMode for (int i = 1; i < clearAllUndo.size(); i += 2) { let node = clearAllUndo[i]; - let stroke = clearAllUndo[i + 1]; + let uuid = clearAllUndo[i + 1]; + + let stroke = findStrokeByUuid(node, frame(), uuid); + if (stroke != "") + { + let parts = stroke.split(":"); // Stroke format: type:id:frame:user_processId + let frame = int(parts[2]); - let parts = stroke.split(":"); // Stroke format: type:id:frame:user_processId - let frame = int(parts[2]); + let orderName = frameOrderName(node, frame); - let orderName = frameOrderName(node, frame); + if (!propertyExists(orderName)) + { + newProperty(orderName, StringType, 1); + } - if (!propertyExists(orderName)) - { - newProperty(orderName, StringType, 1); + insertStringProperty(orderName, string[] {stroke}); } - - insertStringProperty(orderName, string[] {stroke}); } if (!propertyExists(clearAllRedoProperty)) @@ -1602,28 +1651,36 @@ class: AnnotateMinorMode : MinorMode let undo = getStringProperty(undoProperty); let redo = getStringProperty(redoProperty); let order = getStringProperty(orderProperty); - + let action = undo.back(); undo.resize(undo.size() - 1); + + string[] affectedStrokes; if (action == "create") { if (undo.size() > 0) { - let stroke = undo.back(); + let uuid = undo.back(); undo.resize(undo.size() - 1); + + affectedStrokes.push_back(uuid); - for_index(i; order) + let stroke = findStrokeByUuid(_currentNode, frame, uuid); + if (stroke != "") { - if (order[i] == stroke) + for_index(i; order) { - order.erase(i, 1); - break; + if (order[i] == stroke) + { + order.erase(i, 1); + break; + } } + + redo.push_back(uuid); + redo.push_back("create"); } - - redo.push_back(stroke); - redo.push_back("create"); } } else if (action == "clearAll") @@ -1634,24 +1691,26 @@ class: AnnotateMinorMode : MinorMode undo.resize(undo.size() - 1); let count = int(strokeCount); - string[] strokes; + string[] uuids; for (int i = 0; i < count; i++) { if (undo.size() > 0) { - strokes.push_back(undo.back()); + uuids.push_back(undo.back()); undo.resize(undo.size() - 1); } } - for_each(stroke; strokes) + for_each(uuid; uuids) { + affectedStrokes.push_back(uuid); + let stroke = findStrokeByUuid(_currentNode, frame, uuid); order.push_back(stroke); } - for_each(stroke; strokes) + for_each(uuid; uuids) { - redo.push_back(stroke); + redo.push_back(uuid); } redo.push_back(strokeCount); redo.push_back("clearAll"); @@ -1663,6 +1722,12 @@ class: AnnotateMinorMode : MinorMode setStringProperty(redoProperty, redo, true); setStringProperty(orderProperty, order, true); endCompoundStateChange(); + + if (affectedStrokes.size() > 0) + { + string eventContents = string.join(affectedStrokes, "|"); + sendInternalEvent("undo-paint", eventContents); + } } method: redoPaint (void;) @@ -1682,24 +1747,28 @@ class: AnnotateMinorMode : MinorMode for (int i = 1; i < clearAllRedo.size(); i += 2) { let node = clearAllRedo[i]; - let stroke = clearAllRedo[i + 1]; + let uuid = clearAllRedo[i + 1]; - let parts = stroke.split(":"); // Stroke format: type:id:frame:user_processId - let frame = int(parts[2]); - - let orderName = frameOrderName(node, frame); - if (propertyExists(orderName)) + let stroke = findStrokeByUuid(node, frame(), uuid); + if (stroke != "") { - let order = getStringProperty(orderName); - for_index(index; order) + let parts = stroke.split(":"); // Stroke format: type:id:frame:user_processId + let frame = int(parts[2]); + + let orderName = frameOrderName(node, frame); + if (propertyExists(orderName)) { - if (order[index] == stroke) + let order = getStringProperty(orderName); + for_index(index; order) { - order.erase(index, 1); - break; + if (order[index] == stroke) + { + order.erase(index, 1); + break; + } } + setStringProperty(orderName, order, true); } - setStringProperty(orderName, order, true); } } @@ -1734,21 +1803,29 @@ class: AnnotateMinorMode : MinorMode let undo = getStringProperty(undoProperty); let redo = getStringProperty(redoProperty); let order = getStringProperty(orderProperty); - + let action = redo.back(); redo.resize(redo.size() - 1); + + string[] affectedStrokes; if (action == "create") { if (redo.size() > 0) { - let stroke = redo.back(); + let uuid = redo.back(); redo.resize(redo.size() - 1); + + affectedStrokes.push_back(uuid); - order.push_back(stroke); - - undo.push_back(stroke); - undo.push_back("create"); + let stroke = findStrokeByUuid(_currentNode, frame, uuid); + if (stroke != "") + { + order.push_back(stroke); + + undo.push_back(uuid); + undo.push_back("create"); + } } } else if (action == "clearAll") @@ -1759,18 +1836,20 @@ class: AnnotateMinorMode : MinorMode redo.resize(redo.size() - 1); let count = int(countStr); - string[] strokes; + string[] uuids; for (int i = 0; i < count; i++) { if (redo.size() > 0) { - strokes.push_back(redo.back()); + uuids.push_back(redo.back()); redo.resize(redo.size() - 1); } } - for_each(stroke; strokes) + for_each(uuid; uuids) { + affectedStrokes.push_back(uuid); + let stroke = findStrokeByUuid(_currentNode, frame, uuid); for_index(i; order) { if (order[i] == stroke) @@ -1781,9 +1860,9 @@ class: AnnotateMinorMode : MinorMode } } - for_each(stroke; strokes) + for_each(uuid; uuids) { - undo.push_back(stroke); + undo.push_back(uuid); } undo.push_back(countStr); undo.push_back("clearAll"); @@ -1795,6 +1874,12 @@ class: AnnotateMinorMode : MinorMode setStringProperty(redoProperty, redo, true); setStringProperty(orderProperty, order, true); endCompoundStateChange(); + + if (affectedStrokes.size() > 0) + { + string eventContents = string.join(affectedStrokes, "|"); + sendInternalEvent("redo-paint", eventContents); + } } method: clearAllUsersUndoRedoStacks (void; string node, int frame, string excludeUser="") @@ -1832,10 +1917,16 @@ class: AnnotateMinorMode : MinorMode { string[] undo; string[] redo; - + for_each(stroke; order) { - undo.push_back(stroke); + let uuidProperty = "%s.%s.uuid" % (node, stroke); + + if (propertyExists(uuidProperty)) + { + let uuid = getStringProperty(uuidProperty).front(); + undo.push_back(uuid); + } } undo.push_back(string(order.size())); undo.push_back("clearAll"); @@ -1883,8 +1974,18 @@ class: AnnotateMinorMode : MinorMode let order = getStringProperty(orderProperty); for_each(stroke; order) { + let uuidProperty = "%s.%s.uuid" % (node, stroke); + clearAllActions.push_back(node); - clearAllActions.push_back(stroke); + if (propertyExists(uuidProperty)) + { + let uuid = getStringProperty(uuidProperty).front(); + clearAllActions.push_back(uuid); + } + else + { + clearAllActions.push_back(""); + } } setStringProperty(orderProperty, string[] {}, true); @@ -1899,8 +2000,18 @@ class: AnnotateMinorMode : MinorMode let srcOrder = getStringProperty(sourceFrameOrderProperty); for_each(stroke; srcOrder) { + let uuidProperty = "%s.%s.uuid" % (node, stroke); + clearAllActions.push_back(node); - clearAllActions.push_back(stroke); + if (propertyExists(uuidProperty)) + { + let uuid = getStringProperty(uuidProperty).front(); + clearAllActions.push_back(uuid); + } + else + { + clearAllActions.push_back(""); + } } setStringProperty(sourceFrameOrderProperty, string[] {}, true); diff --git a/src/plugins/rv-packages/otio_reader/annotation_hook.py b/src/plugins/rv-packages/otio_reader/annotation_hook.py index 64e143abd..c2e0b6b58 100644 --- a/src/plugins/rv-packages/otio_reader/annotation_hook.py +++ b/src/plugins/rv-packages/otio_reader/annotation_hook.py @@ -7,6 +7,7 @@ import logging +import uuid import effectHook import opentimelineio as otio from rv import commands, extra_commands @@ -85,18 +86,13 @@ def _transform_otio_to_world_coordinate(point): def hook_function(in_timeline: otio.schemadef.Annotation.Annotation, argument_map: dict | None = None) -> None: """A hook for the annotation schema""" try: - if argument_map["effect_metadata"]: - effect_metadata = argument_map["effect_metadata"] - commands.setIntProperty("#Session.paintEffects.hold", [effect_metadata.get("hold", 0)]) - commands.setIntProperty("#Session.paintEffects.ghost", [effect_metadata.get("ghost", 0)]) - else: - commands.setIntProperty("#Session.paintEffects.hold", [in_timeline.hold]) - commands.setIntProperty("#Session.paintEffects.ghost", [in_timeline.ghost]) + commands.setIntProperty("#Session.paintEffects.hold", [in_timeline.hold]) + commands.setIntProperty("#Session.paintEffects.ghost", [in_timeline.ghost]) commands.setIntProperty("#Session.paintEffects.ghostBefore", [in_timeline.ghost_before]) commands.setIntProperty("#Session.paintEffects.ghostAfter", [in_timeline.ghost_after]) except Exception: - logging.exception("Unable to set Hold and Ghost properties") + logging.warning("Unable to set Hold and Ghost properties") for layer in in_timeline.layers: if layer.name == "Paint": @@ -136,20 +132,20 @@ def hook_function(in_timeline: otio.schemadef.Annotation.Annotation, argument_ma "mode": 0 if layer.type == "COLOR" else 1, "startFrame": start_time, "duration": duration, + "uuid": layer.id + if layer.id + else str(uuid.uuid4()), # TODO: Remove when CR starts sending this property }, ) - if not commands.propertyExists(f"{frame_component}.order"): - commands.newProperty(f"{frame_component}.order", commands.StringType, 1) - - commands.insertStringProperty(f"{frame_component}.order", [f"pen:{stroke_id}:{frame}:annotation"]) - points_property = f"{pen_component}.points" width_property = f"{pen_component}.width" + if not commands.propertyExists(points_property): commands.newProperty(points_property, commands.FloatType, 2) if not commands.propertyExists(width_property): commands.newProperty(width_property, commands.FloatType, 1) + for point in layer.points: world_coordinate_x, world_coordinate_y, world_coordinate_width = _transform_otio_to_world_coordinate( point @@ -160,3 +156,9 @@ def hook_function(in_timeline: otio.schemadef.Annotation.Annotation, argument_ma [world_coordinate_x, world_coordinate_y], ) commands.insertFloatProperty(width_property, [world_coordinate_width]) + + if not layer.soft_deleted: + if not commands.propertyExists(f"{frame_component}.order"): + commands.newProperty(f"{frame_component}.order", commands.StringType, 1) + + commands.insertStringProperty(f"{frame_component}.order", [f"pen:{stroke_id}:{frame}:annotation"]) diff --git a/src/plugins/rv-packages/otio_reader/paint_schema.py b/src/plugins/rv-packages/otio_reader/paint_schema.py index 8334bf9f1..30117c477 100644 --- a/src/plugins/rv-packages/otio_reader/paint_schema.py +++ b/src/plugins/rv-packages/otio_reader/paint_schema.py @@ -1,5 +1,5 @@ # ***************************************************************************** -# Copyright 2024 Autodesk, Inc. All rights reserved. +# Copyright 2025 Autodesk, Inc. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 # @@ -29,13 +29,14 @@ class Paint(otio.core.SerializableObject): """A schema for the start of an annotation""" - _serializable_label = "Paint.1" + _serializable_label = "Paint.2" _name = "Paint" def __init__( self, name: str = "", id: str = "", + soft_deleted: bool = False, points: list = [], rgba: list = [], type: str = "", @@ -46,6 +47,7 @@ def __init__( super().__init__() self.name = name self.id = id + self.soft_deleted = soft_deleted self.points = points self.rgba = rgba self.type = type @@ -57,6 +59,8 @@ def __init__( id = otio.core.serializable_field("id", required_type=str, doc=("name: expects a string")) + soft_deleted = otio.core.serializable_field("soft_deleted", required_type=bool, doc=("name: expects a bool")) + _points = otio.core.serializable_field( "points", required_type=list, doc=("points: expects a list of point objects") ) @@ -101,13 +105,13 @@ def layer_range(self, val): def __str__(self) -> str: return ( - f"Paint({self.name}, {self.id}, {self.points}, {self.rgba}, {self.type}, " + f"Paint({self.id}, {self.soft_deleted}, {self.points}, {self.rgba}, {self.type}, " f"{self.brush}, {self.layer_range}, {self.visible})" ) def __repr__(self) -> str: return ( - f"otio.schema.Paint(name={self.name!r}, id={self.id!r}, points={self.points!r}, " + f"otio.schema.Paint(id={self.id!r}, soft_deleted={self.soft_deleted!r}, points={self.points!r}, " f"rgba={self.rgba!r}, type={self.type!r}, brush={self.brush!r}, " f"layer_range={self.layer_range!r}, visible={self.visible!r})" )