caching and metadata

This commit is contained in:
Nicolas Predella 2026-05-22 15:13:23 +03:00
parent 7f480c698d
commit 64eff798ac
4 changed files with 89 additions and 27 deletions

View file

@ -621,6 +621,29 @@ class SLICENODE_GcodeBlock(SliceNode):
def evaluate_output(self, socket_name: str, **_kwargs) -> str: def evaluate_output(self, socket_name: str, **_kwargs) -> str:
return self.outputs['Gcode'].value return self.outputs['Gcode'].value
@register_class
class SLICENODE_AssignMetadata(SliceNode):
bl_label = "Assign Metadata"
def init(self, context):
self.add_input("SLICESOCK_String", "Name")
self.add_input("SLICESOCK_String", "Filament Type")
self.add_input("SLICESOCK_String", "Printer Model")
self.add_output("SLICESOCK_GcodeRo", "Gcode")
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
name = self.resolve("Name")
filament = self.resolve("Filament Type")
printer = self.resolve("Printer Model")
lines = []
if name:
lines.append(f'; objects_info = {{"objects": [{{"name": "{name}", "polygon": []}}]}}')
if filament:
lines.append(f'; filament_type = {filament}')
if printer:
lines.append(f'; printer_model = {printer}')
return '\n'.join(lines)
@register_class @register_class
class SLICENODE_GcodeHelper(SliceNode): class SLICENODE_GcodeHelper(SliceNode):
bl_label = "Gcode Helper" bl_label = "Gcode Helper"

View file

@ -194,6 +194,7 @@ class SLICENODE_MT_Gcode(Menu):
assert layout assert layout
layout.operator("node.add_node", text="Paths to Gcode").type = "SLICENODE_PathsToGcode" layout.operator("node.add_node", text="Paths to Gcode").type = "SLICENODE_PathsToGcode"
layout.operator("node.add_node", text="Gcode Block").type = "SLICENODE_GcodeBlock" layout.operator("node.add_node", text="Gcode Block").type = "SLICENODE_GcodeBlock"
layout.operator("node.add_node", text="Assign Metadata").type = "SLICENODE_AssignMetadata"
layout.operator("node.add_node", text="Gcode Helper").type = "SLICENODE_GcodeHelper" layout.operator("node.add_node", text="Gcode Helper").type = "SLICENODE_GcodeHelper"
layout.operator("node.add_node", text="Load Gcode").type = "SLICENODE_LoadGcode" layout.operator("node.add_node", text="Load Gcode").type = "SLICENODE_LoadGcode"

View file

@ -9,7 +9,7 @@ from bpy.types import NodeSocket, Node
class NodeException(Exception): ... class NodeException(Exception): ...
_EvalCache = dict[tuple[int, str], Any] _EvalCache = dict[tuple[int, str, str], Any]
class AutoBlIdName: class AutoBlIdName:
def __init_subclass__(cls, **kwargs): def __init_subclass__(cls, **kwargs):
@ -32,7 +32,9 @@ class SliceNode(Node, AutoBlIdName):
def evaluate_output(self, socket_name: str, **_kwargs) -> Any: ... def evaluate_output(self, socket_name: str, **_kwargs) -> Any: ...
def resolve(self, fieldname) -> Any: def resolve(self, fieldname) -> Any:
return resolve_input(self.inputs.get(fieldname)) cache = getattr(self, "_current_cache", None)
visited = getattr(self, "_current_visited", None)
return resolve_input(self.inputs.get(fieldname), cache=cache, visited=visited)
def add_output(self, bl_idname: str, name: str = "", identifier: str = ""): def add_output(self, bl_idname: str, name: str = "", identifier: str = ""):
eval_name = name or get_label_by_name(bl_idname) eval_name = name or get_label_by_name(bl_idname)
@ -48,8 +50,10 @@ class SliceNode(Node, AutoBlIdName):
self._update_multi_inputs() self._update_multi_inputs()
def resolve_multi_input(self, fieldname) -> Any: def resolve_multi_input(self, fieldname) -> Any:
cache = getattr(self, "_current_cache", None)
visited = getattr(self, "_current_visited", None)
inputs = [i for i in list(self.inputs) if i.name == fieldname] inputs = [i for i in list(self.inputs) if i.name == fieldname]
return [resolve_input(r) for r in inputs] return [resolve_input(r, cache=cache, visited=visited) for r in inputs]
def _update_multi_inputs(self): def _update_multi_inputs(self):
if not self.get('multi_sockets'): return if not self.get('multi_sockets'): return
@ -81,6 +85,21 @@ class SliceNode(Node, AutoBlIdName):
## Evaluation helpers ## Evaluation helpers
_current_execution_cache: _EvalCache | None = None
@contextmanager
def execution_cache():
global _current_execution_cache
if _current_execution_cache is not None:
yield _current_execution_cache
return
_current_execution_cache = {}
try:
yield _current_execution_cache
finally:
_current_execution_cache = None
@contextmanager @contextmanager
def node_execution_feedback(node: bpy.types.Node): def node_execution_feedback(node: bpy.types.Node):
# Save original color # Save original color
@ -93,7 +112,8 @@ def node_execution_feedback(node: bpy.types.Node):
# Refresh UI # Refresh UI
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
try: try:
yield with execution_cache():
yield
finally: finally:
node.use_custom_color = old_use_color node.use_custom_color = old_use_color
node.color = old_color node.color = old_color
@ -129,6 +149,11 @@ def resolve_input(
assert socket.node assert socket.node
if cache is None:
cache = getattr(socket.node, "_current_cache", _current_execution_cache)
if visited is None:
visited = getattr(socket.node, "_current_visited", None)
if not socket.is_linked: if not socket.is_linked:
val = getattr(socket, "value", None) val = getattr(socket, "value", None)
if val is not None: if val is not None:
@ -153,39 +178,51 @@ def _resolve_output(
cache: _EvalCache | None = None, cache: _EvalCache | None = None,
visited: set[tuple[int, str]] | None = None, visited: set[tuple[int, str]] | None = None,
): ):
if cache is None:
cache = _current_execution_cache
cache = {} if cache is None else cache cache = {} if cache is None else cache
visited = set() if visited is None else visited visited = set() if visited is None else visited
assert socket.node assert socket.node
key = (socket.node.as_pointer(), socket.node.name) key = (socket.node.as_pointer(), socket.node.name, socket.name)
if key in cache: return cache[key] if key in cache: return cache[key]
if key in visited: return default # cycle guard
visited.add(key) node_key = (socket.node.as_pointer(), socket.node.name)
if node_key in visited: return default # cycle guard
visited.add(node_key)
try: try:
with node_execution_feedback(socket.node): with node_execution_feedback(socket.node):
if not isinstance(socket.node, SliceNode): if not isinstance(socket.node, SliceNode):
return default return default
if getattr(socket.node, "mute", False): # Set these temporarily so self.resolve() can find them
# Attempt bypass: find a matching input socket by name or type socket.node._current_cache = cache # type: ignore
input_to_bypass = socket.node.inputs.get(socket.name) socket.node._current_visited = visited # type: ignore
if not input_to_bypass:
for inp in socket.node.inputs:
if inp.bl_idname == socket.bl_idname:
input_to_bypass = inp
break
if input_to_bypass: try:
value = resolve_input(typing.cast(Any, input_to_bypass), cache=cache, visited=visited) if getattr(socket.node, "mute", False):
cache[key] = value # Attempt bypass: find a matching input socket by name or type
return value input_to_bypass = socket.node.inputs.get(socket.name)
return default if not input_to_bypass:
for inp in socket.node.inputs:
if inp.bl_idname == socket.bl_idname:
input_to_bypass = inp
break
assert socket.node.evaluate_output if input_to_bypass:
value = socket.node.evaluate_output(socket.name, cache=cache, visited=visited) value = resolve_input(typing.cast(Any, input_to_bypass), cache=cache, visited=visited)
cache[key] = value cache[key] = value
return value return value
return default
assert socket.node.evaluate_output
value = socket.node.evaluate_output(socket.name, cache=cache, visited=visited)
cache[key] = value
return value
finally:
if hasattr(socket.node, "_current_cache"): del socket.node._current_cache
if hasattr(socket.node, "_current_visited"): del socket.node._current_visited
finally: finally:
visited.remove(key) visited.remove(node_key)

View file

@ -52,7 +52,8 @@ def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str:
names: list[str] = [str(v['name'].split(" id:")[0]) for v in gcode_info['objects']] names: list[str] = [str(v['name'].split(" id:")[0]) for v in gcode_info['objects']]
counts = Counter(names) counts = Counter(names)
object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items()) object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items())
return "-".join([object_names, filament, printer]) parts = [object_names, filament, printer]
return "-".join([str(p) for p in parts if p])
except: except:
pass pass
return default return default