import bpy, pathlib
from .node_modifier import NodeModifier
from . import compat
NODES_FOLDER = 'HOPS_Nodes'
INPUTS = {'Tag Inset Slice Face Value', 'Clean Attribute 4', 'Tag Faces Name', 'Solver', 'Material', 'Transfer Normals', 'Weld', 'Clean Attribute 5', 'Clean Attribute 1', 'Inset Slice Offset', 'Tag Edge Boundary Name', 'Clean Attribute 3', 'Inset Thickness', 'Material Cut', 'Non Manifold', 'Inset Slice', 'Tag Inset Slice Edge Name', 'Operation', 'Tag Inset Slice Edge Value', 'Tag Edge Boundary Value', 'Object', 'Tag Faces Value', 'Input Geometry', 'Tag Inset Slice Face Name', 'Merge Threshold', 'Clean Attribute 2', 'Stitch Distance'}
OPERATION_INPUT = {0: 'INTERSECT', 'INTERSECT': 0, 1: 'UNION', 'UNION': 1, 2: 'DIFFERENCE', 'DIFFERENCE': 2, 3: 'INSET', 'INSET': 3, 4: 'KNIFE', 'KNIFE': 4}
SOLVER_INPUT = [itm.identifier for itm in bpy.types.BooleanModifier.bl_rna.properties['solver'].enum_items] # FLOAT/FAST, EXACT, MANIFOLD
# 5.2 stores menu sockets as their enum identifier instead of an index
OPERATION_MENU = {'INTERSECT': 'Intersect', 'UNION': 'Union', 'DIFFERENCE': 'Difference', 'INSET': 'Inset', 'KNIFE': 'Knife'}
OPERATION_MENU.update({menu: hops for hops, menu in OPERATION_MENU.items()})
SOLVER_MENU = ['Float', 'Exact', 'Manifold'] # ordered as SOLVER_INPUT
class NodeBoolean(NodeModifier):
default_name = 'HOPS_Boolean'
asset_name = 'HOPS_Boolean'
blend_filename = 'HOPS_Boolean.blend'
# input wrappers
@property
def input_geometry(self):
return self.inputs[self.input_map["Input Geometry"]]
@input_geometry.setter
def input_geometry(self, val):
self.inputs[self.input_map["Input Geometry"]] = val
self.update_tag()
@property
def operation(self):
'''DIFFERNCE, UNION, INTERSECT, INSET, KNIFE'''
value = self.inputs[self.input_map["Operation"]]
if compat.RNA_INPUTS: return OPERATION_MENU[value]
return OPERATION_INPUT[value]
@operation.setter
def operation(self, val):
self.inputs[self.input_map["Operation"]] = OPERATION_MENU[val] if compat.RNA_INPUTS else OPERATION_INPUT[val]
self.update_tag()
@property
def object(self):
return self.inputs[self.input_map["Object"]]
@object.setter
def object(self, val):
self.inputs[self.input_map["Object"]] = val
self.update_tag()
@property
def solver(self):
'''FLOAT, EXACT, MANIFOLD'''
value = self.inputs[self.input_map["Solver"]]
if compat.RNA_INPUTS: return SOLVER_INPUT[SOLVER_MENU.index(value)]
return SOLVER_INPUT[value]
@solver.setter
def solver(self, val):
index = SOLVER_INPUT.index(val)
self.inputs[self.input_map["Solver"]] = SOLVER_MENU[index] if compat.RNA_INPUTS else index
self.update_tag()
@property
def inset_thickness(self):
return self.inputs[self.input_map["Inset Thickness"]]
@inset_thickness.setter
def inset_thickness(self, val):
self.inputs[self.input_map["Inset Thickness"]] = float(val)
self.update_tag()
@property
def stitch_distance(self):
return self.inputs[self.input_map["Stitch Distance"]]
@stitch_distance.setter
def stitch_distance(self, val):
self.inputs[self.input_map["Stitch Distance"]] = float(val)
self.update_tag()
@property
def non_manifold(self):
return self.inputs[self.input_map["Non Manifold"]]
@non_manifold.setter
def non_manifold(self, val):
self.inputs[self.input_map["Non Manifold"]] = bool(val)
self.update_tag()
@property
def inset_slice(self):
return self.inputs[self.input_map["Inset Slice"]]
@inset_slice.setter
def inset_slice(self, val):
self.inputs[self.input_map["Inset Slice"]] = bool(val)
self.update_tag()
@property
def inset_slice_offset(self):
return self.inputs[self.input_map["Inset Slice Offset"]]
@inset_slice_offset.setter
def inset_slice_offset(self, val):
self.inputs[self.input_map["Inset Slice Offset"]] = float(val)
self.update_tag()
@property
def transfer_normals(self):
return self.inputs[self.input_map["Transfer Normals"]]
@transfer_normals.setter
def transfer_normals(self, val):
self.inputs[self.input_map["Transfer Normals"]] = bool(val)
self.update_tag()
@property
def material_cut(self):
return self.inputs[self.input_map["Material Cut"]]
@material_cut.setter
def material_cut(self, val):
self.inputs[self.input_map["Material Cut"]] = bool(val)
self.update_tag()
@property
def material(self):
return self.inputs[self.input_map["Material"]]
@material.setter
def material(self, val):
self.inputs[self.input_map["Material"]] = val
self.update_tag()
@property
def weld(self):
return self.inputs[self.input_map["Weld"]]
@weld.setter
def weld(self, val):
self.inputs[self.input_map["Weld"]] = bool(val)
self.update_tag()
@property
def merge_threshold(self):
return self.inputs[self.input_map["Merge Threshold"]]
@merge_threshold.setter
def merge_threshold(self, val):
self.inputs[self.input_map["Merge Threshold"]] = float(val)
self.update_tag()
@property
def tag_faces_name(self):
return self.inputs[self.input_map["Tag Faces Name"]]
@tag_faces_name.setter
def tag_faces_name(self, val):
self.inputs[self.input_map["Tag Faces Name"]] = val
self.update_tag()
@property
def tag_faces_value(self):
return self.inputs[self.input_map["Tag Faces Value"]]
@tag_faces_value.setter
def tag_faces_value(self, val):
self.inputs[self.input_map["Tag Faces Value"]] = float(val)
self.update_tag()
@property
def tag_edge_boundary_name(self):
return self.inputs[self.input_map["Tag Edge Boundary Name"]]
@tag_edge_boundary_name.setter
def tag_edge_boundary_name(self, val):
self.inputs[self.input_map["Tag Edge Boundary Name"]] = val
self.update_tag()
@property
def tag_edge_boundary_value(self):
return self.inputs[self.input_map["Tag Edge Boundary Value"]]
@tag_edge_boundary_value.setter
def tag_edge_boundary_value(self, val):
self.inputs[self.input_map["Tag Edge Boundary Value"]] = float(val)
self.update_tag()
@property
def tag_inset_slice_face_name(self):
return self.inputs[self.input_map["Tag Inset Slice Face Name"]]
@tag_inset_slice_face_name.setter
def tag_inset_slice_face_name(self, val):
self.inputs[self.input_map["Tag Inset Slice Face Name"]] = val
self.update_tag()
@property
def tag_inset_slice_face_value(self):
return self.inputs[self.input_map["Tag Inset Slice Face Value"]]
@tag_inset_slice_face_value.setter
def tag_inset_slice_face_value(self, val):
self.inputs[self.input_map["Tag Inset Slice Face Value"]] = float(val)
self.update_tag()
@property
def tag_inset_slice_edge_name(self):
return self.inputs[self.input_map["Tag Inset Slice Edge Name"]]
@tag_inset_slice_edge_name.setter
def tag_inset_slice_edge_name(self, val):
self.inputs[self.input_map["Tag Inset Slice Edge Name"]] = val
self.update_tag()
@property
def tag_inset_slice_edge_value(self):
return self.inputs[self.input_map["Tag Inset Slice Edge Value"]]
@tag_inset_slice_edge_value.setter
def tag_inset_slice_edge_value(self, val):
self.inputs[self.input_map["Tag Inset Slice Edge Value"]] = float(val)
self.update_tag()
@property
def clean_attribute_1(self):
return self.inputs[self.input_map["Clean Attribute 1"]]
@clean_attribute_1.setter
def clean_attribute_1(self, val):
self.inputs[self.input_map["Clean Attribute 1"]] = val
self.update_tag()
@property
def clean_attribute_2(self):
return self.inputs[self.input_map["Clean Attribute 2"]]
@clean_attribute_2.setter
def clean_attribute_2(self, val):
self.inputs[self.input_map["Clean Attribute 2"]] = val
self.update_tag()
@property
def clean_attribute_3(self):
return self.inputs[self.input_map["Clean Attribute 3"]]
@clean_attribute_3.setter
def clean_attribute_3(self, val):
self.inputs[self.input_map["Clean Attribute 3"]] = val
self.update_tag()
@property
def clean_attribute_4(self):
return self.inputs[self.input_map["Clean Attribute 4"]]
@clean_attribute_4.setter
def clean_attribute_4(self, val):
self.inputs[self.input_map["Clean Attribute 4"]] = val
self.update_tag()
@property
def clean_attribute_5(self):
return self.inputs[self.input_map["Clean Attribute 5"]]
@clean_attribute_5.setter
def clean_attribute_5(self, val):
self.inputs[self.input_map["Clean Attribute 5"]] = val
self.update_tag()
@classmethod
def is_valid_node_group(cls, node_group) -> bool:
return INPUTS.issubset(node_group.interface.items_tree.keys())
@classmethod
def get_or_load_node_group(cls):
if not cls.last_valid_group_name:
node_groups = list(filter(cls.is_asset_node_group, bpy.data.node_groups))
for ng in node_groups:
if cls.is_valid_node_group(ng):
return ng
try:
node_group = bpy.data.node_groups[cls.last_valid_group_name]
if cls.is_asset_node_group(node_group) and cls.is_valid_node_group(node_group):
return node_group
except KeyError:
pass
# if last loaded group isn't valid, load a new one
path = cls.get_filepath()
with bpy.data.libraries.load(path) as (data_from, data_to):
data_to.node_groups.append(cls.asset_name)
node_group = data_to.node_groups[0]
cls.last_valid_group_name = data_to.node_groups[0].name
return data_to.node_groups[0]
@classmethod
def calc_asset_path(cls):
blend = cls.blend_filename
path = pathlib.Path(__file__).parent / NODES_FOLDER
return str(path / blend)