OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / utility / geometry_nodes / node_boolean.py

315 lines · python ref:
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)