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
|
||||
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 .ui import panels
|
||||
from .property_groups.bpy_property_groups import SlicerObjectPropertyGroup
|
||||
|
||||
bl_info = {
|
||||
"name": "NodeSlicer",
|
||||
|
|
@ -13,16 +15,33 @@ bl_info = {
|
|||
"category": "Node",
|
||||
}
|
||||
|
||||
_registered_nodeslicer = False
|
||||
|
||||
def register():
|
||||
global _registered_nodeslicer
|
||||
registry.blender_register_classes()
|
||||
bpy.types.NODE_MT_add.append(tree.draw_add_menu)
|
||||
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')
|
||||
|
||||
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)
|
||||
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')
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -376,35 +376,18 @@ class SLICENODE_IntMath(MathNode):
|
|||
class SLICENODE_GetMesh(SliceNode):
|
||||
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):
|
||||
self.add_input("SLICESOCK_Object")
|
||||
self.add_input("SLICESOCK_Overrides")
|
||||
self.add_input("SLICESOCK_Int", "Extruder").value = 1
|
||||
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:
|
||||
if socket_name != "Mesh": return None
|
||||
|
||||
obj: Any = self.resolve("Object")
|
||||
modifiers: dict[str, str] = self.resolve("Overrides")
|
||||
extruder = self.resolve("Extruder")
|
||||
part_type = self.part_type
|
||||
|
||||
modifiers = {v.field: v.value_str for k, v in dict(obj.nodeslicer.modifiers).items()}
|
||||
extruder = obj.nodeslicer.extruder
|
||||
part_type = obj.nodeslicer.object_type
|
||||
|
||||
so: SlicingObject = SlicingObject(obj, obj.name)
|
||||
so.assign_modifiers(modifiers)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ class InputNodeSocket(SliceNodeSocket):
|
|||
if not self.is_output and self.is_linked:
|
||||
layout.label(text=text or self.name)
|
||||
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):
|
||||
layout.label(text=text or self.name)
|
||||
|
|
@ -131,6 +134,8 @@ class SLICESOCK_PrusaslicerConf(LabelNodeSocket):
|
|||
default = None
|
||||
bl_color = (0.95, 0., 0., 1.0) # #F26C21
|
||||
|
||||
|
||||
|
||||
@register_class
|
||||
class OverrideItem(bpy.types.PropertyGroup):
|
||||
field: NfStringProperty(name="Field", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
|
|
@ -140,43 +145,19 @@ class OverrideItem(bpy.types.PropertyGroup):
|
|||
def is_active(self):
|
||||
return bool(self.field) or bool(self.value_str)
|
||||
|
||||
class CollectionInputNodeSocket(InputNodeSocket):
|
||||
items: Any
|
||||
def draw_item(self, layout):
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "field", text="")
|
||||
row.prop(self, "value_str", text="")
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
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):
|
||||
from ...infra.mixins import CollectionMixin
|
||||
class SLICESOCK_Overrides_Common(CollectionMixin, InputNodeSocket):
|
||||
bl_label = "Overrides"
|
||||
|
||||
items: CollectionProperty(type=OverrideItem) # pyright: ignore[reportInvalidTypeForm]
|
||||
|
||||
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
|
||||
def value(self) -> dict[str, str]:
|
||||
overrides = {}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import bpy
|
|||
from bpy.types import NodeTree, Operator, Menu
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
from ...infra.mixins import update_collection
|
||||
from ...registry import register_class
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -91,13 +92,24 @@ def tree_update_handler(scene, depsgraph):
|
|||
finally:
|
||||
_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
|
||||
def depsgraph_update_handler(scene, depsgraph):
|
||||
global _is_updating, _tree_changed
|
||||
if _is_updating: return
|
||||
|
||||
tree_update_handler(scene, depsgraph)
|
||||
|
||||
object_init_handler(scene, depsgraph)
|
||||
|
||||
@register_class
|
||||
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