diff --git a/src/plugins/rv-packages/annotate/annotate_mode.mu b/src/plugins/rv-packages/annotate/annotate_mode.mu index 0ec3d4f6b..76d2ff7d2 100644 --- a/src/plugins/rv-packages/annotate/annotate_mode.mu +++ b/src/plugins/rv-packages/annotate/annotate_mode.mu @@ -567,10 +567,13 @@ class: AnnotateMinorMode : MinorMode let uuid = generateUuid(); let uuidProperty = "%s.uuid" % n; - newProperty(uuidProperty, StringType, 1); setStringProperty(uuidProperty, string[] {uuid}, true); + let softDeletedProperty = "%s.softDeleted" % n; + newProperty(softDeletedProperty, IntType, 1); + setIntProperty(softDeletedProperty, int[] {0}, true); + let stroke = n.split(".").back(); if (!propertyExists(orderName)) @@ -709,6 +712,10 @@ class: AnnotateMinorMode : MinorMode newProperty(uuidProperty, StringType, 1); setStringProperty(uuidProperty, string[] {uuid}, true); + let softDeletedProperty = "%s.softDeleted" % n; + newProperty(softDeletedProperty, IntType, 1); + setIntProperty(softDeletedProperty, int[] {0}, true); + let stroke = n.split(".").back(); if (!propertyExists(orderName)) @@ -1596,6 +1603,8 @@ class: AnnotateMinorMode : MinorMode // clearAllUndo format: ["clearAllFrames", "node1", "stroke1", "node2", "stroke2"] if (clearAllUndo.size() > 1 && clearAllUndo[0] == "clearAllFrames") { + string[] affectedStrokes; + beginCompoundStateChange(); for (int i = 1; i < clearAllUndo.size(); i += 2) @@ -1603,12 +1612,20 @@ class: AnnotateMinorMode : MinorMode let node = clearAllUndo[i]; let uuid = clearAllUndo[i + 1]; + affectedStrokes.push_back(uuid); + 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 softDeleted = "%s.%s.softDeleted" % (node, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {0}, true); + } + let orderName = frameOrderName(node, frame); if (!propertyExists(orderName)) @@ -1629,6 +1646,13 @@ class: AnnotateMinorMode : MinorMode setStringProperty(clearAllUndoProperty, string[] {}, true); endCompoundStateChange(); + + if (affectedStrokes.size() > 0) + { + string eventContents = string.join(affectedStrokes, "|"); + sendInternalEvent("undo-paint", eventContents); + } + return; } } @@ -1669,6 +1693,12 @@ class: AnnotateMinorMode : MinorMode let stroke = findStrokeByUuid(_currentNode, frame, uuid); if (stroke != "") { + let softDeleted = "%s.%s.softDeleted" % (_currentNode, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {1}, true); + } + for_index(i; order) { if (order[i] == stroke) @@ -1705,6 +1735,13 @@ class: AnnotateMinorMode : MinorMode { affectedStrokes.push_back(uuid); let stroke = findStrokeByUuid(_currentNode, frame, uuid); + + let softDeleted = "%s.%s.softDeleted" % (_currentNode, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {0}, true); + } + order.push_back(stroke); } @@ -1742,6 +1779,8 @@ class: AnnotateMinorMode : MinorMode // clearAllRedo format: ["clearAllFrames", "node1", "stroke1", "node2", "stroke2"] if (clearAllRedo.size() > 1 && clearAllRedo[0] == "clearAllFrames") { + string[] affectedStrokes; + beginCompoundStateChange(); for (int i = 1; i < clearAllRedo.size(); i += 2) @@ -1749,12 +1788,20 @@ class: AnnotateMinorMode : MinorMode let node = clearAllRedo[i]; let uuid = clearAllRedo[i + 1]; + affectedStrokes.push_back(uuid); + 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 softDeleted = "%s.%s.softDeleted" % (node, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {1}, true); + } + let orderName = frameOrderName(node, frame); if (propertyExists(orderName)) { @@ -1781,6 +1828,13 @@ class: AnnotateMinorMode : MinorMode setStringProperty(clearAllRedoProperty, string[] {}, true); endCompoundStateChange(); + + if (affectedStrokes.size() > 0) + { + string eventContents = string.join(affectedStrokes, "|"); + sendInternalEvent("redo-paint", eventContents); + } + return; } } @@ -1821,6 +1875,12 @@ class: AnnotateMinorMode : MinorMode let stroke = findStrokeByUuid(_currentNode, frame, uuid); if (stroke != "") { + let softDeleted = "%s.%s.softDeleted" % (_currentNode, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {0}, true); + } + order.push_back(stroke); undo.push_back(uuid); @@ -1850,6 +1910,13 @@ class: AnnotateMinorMode : MinorMode { affectedStrokes.push_back(uuid); let stroke = findStrokeByUuid(_currentNode, frame, uuid); + + let softDeleted = "%s.%s.softDeleted" % (_currentNode, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {1}, true); + } + for_index(i; order) { if (order[i] == stroke) @@ -1917,6 +1984,7 @@ class: AnnotateMinorMode : MinorMode { string[] undo; string[] redo; + string[] affectedStrokes; for_each(stroke; order) { @@ -1926,6 +1994,13 @@ class: AnnotateMinorMode : MinorMode { let uuid = getStringProperty(uuidProperty).front(); undo.push_back(uuid); + affectedStrokes.push_back(uuid); + + let softDeleted = "%s.%s.softDeleted" % (node, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {1}, true); + } } } undo.push_back(string(order.size())); @@ -1952,6 +2027,12 @@ class: AnnotateMinorMode : MinorMode clearAllUsersUndoRedoStacks(node, frame, excludeUser); endCompoundStateChange(); + + if (affectedStrokes.size() > 0) + { + string eventContents = string.join(affectedStrokes, "|"); + sendInternalEvent("clear-paint", eventContents); + } } } } @@ -1960,6 +2041,7 @@ class: AnnotateMinorMode : MinorMode { string[] clearAllActions; clearAllActions.push_back("clearAllFrames"); + string[] affectedStrokes; beginCompoundStateChange(); @@ -1976,15 +2058,18 @@ class: AnnotateMinorMode : MinorMode { let uuidProperty = "%s.%s.uuid" % (node, stroke); - clearAllActions.push_back(node); if (propertyExists(uuidProperty)) { let uuid = getStringProperty(uuidProperty).front(); + clearAllActions.push_back(node); clearAllActions.push_back(uuid); - } - else - { - clearAllActions.push_back(""); + affectedStrokes.push_back(uuid); + + let softDeleted = "%s.%s.softDeleted" % (node, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {1}, true); + } } } @@ -2002,15 +2087,18 @@ class: AnnotateMinorMode : MinorMode { let uuidProperty = "%s.%s.uuid" % (node, stroke); - clearAllActions.push_back(node); if (propertyExists(uuidProperty)) { let uuid = getStringProperty(uuidProperty).front(); + clearAllActions.push_back(node); clearAllActions.push_back(uuid); - } - else - { - clearAllActions.push_back(""); + affectedStrokes.push_back(uuid); + + let softDeleted = "%s.%s.softDeleted" % (node, stroke); + if (propertyExists(softDeleted)) + { + setIntProperty(softDeleted, int[] {1}, true); + } } } @@ -2041,6 +2129,13 @@ class: AnnotateMinorMode : MinorMode } endCompoundStateChange(); + + if (affectedStrokes.size() > 0) + { + string eventContents = string.join(affectedStrokes, "|"); + sendInternalEvent("clear-all-paint", eventContents); + } + updateFrameDependentState(); redraw(); } @@ -2165,13 +2260,6 @@ class: AnnotateMinorMode : MinorMode return if _syncAutoStart then CheckedMenuState else UncheckedMenuState; } - method: undoSlot (void; bool checked) - { - undoPaint(); - updateFrameDependentState(); - redraw(); - } - method: clearSlot (void; bool checked) { clearPaint(_currentNode, _currentNodeInfo.frame); @@ -2197,6 +2285,13 @@ class: AnnotateMinorMode : MinorMode redraw(); } + method: undoSlot (void; bool checked) + { + undoPaint(); + updateFrameDependentState(); + redraw(); + } + method: redoSlot (void; bool checked) { redoPaint(); @@ -3034,6 +3129,8 @@ class: AnnotateMinorMode : MinorMode _clearButton.setMenu(clearMenu); _clearButton.setPopupMode(QToolButton.InstantPopup); + connect(clearMenu, QMenu.aboutToShow, undoRedoClearUpdate); + connect(_clearFrameAct, QAction.triggered, clearSlot); connect(_clearAllFramesAct, QAction.triggered, clearAllSlot); _clearButton.setStyleSheet("QToolButton::menu-indicator { subcontrol-position: bottom right; top: -2px; }"); diff --git a/src/plugins/rv-packages/otio_reader/annotation_hook.py b/src/plugins/rv-packages/otio_reader/annotation_hook.py index c2e0b6b58..565e75929 100644 --- a/src/plugins/rv-packages/otio_reader/annotation_hook.py +++ b/src/plugins/rv-packages/otio_reader/annotation_hook.py @@ -7,7 +7,6 @@ import logging -import uuid import effectHook import opentimelineio as otio from rv import commands, extra_commands @@ -95,70 +94,65 @@ def hook_function(in_timeline: otio.schemadef.Annotation.Annotation, argument_ma logging.warning("Unable to set Hold and Ghost properties") for layer in in_timeline.layers: - if layer.name == "Paint": - if isinstance(layer.layer_range, otio.opentime.TimeRange): - time_range = layer.layer_range - else: - time_range = otio.opentime.TimeRange(layer.layer_range["start_time"], layer.layer_range["duration"]) - - relative_time = time_range.end_time_inclusive() - frame = relative_time.to_frames() - - source_node = argument_map.get("source_group") - paint_node = extra_commands.nodesInGroupOfType(source_node, "RVPaint")[0] - paint_component = f"{paint_node}.paint" - stroke_id = commands.getIntProperty(f"{paint_component}.nextId")[0] + 1 - pen_component = f"{paint_node}.pen:{stroke_id}:{frame}:annotation" - frame_component = f"{paint_node}.frame:{frame}" - - # Set properties on the paint component of the RVPaint node - effectHook.set_rv_effect_props(paint_component, {"nextId": stroke_id}) - - start_time = int(time_range.start_time.value) - end_time = int(start_time + time_range.duration.value) - - duration = end_time - start_time - - # Add and set properties on the pen component of the RVPaint node - effectHook.add_rv_effect_props( - pen_component, - { - "color": list(map(float, layer.rgba)), - "brush": layer.brush, - "debug": 1, - "join": 3, - "cap": 1, - "splat": 0, - "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 isinstance(layer.layer_range, otio.opentime.TimeRange): + time_range = layer.layer_range + else: + time_range = otio.opentime.TimeRange(layer.layer_range["start_time"], layer.layer_range["duration"]) + + relative_time = time_range.end_time_inclusive() + frame = relative_time.to_frames() + + source_node = argument_map.get("source_group") + paint_node = extra_commands.nodesInGroupOfType(source_node, "RVPaint")[0] + paint_component = f"{paint_node}.paint" + stroke_id = commands.getIntProperty(f"{paint_component}.nextId")[0] + 1 + pen_component = f"{paint_node}.pen:{stroke_id}:{frame}:annotation" + frame_component = f"{paint_node}.frame:{frame}" + + # Set properties on the paint component of the RVPaint node + effectHook.set_rv_effect_props(paint_component, {"nextId": stroke_id}) + + start_time = int(time_range.start_time.value) + end_time = int(start_time + time_range.duration.value) + + duration = end_time - start_time + + # Add and set properties on the pen component of the RVPaint node + effectHook.add_rv_effect_props( + pen_component, + { + "color": list(map(float, layer.rgba)), + "brush": layer.brush, + "debug": 1, + "join": 3, + "cap": 1, + "splat": 0, + "mode": 0 if layer.type == "COLOR" else 1, + "startFrame": start_time, + "duration": duration, + "uuid": layer.id, + }, + ) + + 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) + + commands.insertFloatProperty( + points_property, + [world_coordinate_x, world_coordinate_y], ) + commands.insertFloatProperty(width_property, [world_coordinate_width]) - 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 - ) - - commands.insertFloatProperty( - points_property, - [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) + 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"]) + 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 30117c477..e47c75b14 100644 --- a/src/plugins/rv-packages/otio_reader/paint_schema.py +++ b/src/plugins/rv-packages/otio_reader/paint_schema.py @@ -34,9 +34,8 @@ class Paint(otio.core.SerializableObject): def __init__( self, - name: str = "", id: str = "", - soft_deleted: bool = False, + soft_deleted: bool | None = None, points: list = [], rgba: list = [], type: str = "", @@ -45,7 +44,6 @@ def __init__( visible: bool = True, ) -> None: super().__init__() - self.name = name self.id = id self.soft_deleted = soft_deleted self.points = points @@ -55,8 +53,6 @@ def __init__( self.layer_range = layer_range self.visible = visible - name = otio.core.serializable_field("name", required_type=str, doc=("name: expects a string")) - 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"))