object properties
This commit is contained in:
parent
5050a5fa5f
commit
8ec3ae38fa
8 changed files with 229 additions and 55 deletions
21
__init__.py
21
__init__.py
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
||||||
import bpy
|
import bpy
|
||||||
from . import registry
|
from . import registry
|
||||||
from .core.nodeslicer import sockets, nodes, tree # sockets and nodes need also to be imported in order for them to be autoregistered
|
from .core.nodeslicer import sockets, nodes, tree # sockets and nodes need also to be imported in order for them to be autoregistered
|
||||||
|
from .ui import panels
|
||||||
|
from .property_groups.bpy_property_groups import SlicerObjectPropertyGroup
|
||||||
|
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "NodeSlicer",
|
"name": "NodeSlicer",
|
||||||
|
|
@ -13,16 +15,33 @@ bl_info = {
|
||||||
"category": "Node",
|
"category": "Node",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_registered_nodeslicer = False
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
global _registered_nodeslicer
|
||||||
registry.blender_register_classes()
|
registry.blender_register_classes()
|
||||||
bpy.types.NODE_MT_add.append(tree.draw_add_menu)
|
bpy.types.NODE_MT_add.append(tree.draw_add_menu)
|
||||||
bpy.app.handlers.depsgraph_update_post.append(tree.depsgraph_update_handler)
|
bpy.app.handlers.depsgraph_update_post.append(tree.depsgraph_update_handler)
|
||||||
|
|
||||||
|
if not hasattr(bpy.types.Object, "nodeslicer"):
|
||||||
|
bpy.types.Object.nodeslicer = bpy.props.PointerProperty(type=SlicerObjectPropertyGroup, name="nodeslicer") #type: ignore
|
||||||
|
_registered_nodeslicer = True
|
||||||
|
|
||||||
print('Nodeslicer Loaded')
|
print('Nodeslicer Loaded')
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.app.handlers.depsgraph_update_post.remove(tree.depsgraph_update_handler)
|
global _registered_nodeslicer
|
||||||
|
if tree.depsgraph_update_handler in bpy.app.handlers.depsgraph_update_post:
|
||||||
|
bpy.app.handlers.depsgraph_update_post.remove(tree.depsgraph_update_handler)
|
||||||
|
|
||||||
bpy.types.NODE_MT_add.remove(tree.draw_add_menu)
|
bpy.types.NODE_MT_add.remove(tree.draw_add_menu)
|
||||||
registry.blender_unregister_classes()
|
registry.blender_unregister_classes()
|
||||||
|
|
||||||
|
if _registered_nodeslicer:
|
||||||
|
if hasattr(bpy.types.Object, "nodeslicer"):
|
||||||
|
del bpy.types.Object.nodeslicer #type: ignore
|
||||||
|
_registered_nodeslicer = False
|
||||||
|
|
||||||
print('Nodeslicer Unloaded')
|
print('Nodeslicer Unloaded')
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -376,35 +376,18 @@ class SLICENODE_IntMath(MathNode):
|
||||||
class SLICENODE_GetMesh(SliceNode):
|
class SLICENODE_GetMesh(SliceNode):
|
||||||
bl_label = "Get Mesh"
|
bl_label = "Get Mesh"
|
||||||
|
|
||||||
part_type: NfEnumProperty( # pyright: ignore[reportInvalidTypeForm]
|
|
||||||
name='',
|
|
||||||
items=[
|
|
||||||
('ModelPart', 'Model Part', ''),
|
|
||||||
('NegativeVolume', 'Negative Volume', ''),
|
|
||||||
('ParameterModifier', 'Parameter Modifier', ''),
|
|
||||||
('SupportBlocker', 'Support Blocker', ''),
|
|
||||||
('SupportEnforcer', 'Support Enforcer', '')
|
|
||||||
],
|
|
||||||
default='ModelPart'
|
|
||||||
)
|
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_input("SLICESOCK_Object")
|
self.add_input("SLICESOCK_Object")
|
||||||
self.add_input("SLICESOCK_Overrides")
|
|
||||||
self.add_input("SLICESOCK_Int", "Extruder").value = 1
|
|
||||||
self.add_output("SLICESOCK_Mesh")
|
self.add_output("SLICESOCK_Mesh")
|
||||||
|
|
||||||
def draw_buttons(self, context, layout):
|
|
||||||
assert layout
|
|
||||||
layout.prop(self, "part_type")
|
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | None:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | None:
|
||||||
if socket_name != "Mesh": return None
|
if socket_name != "Mesh": return None
|
||||||
|
|
||||||
obj: Any = self.resolve("Object")
|
obj: Any = self.resolve("Object")
|
||||||
modifiers: dict[str, str] = self.resolve("Overrides")
|
|
||||||
extruder = self.resolve("Extruder")
|
modifiers = {v.field: v.value_str for k, v in dict(obj.nodeslicer.modifiers).items()}
|
||||||
part_type = self.part_type
|
extruder = obj.nodeslicer.extruder
|
||||||
|
part_type = obj.nodeslicer.object_type
|
||||||
|
|
||||||
so: SlicingObject = SlicingObject(obj, obj.name)
|
so: SlicingObject = SlicingObject(obj, obj.name)
|
||||||
so.assign_modifiers(modifiers)
|
so.assign_modifiers(modifiers)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,10 @@ class InputNodeSocket(SliceNodeSocket):
|
||||||
if not self.is_output and self.is_linked:
|
if not self.is_output and self.is_linked:
|
||||||
layout.label(text=text or self.name)
|
layout.label(text=text or self.name)
|
||||||
else:
|
else:
|
||||||
layout.prop(self, "value", text=text or self.name)
|
self._draw_prop(text, layout)
|
||||||
|
|
||||||
|
def _draw_prop(self, text, layout):
|
||||||
|
layout.prop(self, "value", text=text or self.name)
|
||||||
|
|
||||||
def _draw_as_ro(self, context: Any, layout: Any, node: Any, text: str | None):
|
def _draw_as_ro(self, context: Any, layout: Any, node: Any, text: str | None):
|
||||||
layout.label(text=text or self.name)
|
layout.label(text=text or self.name)
|
||||||
|
|
@ -131,6 +134,8 @@ class SLICESOCK_PrusaslicerConf(LabelNodeSocket):
|
||||||
default = None
|
default = None
|
||||||
bl_color = (0.95, 0., 0., 1.0) # #F26C21
|
bl_color = (0.95, 0., 0., 1.0) # #F26C21
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class OverrideItem(bpy.types.PropertyGroup):
|
class OverrideItem(bpy.types.PropertyGroup):
|
||||||
field: NfStringProperty(name="Field", default="") # pyright: ignore[reportInvalidTypeForm]
|
field: NfStringProperty(name="Field", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
@ -140,43 +145,19 @@ class OverrideItem(bpy.types.PropertyGroup):
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return bool(self.field) or bool(self.value_str)
|
return bool(self.field) or bool(self.value_str)
|
||||||
|
|
||||||
class CollectionInputNodeSocket(InputNodeSocket):
|
def draw_item(self, layout):
|
||||||
items: Any
|
row = layout.row(align=True)
|
||||||
|
row.prop(self, "field", text="")
|
||||||
|
row.prop(self, "value_str", text="")
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
from ...infra.mixins import CollectionMixin
|
||||||
super().__init__(*args, **kwargs)
|
class SLICESOCK_Overrides_Common(CollectionMixin, InputNodeSocket):
|
||||||
if len(self.items) == 0:
|
|
||||||
self.items.add()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
if self.items[-1].is_active:
|
|
||||||
self.items.add()
|
|
||||||
|
|
||||||
while len(self.items) >= 2:
|
|
||||||
if not self.items[-1].is_active and not self.items[-2].is_active:
|
|
||||||
self.items.remove(len(self.items) - 1)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class SLICESOCK_Overrides_Common(CollectionInputNodeSocket):
|
|
||||||
bl_label = "Overrides"
|
bl_label = "Overrides"
|
||||||
|
|
||||||
items: CollectionProperty(type=OverrideItem) # pyright: ignore[reportInvalidTypeForm]
|
items: CollectionProperty(type=OverrideItem) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
bl_color = (1.0, 1.0, 0.0, 1.0) # Yellow
|
bl_color = (1.0, 1.0, 0.0, 1.0) # Yellow
|
||||||
|
|
||||||
def draw(self, context, layout, node, text):
|
|
||||||
if not self.is_output and self.is_linked:
|
|
||||||
layout.label(text=text or self.name)
|
|
||||||
return
|
|
||||||
|
|
||||||
col = layout.column(align=True)
|
|
||||||
for item in self.items:
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.prop(item, "field", text="")
|
|
||||||
row.prop(item, "value_str", text="")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self) -> dict[str, str]:
|
def value(self) -> dict[str, str]:
|
||||||
overrides = {}
|
overrides = {}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import bpy
|
||||||
from bpy.types import NodeTree, Operator, Menu
|
from bpy.types import NodeTree, Operator, Menu
|
||||||
from bpy.app.handlers import persistent
|
from bpy.app.handlers import persistent
|
||||||
|
|
||||||
|
from ...infra.mixins import update_collection
|
||||||
from ...registry import register_class
|
from ...registry import register_class
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -91,13 +92,24 @@ def tree_update_handler(scene, depsgraph):
|
||||||
finally:
|
finally:
|
||||||
_is_updating = False
|
_is_updating = False
|
||||||
|
|
||||||
|
def object_init_handler(scene, depsgraph):
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
if not obj or not hasattr(obj, "nodeslicer"):
|
||||||
|
return
|
||||||
|
|
||||||
|
pg = getattr(obj, 'nodeslicer', None)
|
||||||
|
if not pg:
|
||||||
|
return
|
||||||
|
|
||||||
|
update_collection(pg.modifiers)
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def depsgraph_update_handler(scene, depsgraph):
|
def depsgraph_update_handler(scene, depsgraph):
|
||||||
global _is_updating, _tree_changed
|
global _is_updating, _tree_changed
|
||||||
if _is_updating: return
|
if _is_updating: return
|
||||||
|
|
||||||
tree_update_handler(scene, depsgraph)
|
tree_update_handler(scene, depsgraph)
|
||||||
|
object_init_handler(scene, depsgraph)
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICETREE_NodeTree(NodeTree):
|
class SLICETREE_NodeTree(NodeTree):
|
||||||
|
|
|
||||||
37
infra/mixins.py
Normal file
37
infra/mixins.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
class DrawCollectionMixin():
|
||||||
|
items: bpy.props.CollectionProperty # pyright: ignore[reportGeneralTypeIssues]
|
||||||
|
|
||||||
|
def draw_collection(self, items, layout):
|
||||||
|
col = layout.column(align=True)
|
||||||
|
for item in items:
|
||||||
|
item.draw_item(col)
|
||||||
|
|
||||||
|
def _draw_prop(self, text, layout):
|
||||||
|
self.draw_collection(self.items, layout)
|
||||||
|
|
||||||
|
def update_collection(items: bpy.props.CollectionProperty) -> None: # pyright: ignore[reportGeneralTypeIssues]
|
||||||
|
if len(items) == 0:
|
||||||
|
items.add()
|
||||||
|
|
||||||
|
if items[-1].is_active:
|
||||||
|
items.add()
|
||||||
|
|
||||||
|
while len(items) >= 2:
|
||||||
|
if not items[-1].is_active and not items[-2].is_active:
|
||||||
|
items.remove(len(items) - 1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
class UpdateCollectionMixin():
|
||||||
|
items: bpy.props.CollectionProperty # pyright: ignore[reportGeneralTypeIssues]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
update_collection(self.items)
|
||||||
|
|
||||||
|
class CollectionMixin(DrawCollectionMixin, UpdateCollectionMixin): ...
|
||||||
32
property_groups/bpy_property_groups.py
Normal file
32
property_groups/bpy_property_groups.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from ..core.nodeslicer.sockets import OverrideItem
|
||||||
|
from ..registry import register_class
|
||||||
|
|
||||||
|
extruder_options: list[tuple[str, str, str]] = [
|
||||||
|
("0", "Default Extruder", "Default Extruder"),
|
||||||
|
("1", "Extruder 1", "Extruder 1"),
|
||||||
|
("2", "Extruder 2", "Extruder 2"),
|
||||||
|
("3", "Extruder 3", "Extruder 3"),
|
||||||
|
("4", "Extruder 4", "Extruder 4"),
|
||||||
|
("5", "Extruder 5", "Extruder 5"),
|
||||||
|
]
|
||||||
|
|
||||||
|
object_type_options: list[tuple[str, str, str]] = [
|
||||||
|
("ModelPart", "Part", "Model Part"),
|
||||||
|
("NegativeVolume", "Negative Volume", "Negative Volume"),
|
||||||
|
("ParameterModifier", "Modifier", "Modifier"),
|
||||||
|
("SupportBlocker", "Support Blocker", "Support Blocker"),
|
||||||
|
("SupportEnforcer", "Support Enforcer", "Support Enforcer"),
|
||||||
|
("WipeTower", "Wipe Tower", "Wipe Tower"),
|
||||||
|
("Ignore", "Ignore", "Ignore"),
|
||||||
|
]
|
||||||
|
|
||||||
|
from ..core.nodeslicer.bpy_ns_types import NfEnumProperty, NfStringProperty
|
||||||
|
|
||||||
|
@register_class
|
||||||
|
class SlicerObjectPropertyGroup(bpy.types.PropertyGroup):
|
||||||
|
object_type: NfEnumProperty(name="Part type", default="ModelPart", items=object_type_options) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
extruder: NfEnumProperty(name="Extruder", default="0", items=extruder_options) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
search_term: NfStringProperty(name="Search") # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
modifiers: bpy.props.CollectionProperty(type=OverrideItem) # pyright: ignore[reportInvalidTypeForm]
|
||||||
79
property_groups/property_groups.py
Normal file
79
property_groups/property_groups.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.types import bpy_struct
|
||||||
|
|
||||||
|
import math
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
def _pv(inst: bpy_struct) -> 'PrusaSlicerTypes':
|
||||||
|
return cast(PrusaSlicerTypes, inst)
|
||||||
|
|
||||||
|
def get_prop_bool(inst: bpy_struct) -> bool:
|
||||||
|
return _pv(inst).param_value == '1'
|
||||||
|
|
||||||
|
def set_prop_bool(inst: bpy_struct, value: bool) -> None:
|
||||||
|
_pv(inst).param_value = '1' if value else '0'
|
||||||
|
|
||||||
|
def get_prop_float(inst: bpy_struct) -> float:
|
||||||
|
return float(_pv(inst).param_value) if _pv(inst).param_value else 0
|
||||||
|
|
||||||
|
def set_prop_float(inst: bpy_struct, value: float) -> None:
|
||||||
|
_pv(inst).param_value = str(round(value, 5))
|
||||||
|
|
||||||
|
def get_prop_int(inst: bpy_struct) -> int:
|
||||||
|
return int(_pv(inst).param_value) if _pv(inst).param_value else 0
|
||||||
|
|
||||||
|
def set_prop_int(inst: bpy_struct, value: int) -> None:
|
||||||
|
_pv(inst).param_value = str(value)
|
||||||
|
|
||||||
|
def get_prop_perc(inst: bpy_struct) -> float:
|
||||||
|
if _pv(inst).param_value:
|
||||||
|
return float(_pv(inst).param_value.rstrip('%')) if _pv(inst).param_value else 0
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def set_prop_perc(inst: bpy_struct, value: float) -> None:
|
||||||
|
_pv(inst).param_value = str(value)+'%'
|
||||||
|
|
||||||
|
def get_prop_angle(inst: bpy_struct) -> float:
|
||||||
|
return (float(_pv(inst).param_value) * math.pi) / 180
|
||||||
|
|
||||||
|
def set_prop_angle(inst: bpy_struct, value: float) -> None:
|
||||||
|
_pv(inst).param_value = str(round((value * 180) / math.pi, 5))
|
||||||
|
|
||||||
|
class PrusaSlicerTypes():
|
||||||
|
param_value: bpy.props.StringProperty(name='') # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
|
param_bool: bpy.props.BoolProperty(name='',
|
||||||
|
get=get_prop_bool,
|
||||||
|
set=set_prop_bool,
|
||||||
|
default=False,
|
||||||
|
) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
|
param_float: bpy.props.FloatProperty(name='',
|
||||||
|
get=get_prop_float,
|
||||||
|
set=set_prop_float,
|
||||||
|
soft_min=0,
|
||||||
|
step = 5,
|
||||||
|
) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
|
param_int: bpy.props.IntProperty(name='',
|
||||||
|
get=get_prop_int,
|
||||||
|
set=set_prop_int,
|
||||||
|
) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
|
param_perc: bpy.props.FloatProperty(name='',
|
||||||
|
get=get_prop_perc,
|
||||||
|
set=set_prop_perc,
|
||||||
|
subtype='PERCENTAGE',
|
||||||
|
soft_min=0,
|
||||||
|
soft_max=100,
|
||||||
|
step = 5,
|
||||||
|
) # pyright: ignore[reportInvalidTypeForm]
|
||||||
|
|
||||||
|
param_angle: bpy.props.FloatProperty(name='',
|
||||||
|
get=get_prop_angle,
|
||||||
|
set=set_prop_angle,
|
||||||
|
subtype='ANGLE',
|
||||||
|
soft_min=0,
|
||||||
|
soft_max=360,
|
||||||
|
step = 1,
|
||||||
|
) # pyright: ignore[reportInvalidTypeForm]
|
||||||
31
ui/panels.py
Normal file
31
ui/panels.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
from bpy.types import Panel
|
||||||
|
|
||||||
|
from ..infra.mixins import DrawCollectionMixin
|
||||||
|
|
||||||
|
from ..registry import register_class
|
||||||
|
|
||||||
|
@register_class
|
||||||
|
class SlicerObjectPanel(Panel, DrawCollectionMixin):
|
||||||
|
bl_label = "NodeSlicer Settings"
|
||||||
|
bl_idname = f"OBJECT_PT_nodeslicer"
|
||||||
|
bl_space_type = 'PROPERTIES'
|
||||||
|
bl_region_type = 'WINDOW'
|
||||||
|
bl_context = "object"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
if not layout: return
|
||||||
|
|
||||||
|
obj = context.object
|
||||||
|
if not obj: return
|
||||||
|
|
||||||
|
pg = getattr(obj, 'nodeslicer', None)
|
||||||
|
if not pg:
|
||||||
|
layout.label(text="Property not registered")
|
||||||
|
return
|
||||||
|
|
||||||
|
layout.prop(pg, "object_type", text="Object type")
|
||||||
|
layout.prop(pg, "extruder", text="Extruder")
|
||||||
|
|
||||||
|
if pg.object_type in ['ParameterModifier', 'ModelPart']:
|
||||||
|
self.draw_collection(pg.modifiers, layout)
|
||||||
Loading…
Add table
Reference in a new issue