Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,4 @@ utils/*
# Blender specific
.blender_ext/
/notes
nul
25 changes: 22 additions & 3 deletions operators/base_stateful.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Union
from typing import Optional, Any

import bpy
from bpy.types import Context
Expand All @@ -8,6 +8,7 @@
from ..stateful_operator.integration import StatefulOperator
from ..model.types import SlvsGenericEntity, SlvsPoint3D, SlvsPoint2D, SlvsNormal3D
from .utilities import get_hovered
from ..serialize import scene_to_dict, scene_from_dict


class GenericEntityOp(StatefulOperator):
Expand Down Expand Up @@ -121,7 +122,7 @@ def state_property(self, state_index):
return pointer_name + "_fallback"
return ""

def get_state_pointer(self, index=Union[None, int], implicit=False):
def get_state_pointer(self, index: Optional[int] = None, implicit=False):
retval = super().get_state_pointer(index=index, implicit=implicit)
if retval:
return retval
Expand Down Expand Up @@ -161,7 +162,9 @@ def set_state_pointer(self, values, index=None, implicit=False):
state = self.get_states_definition()[index]
pointer_name = state.pointer
data = self._state_data.get(index, {})
pointer_type = data["type"]
pointer_type = data.get("type")
if pointer_type is None:
return None

if issubclass(pointer_type, SlvsGenericEntity):
value = values[0] if values is not None else None
Expand All @@ -183,3 +186,19 @@ def gather_selection(self, context: Context):

selected.extend(list(context.scene.sketcher.entities.selected))
return selected

def on_before_redo_states(self, context: Context):
global_data.ignore_list.clear()

def create_snapshot(self, context: Context) -> Any:
"""Create a complete snapshot of all sketcher state using serialization"""
# Use the existing serialization system
return scene_to_dict(context.scene)

def restore_snapshot(self, context: Context, snapshot: Any) -> None:
"""Restore sketcher state from serialized snapshot"""
if not snapshot:
return

# Use the existing deserialization system
scene_from_dict(context.scene, snapshot)
1 change: 0 additions & 1 deletion stateful_operator/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"F",
"T",
"Y",
"U",
"R",
"E",
"G",
Expand Down
29 changes: 22 additions & 7 deletions stateful_operator/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import bpy
from bpy.types import Context

from typing import Optional
from typing import Optional, Any


class StatefulOperator(StatefulOperatorLogic):
Expand All @@ -32,6 +32,7 @@ def _has_global_object(cls):
states = cls.get_states_definition()
return any([s.pointer == "object" for s in states])

@classmethod
def _get_global_object_index(cls):
states = cls.get_states_definition()
object_in_list = [s.pointer == "object" for s in states]
Expand All @@ -58,7 +59,7 @@ def register_properties(cls):
if pointer_name in annotations.keys():
# Skip pointers that have a property defined
# Note: pointer might not need implicit props, thus no need for getter/setter
return
continue

if hasattr(cls, pointer_name):
# This can happen when the addon is re-enabled in the same session
Expand Down Expand Up @@ -102,7 +103,10 @@ def get_state_pointer(
obj_name = self._state_data[global_ob_index]["object_name"]
else:
obj_name = data["object_name"]
obj = get_evaluated_obj(bpy.context, bpy.data.objects[obj_name])
blender_obj = bpy.data.objects.get(obj_name)
if blender_obj is None:
return None
obj = get_evaluated_obj(bpy.context, blender_obj)

if pointer_type in mesh_element_types:
index = data["mesh_index"]
Expand Down Expand Up @@ -136,7 +140,9 @@ def set_state_pointer(self, values, index=None, implicit=False):
pointer_name = state.pointer
data = self._state_data.get(index, {})

pointer_type = data["type"]
pointer_type = data.get("type")
if pointer_type is None:
return None

def get_value(index):
if values is None:
Expand Down Expand Up @@ -210,7 +216,7 @@ def pick_element(self, context: Context, coords):
"object_name"
)
if global_ob_name:
global_ob = bpy.data.objects[global_ob_name]
global_ob = bpy.data.objects.get(global_ob_name)

ob, type, index = get_mesh_element(context, coords, **types, object=global_ob)

Expand All @@ -235,7 +241,8 @@ def gather_selection(self, context: Context):
selected = []
states = self.get_states()
types = []
[types.extend(s.types) for s in states]
for s in states:
types.extend(s.types)

# Note: Where to take mesh elements from? Editmode data is only written
# when left probably making it impossible to use selected elements in realtime.
Expand All @@ -250,7 +257,7 @@ def parse_selection(self, context, selected, index=None):
# should go through objects, vertices, entities depending on state.types

result = None
if not index:
if index is None:
index = self.state_index
state = self.get_states_definition()[index]
data = self.get_state_data(index)
Expand Down Expand Up @@ -290,3 +297,11 @@ def draw(self, context):

if hasattr(self, "draw_settings"):
self.draw_settings(context)

def create_snapshot(self, context: Context):
"""Snapshot relevant Blender data references"""
return None

def restore_snapshot(self, context: Context, snapshot):
"""Restore Blender references - mostly validation"""
pass
13 changes: 11 additions & 2 deletions stateful_operator/invoke_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,17 @@ def execute(self, context: Context):

options["wait_for_input"] = True

op_name = self.operator.split(".", 1)
op = getattr(getattr(bpy.ops, op_name[0]), op_name[1])
parts = self.operator.split(".", 1)
if len(parts) != 2:
self.report({"ERROR"}, f"Invalid operator id '{self.operator}': expected 'module.name'")
return {"CANCELLED"}

module = getattr(bpy.ops, parts[0], None)
op = getattr(module, parts[1], None) if module is not None else None
if op is None:
self.report({"ERROR"}, f"Operator not found: '{self.operator}'")
return {"CANCELLED"}

if op.poll():
op("INVOKE_DEFAULT", **options)
return {"FINISHED"}
Loading