OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / modals / to_shape_1_5.py

2299 lines · python ref:
import bpy
import bmesh
import enum
from math import radians, copysign, sqrt
from ... utility import addon, collections, object, math , modifier, operator_override, method_handler, context_copy
# from ... utils.blender_ui import get_dpi_factor
# from ... utils.context import ExecutionContext
from bpy.props import EnumProperty, FloatProperty, BoolProperty
from mathutils import Vector, Matrix, Euler

from ... ui_framework.master import Master
from ... ui_framework.utils.mods_list import get_mods_list
from ... utility.base_modal_controls import Base_Modal_Controls

# Cursor Warp imports
from ... utils.toggle_view3d_panels import collapse_3D_view_panels
from ... utils.modal_frame_drawing import draw_modal_frame
from ... utils.cursor_warp import mouse_warp

class Mouse_states(enum.Enum):
    none = enum.auto()
    scale = enum.auto()
    decap_adjsut = enum.auto()
    decap_solidify = enum.auto()
    hull_dissolve = enum.auto()
    shape_offset = enum.auto()
    cyl_diameter = enum.auto()
    alignment = enum.auto()

class Scroll_states(enum.Enum):
    none = enum.auto()
    cyl_segments = enum.auto()
    sphere_adjust = enum.auto()
    quad_sphere_adjust = enum.auto()

def selector_update(self, context):
    operator = HOPS_OT_Conv_To_Shape_1_5.operator
    if not operator: return

    operator.mouse_state = operator.mouse_def_states[operator.primitive_type]
    operator.scroll_state = operator.scroll_def_states[operator.primitive_type]
    operator.create_shape(context)


DESC = """To_Shape 1.5 \n
Convert selection to a myriad of shapes including empties.
Interactive version of the classic operator
"""


class HOPS_OT_Conv_To_Shape_1_5(bpy.types.Operator):
    bl_idname = "hops.to_shape_1_5"
    bl_label = "To_Shape 1.5"
    bl_options = {'REGISTER', 'UNDO'}
    bl_description = DESC

    operator = None
    individual: bpy.props.BoolProperty(
        name="Individual",
        description="Create shape per object using its local space",
        default = False)

    equalize_mode_items=[
        ('OFF', "OFF", "OFF"),
        ('ALL', "ALL", "ALL"),
        ('RADIUS', "RADIUS", "RADIUS"),
    ]

    equalize_mode: bpy.props.EnumProperty(
        name="Equalize mode",
        description="How shape is equalzied",
        items=equalize_mode_items,
        default='OFF')

    modified: bpy.props.BoolProperty(
        name="Modified",
        description="Take the bounding box dimensions from the modified object",
        default=True)

    alignment_items=[
        # ('AUTO_OBJECT', "AUTO_OBJECT", "Allign along longest object dimension"),
        # ('AUTO_MESH', "AUTO_MESH", "Allign along longest mesh dimension"),
        ('X', "X", "Allign along X axis"),
        ('Y', "Y", "Allign along Y axis"),
        ('Z', "Z", "Allign along Z axis"),
    ]

    alignment: bpy.props.EnumProperty(
        name="Alignment",
        description="What axis to allign along",
        items=alignment_items,
        default='Z')

    scale: bpy.props.FloatProperty(
        name="Scale",
        description="Scale",
        default=1,
        min = 0,
        precision = 3)

    copy_1st_bvl_items =[
        ('OFF', "OFF", "OFF"),
        ('ON', "ON", "OB"),
        ('ANGLE', "ANGLE", "ANGLE"),
    ]

    copy_1st_bvl: bpy.props.EnumProperty(
        name="Copy 1st bevel",
        description="Copy bevel",
        items=copy_1st_bvl_items,
        default='OFF')

    parent_mode_items=[
        ('OFF', "OFF", "No parenting"),
        ('ON', "ON", "Parent shape to source"),
        ('INVERSE', "INVERSE", "Parent soure to shape"),
    ]

    parent_mode: bpy.props.EnumProperty(
        name="Parent mode",
        description="Parenting",
        items=parent_mode_items,
        default='OFF')

    shape_offset_axis_items=[
        ('CENTER', "CENTER", "No offset"),
        ('+X', "+X", "Offset into +X direction"),
        ('+Y', "+Y", "Offset into +Y direction"),
        ('+Z', "+Z", "Offset into +Z direction"),
        ('-X', "-X", "Offset into -X direction"),
        ('-Y', "-Y", "Offset into -Y direction"),
        ('-Z', "-Z", "Offset into -Z direction"),
        ('ORIGIN', "ORIGIN", "Offset to origin")
    ]

    shape_offset_axis: bpy.props.EnumProperty(
        name="Shape offset",
        description="Relative unit offset",
        items=shape_offset_axis_items,
        default='CENTER')

    shape_offset: bpy.props.FloatProperty(
        name="Offset",
        description="Offset plane from selection",
        default=0)

    shade_wire: bpy.props.BoolProperty(
        name = 'Shade Wire',
        description = 'Shade Wirefame and disable in render',
        default = False)

    active_only: bpy.props.BoolProperty(
        name = 'Active Only',
        description = 'Only use active object',
        default = False)

    ##cylinder
    cyl_segments_default = 32
    cyl_segments: bpy.props.IntProperty(
        name="Segments",
        description="Number of segments",
        default=cyl_segments_default,
        min=3)

    cyl_diameter1_default = 1
    cyl_diameter1: bpy.props.FloatProperty(
        name="Diameter 1",
        description="Diameter 1",
        default=cyl_diameter1_default)

    cyl_diameter2_default = 1
    cyl_diameter2: bpy.props.FloatProperty(
        name="Diameter 2",
        description="Diameter 2",
        default=cyl_diameter2_default)

    ##sphere
    sphere_segments_default = 32
    sphere_segments: bpy.props.IntProperty(
        name="Segments",
        description="Nmber of segments",
        default=sphere_segments_default,
        min=3)

    sphere_rings_default = 16
    sphere_rings: bpy.props.IntProperty(
        name="Rings",
        description="Number of rings",
        default=sphere_rings_default,
        min=3)

    sphere_diameter_default = 1
    sphere_diameter: bpy.props.FloatProperty(
        name="Diameter",
        description="Diameter",
        default=sphere_diameter_default)

    ##quad sphere
    quad_sphere_divisions_default = 5
    quad_sphere_divisions: bpy.props.IntProperty(
        name="Divisions",
        description="Nmber of divisions",
        default=quad_sphere_divisions_default,
        min=1)

    ##plane

    plane_alignment_items=[
        ('X', "X", "Aaling on X axis"),
        ('Y', "Y", "Aaling on Y axis"),
        ('Z', "Z", "Aaling on Z axis")
    ]

    plane_alignment: bpy.props.EnumProperty(
        name="Plane axis",
        description="What side to create planeo on",
        items=plane_alignment_items,
        default='Z')

    # empty

    empty_display_items=[
        ('PLAIN_AXES', 'PLAIN_AXES', 'PLAIN_AXES'),
        ('SINGLE_ARROW', 'SINGLE_ARROW', 'SINGLE_ARROW'),
        ('CIRCLE', 'CIRCLE', 'CIRCLE'),
        ('CUBE', 'CUBE', 'CUBE'),
        ('SPHERE', 'SPHERE', 'SPHERE'),
        ('ARROWS', 'ARROWS', 'ARROWS'),
        ('CONE', 'CONE', 'CONE')
    ]

    empty_display: bpy.props.EnumProperty(
        name="Display type",
        description="Empty display type",
        items=empty_display_items,
        default='PLAIN_AXES')

    # Convex_Hull
    dissolve_angle_default = 5
    dissolve_angle: bpy.props.FloatProperty(
        name = "Dissolve Angles",
        description = "Dissolve faces below this angle",
        default = dissolve_angle_default,
        min = 0,
        max = 180,
        precision = 1
    )

    # Decap
    decap_thickness_default = 0.9
    decap_thickness: bpy.props.FloatProperty(
        name = "Thickness",
        description = "Relative thickness of remaining piece",
        default = decap_thickness_default,
        min = 0,
        max = 1
    )

    decap_center_default = 0.5
    decap_center: bpy.props.FloatProperty(
        name = "Center",
        description = "Relative center of remaining piece",
        default = decap_center_default,
        min = 0,
        max = 1
    )

    decap_fill_mode_items=[
        ('NO', "NO", "NO"),
        ('POS', "POS", "POS"),
        ('NEG', "NEG", "NEG"),
        ('BOTH', "BOTH", "BOTH"),
    ]

    decap_fill_mode: bpy.props.EnumProperty(
        name="Fill mode",
        description="Fill mode",
        items=decap_fill_mode_items,
        default='NO')

    decap_axis_items=[
        ('X', "X", "X"),
        ('Y', "Y", "Y"),
        ('Z', "Z", "Z"),
    ]

    decap_axis: bpy.props.EnumProperty(
        name="Axis",
        description="Slicing axis",
        items=decap_axis_items,
        default='Z')


    display_original : bpy.props.BoolProperty(
        name = "Display original",
        description = "Dispaly original object",
        default = True,
    )

    decap_cap_type_items=[
        ('NO', "NO", "No Caps"),
        ('ARRAY', "ARRAY", "Array-compatible"),
        ('DEFAULT', "DEFAULT", "Default"),
    ]

    decap_cap_type : bpy.props.EnumProperty(
        name="Cap types",
        description="Cap type",
        items=decap_cap_type_items,
        default='NO')

    decap_keep_caps_array : bpy.props.BoolProperty(
        name = "Array-compatible",
        description = "Adjust caps to work as array caps",
        default = False,
    )

    decap_solidify_default = False
    decap_solidify : bpy.props.BoolProperty(
        name = 'Solidify',
        description = 'Solidify 0 thickness slice to match original',
        default = decap_solidify_default
    )

    decap_solidify_multi_default = 1
    decap_solidify_multi : bpy.props.FloatProperty(
        name = 'Solidify multiplier',
        description = 'Solidify multiplier',
        default = decap_solidify_multi_default

    )

    #Curve

    curve_type_items=[
        ('BEZIER', 'BEZIER', 'BEZIER'),
        ('NURBS', 'NURBS', 'NURBS'),
    ]

    curve_type: bpy.props.EnumProperty(
        name='Type',
        description='Spline type',
        items=curve_type_items,
        default='BEZIER',
    )

    curve_axis_items=[
        ('X', "X", "X"),
        ('Y', "Y", "Y"),
        ('Z', "Z", "Z"),
    ]
    curve_axis: bpy.props.EnumProperty(
        name='Axis',
        description='Axis',
        items=curve_axis_items,
        default='Z'
    )


    #selector

    primitive_type_items=[
        ('Cube', "Cube", "Cube"),
        ('Plane', "Plane", "Plane"),
        ('Cylinder', "Cylinder", "Cylinder"),
        ('Sphere', "Sphere", "Sphere"),
        ('Quad_Sphere', "Quad Sphere", "Quad Sphere"),
        # ('Monkey', "Monkey", "Monkey"),
        ('Empty', 'Empty', 'Empty'),
        ('Convex_Hull', 'Convex Hull', 'Convex Hull'),
        ('Decap', 'Decap', 'Create visual copy and decaps it'),
        ('Curve', "Curve", "Curve"),
        ]

    primitive_type: bpy.props.EnumProperty(
        name="Primitive",
        description="Primitive type",
        items=primitive_type_items,
        default='Cube',
        update = selector_update)


    @classmethod
    def poll(cls, context):
        return context.active_object or context.selected_objects


    def draw(self, context):
        self.layout.prop(self, 'primitive_type')
        self.layout.prop(self, 'individual')

        if self.primitive_type not in {'Convex_Hull','Decap'}:
            self.layout.prop(self, 'equalize_mode')

        self.layout.prop(self, 'modified')
        self.layout.prop(self, 'scale')
        self.layout.prop(self, 'parent_mode')
        self.layout.prop(self, 'shape_offset_axis')
        self.layout.prop(self, 'shape_offset')
        self.layout.prop(self, 'active_only')
        self.layout.prop(self, 'shade_wire')

        if context.mode == 'OBJECT':
            self.layout.prop(self, 'display_original')

        if self.primitive_type not in self.no_bevel_club :
            self.layout.prop(self, 'copy_1st_bvl')

        if self.primitive_type == 'Cylinder':
            self.layout.prop(self, 'cyl_segments')
            self.layout.prop(self, 'cyl_diameter1')
            self.layout.prop(self, 'cyl_diameter2')
            self.layout.prop(self, 'alignment')

        elif self.primitive_type == 'Plane':
            self.layout.prop(self, "plane_alignment")

        elif self.primitive_type == 'Sphere':
            self.layout.prop(self, "sphere_segments")
            self.layout.prop(self, "sphere_rings")
            self.layout.prop(self, "sphere_diameter")
            self.layout.prop(self, 'alignment')

        elif self.primitive_type == 'Quad_Sphere':
            self.layout.prop(self, "quad_sphere_divisions")

        elif self.primitive_type == 'Empty':
            self.layout.prop(self, "empty_display")

        elif self.primitive_type == 'Convex_Hull':
            self.layout.prop(self, "dissolve_angle")

        elif self.primitive_type == 'Decap':
            self.layout.prop(self, "decap_axis")
            self.layout.prop(self, "decap_thickness")
            if not self.decap_thickness:
                self.layout.prop(self, 'decap_solidify')
                self.layout.prop(self, 'decap_solidify_multi')

            self.layout.prop(self, "decap_center")
            self.layout.prop(self, "decap_fill_mode")
            self.layout.prop(self, 'decap_cap_type')

        elif self.primitive_type == 'Curve':
            self.layout.prop(self, 'curve_type')
            self.layout.prop(self, 'curve_axis')

    def invoke (self, context, event):
        # if event.shift:
        #     self.individual = True
        # else:
        #     self.individual = False

        # if event.ctrl:
        #     self.primitive_type = 'Empty'
        #     self.individual = True
        #     self.parent_shape = True
        #     self.parent_shape_inverse = True
        #     self.empty_display = 'CUBE'

        if bmesh.ops.create_cone.__doc__.count("radius"):
            def create_cylinder(bm):
                bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=self.cyl_segments, radius1=self.cyl_diameter1/2, radius2=self.cyl_diameter2/2, depth=1)
        else:
            def create_cylinder(bm):
                bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=self.cyl_segments, diameter1=self.cyl_diameter1/2, diameter2=self.cyl_diameter2/2, depth=1)

        if bmesh.ops.create_uvsphere.__doc__.count("radius"):
            def create_uvsphere(bm):
                bmesh.ops.create_uvsphere (bm, u_segments = self.sphere_segments, v_segments = self.sphere_rings, radius = self.sphere_diameter/2 )
        else:
            def create_uvsphere(bm):
                bmesh.ops.create_uvsphere (bm, u_segments = self.sphere_segments, v_segments = self.sphere_rings, diameter = self.sphere_diameter/2 )

        self.create_cylinder = create_cylinder
        self.create_uvsphere = create_uvsphere

        self.mouse_def_states = {
            'Cube' : Mouse_states.none,
            'Plane' : Mouse_states.alignment,
            'Cylinder' : Mouse_states.alignment,
            'Sphere' : Mouse_states.alignment,
            'Quad_Sphere' : Mouse_states.none,
            'Empty' : Mouse_states.none,
            'Convex_Hull' : Mouse_states.none,
            'Decap' : Mouse_states.decap_adjsut,
            'Curve' : Mouse_states.alignment
        }

        self.scroll_def_states = {
            'Cube' : Scroll_states.none,
            'Plane' : Scroll_states.none,
            'Cylinder' : Scroll_states.none,
            'Sphere' : Scroll_states.none,
            'Quad_Sphere' : Scroll_states.none,
            'Empty' : Scroll_states.none,
            'Convex_Hull' : Scroll_states.none,
            'Decap' : Scroll_states.none,
            'Curve' : Scroll_states.none,
        }

        self.offset_map = {
            'CENTER' : Vector((0, 0, 0)),
            '+X'     : Vector((1, 0, 0)),
            '+Y'     : Vector((0, 1, 0)),
            '+Z'     : Vector((0, 0, 1)),
            '-X'     : Vector((-1, 0, 0)),
            '-Y'     : Vector((0, -1, 0)),
            '-Z'     : Vector((0, 0, -1)),
            'ORIGIN' : Vector((0, 0, 0)),
        }

        self.segment_presets = {
            "ONE" : 8,
            "TWO" : 12,
            "THREE" : 24,
            "FOUR" : 32,
            "FIVE" : 64,
            "SIX" : 128,
        }

        for val, i in enumerate(list(self.segment_presets.values()), 1):
            self.segment_presets[F'NUMPAD_{i}'] = val

        self.no_bevel_club = {'Plane', 'Empty', 'Convex_Hull', 'Decap', 'Curve'}
        self.mouse_state = self.mouse_def_states[self.primitive_type]
        self.scroll_state = self.scroll_def_states[self.primitive_type]
        self.last_decap_thick = self.decap_thickness
        self.mouse_buffer = 0
        self.mouse_threshold = 0.15
        self.active_only = False
        self.flair = False
        self.running = False

        self.notify = lambda val: bpy.ops.hops.display_notification(info=val) if addon.preference().ui.Hops_extra_info else lambda val: None
        self.elem_multi = lambda vect1, vect2 : Vector([a*b for a,b in zip(vect1, vect2)])

        bpy.context.view_layer.update()
        self.active_obj_name = context.active_object.name if context.active_object else ''

        # XXX : ST3
        self.selected = set(context.selected_objects)
        # self.selected = set([obj for obj in context.selected_objects if obj.name in context.view_layer.objects])

        if context.active_object and context.mode =='EDIT_MESH':
            self.selected.add(context.active_object)

        if context.mode == 'EDIT_MESH':
            self.get_coords = self.get_coords_edit
            self.deselect = lambda obj: None
            self.set_active = lambda primitive : None
            self.mesh_update = lambda obj: obj.update_from_editmode()

        else:
            self.get_coords = self.get_coords_obj
            self.deselect = lambda obj: obj.select_set(False)
            self.set_active = lambda primitive :set_active(primitive)
            self.mesh_update = lambda obj: None

        self.object_params_select = []
        self.object_params_active = []

        for obj in self.selected:
            self.mesh_update(obj)
            obj_data = Obj_data()
            obj_data.object_name = obj.name
            obj_data.object_matrix = obj.matrix_world.copy()
            obj_data.parent_name = obj.parent.name if obj.parent else ''
            obj_data.matrix_parent_inverse = obj.matrix_parent_inverse.copy()
            obj_data.hide = obj.hide_get()

            obj_data.bounds_base = self.get_coords(context, obj)
            obj_data.bounds_final = self.get_coords(context, obj, modified=True)

            obj_data.bounds_decap_base = self.get_coords_obj(context, obj)
            obj_data.bounds_decap_final = self.get_coords_obj(context, obj, modified=True)

            if len (obj_data.bounds_base)<2 or len(obj_data.bounds_final)<2 :
                continue

            if obj.type == 'EMPTY':
                obj_data.dimensions_base = Vector((1,1,1))
                obj_data.dimensions_final = Vector((1,1,1))
            else:
                obj_data.dimensions_base =  math.dimensions(obj_data.bounds_base)
                obj_data.dimensions_final =  math.dimensions(obj_data.bounds_final)
                obj_data.dimensions_decap_base =  math.dimensions(obj_data.bounds_decap_base)
                obj_data.dimensions_decap_final =  math.dimensions(obj_data.bounds_decap_final)

            obj_data.has_data = obj.type in {'MESH', 'CURVE', 'FONT', 'SURFACE', 'META'}

            obj_data.convex_valid = True
            if obj.type == 'MESH':
                if obj.mode == 'EDIT':
                    bm = bmesh.from_edit_mesh(obj.data)
                    obj_data.convex_valid = len([isinstance(elem, bmesh.types.BMVert) for elem in bm.select_history]) > 2

                else:
                    obj_data.convex_valid = len(obj.data.vertices) > 2

            obj_data.center_base = math.coords_to_center(obj_data.bounds_base)
            obj_data.center_final = math.coords_to_center(obj_data.bounds_final)
            obj_data.center_decap_base = math.coords_to_center(obj_data.bounds_decap_base)
            obj_data.center_decap_final = math.coords_to_center(obj_data.bounds_decap_final)

            self.object_params_select.append(obj_data)

            if obj == context.active_object:
                self.object_params_active.append(obj_data)

            if not self.display_original and context.mode == 'OBJECT':
                obj.hide_set(True)

        if not self.object_params_select:
            return {'CANCELLED'}

        self.created_shapes = set()
        self.deletable = set()
        self.hide_shapes = set()
        self.enable_mods = set()
        self.create_shape(context)
        self.notify(F'Shape: {self.primitive_type}')

        # Base Systems
        self.master = Master(context)
        self.master.only_use_fast_ui = True
        self.base_controls = Base_Modal_Controls(context, event, popover_keys=['TAB', 'SPACE'])
        self.original_tool_shelf, self.original_n_panel = collapse_3D_view_panels()
        self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.safe_draw_shader, (context,), 'WINDOW', 'POST_PIXEL')

        self.__class__.operator = self
        context.window_manager.modal_handler_add(self)
        self.running = True

        return {'RUNNING_MODAL'}

    def modal(self, context, event):

        # Base Systems
        self.master.receive_event(event=event)
        self.base_controls.update(context, event)
        mouse_warp(context, event)

        if self.base_controls.pass_through:
            return {'PASS_THROUGH'}

        elif self.base_controls.popover:
            context.window_manager.popover(popup_draw)

        elif self.base_controls.mouse:
            if self.mouse_state == Mouse_states.scale:
                self.scale += self.base_controls.mouse

            elif self.mouse_state == Mouse_states.decap_adjsut:
                if event.ctrl:
                    self.decap_center += self.base_controls.mouse
                else:
                    self.decap_thickness += self.base_controls.mouse

            elif self.mouse_state == Mouse_states.decap_solidify:
                if event.ctrl:
                    self.decap_center += self.base_controls.mouse
                else:
                    self.decap_solidify_multi += self.base_controls.mouse

            elif self.mouse_state == Mouse_states.hull_dissolve:
                self.dissolve_angle += self.base_controls.mouse*10

            elif self.mouse_state == Mouse_states.shape_offset:
                self.shape_offset += self.base_controls.mouse

            elif self.mouse_state == Mouse_states.cyl_diameter:

                if event.ctrl and not event.alt:
                    self.cyl_diameter1 += self.base_controls.mouse

                elif event.alt and not event.ctrl:
                    self.cyl_diameter2 += self.base_controls.mouse

                else:
                    self.cyl_diameter1 += self.base_controls.mouse
                    self.cyl_diameter2 = self.cyl_diameter1

            elif self.mouse_state == Mouse_states.alignment:
                self.mouse_buffer += self.base_controls.mouse

                if abs(self.mouse_buffer) >= self.mouse_threshold:

                    if self.primitive_type == 'Plane':
                        prop_name = 'plane_alignment'

                    elif self.primitive_type in {'Cylinder', 'Sphere'}:
                        prop_name = 'alignment'

                    elif self.primitive_type  == 'Curve':
                        prop_name = 'curve_axis'

                    setattr(self, prop_name, self.enum_scroll(prop_name, value= int(copysign(1, self.mouse_buffer)) ) )

                    self.mouse_buffer = 0
                    self.create_shape(context)

            if self.mouse_state not in {Mouse_states.none, Mouse_states.alignment}:
                self.create_shape(context, flair=False)

        elif self.base_controls.scroll:
            if self.scroll_state == Scroll_states.none:
                if event.shift:
                    prop_name = ''
                    message = ''

                    if self.primitive_type == 'Plane':
                        prop_name = 'plane_alignment'
                        message = 'Axis:'

                    elif self.primitive_type in {'Cylinder', 'Sphere'}:
                        prop_name = 'alignment'
                        message = 'Alignment:'

                    elif self.primitive_type == 'Empty':
                        prop_name = 'empty_display'
                        message = 'Display:'

                    elif self.primitive_type == 'Decap':
                        prop_name = 'decap_axis'
                        message = 'Axis:'

                    if prop_name:
                        setattr(self, prop_name, self.enum_scroll(prop_name, value=self.base_controls.scroll))
                        self.create_shape(context)
                        self.notify(F'{message} {getattr(self, prop_name)}')

                elif event.ctrl:

                    self.shape_offset_axis  = self.enum_scroll('shape_offset_axis', value=self.base_controls.scroll)
                    self.create_shape(context, flair=False)
                    self.notify(F'Shape offset: {self.shape_offset_axis}')

                else:

                    self.primitive_type = self.enum_scroll('primitive_type', value=self.base_controls.scroll)

                    self.notify(F'Shape: {self.primitive_type}')

            elif self.scroll_state == Scroll_states.cyl_segments:
                self.cyl_segments += self.base_controls.scroll
                self.create_shape(context, flair=False)

            elif self.scroll_state == Scroll_states.sphere_adjust:

                if event.shift:
                    self.sphere_rings += self.base_controls.scroll
                else:
                    self.sphere_segments += self.base_controls.scroll

                self.create_shape(context, flair= False)

            elif self.scroll_state == Scroll_states.quad_sphere_adjust:
                self.quad_sphere_divisions += self.base_controls.scroll
                self.create_shape(context, flair= False)

        elif self.base_controls.confirm:
            self.running = False
            self.flair = True
            self.remove_shader()
            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()

            self.__class__.operator = None

            for obj in  self.hide_shapes:
                obj.hide_set(True)

            for mod in self.enable_mods:
                mod.show_viewport =  True

            override = context_copy(context)
            override['edit_object'] = None
            override['selected_objects'] = list(self.created_shapes)
            override['mode'] = 'OBJECT'

            operator_override(context, bpy.ops.hops.draw_wire_mesh_launcher, override, 'INVOKE_DEFAULT', target='SELECTED')

            self.remove_deletables()

            self.created_shapes.clear()
            self.hide_shapes.clear()
            self.enable_mods.clear()
            self.deletable.clear()

            return {'FINISHED'}

        elif self.base_controls.cancel:
            self.running = False
            self.remove_shader()
            collapse_3D_view_panels(self.original_tool_shelf, self.original_n_panel)
            self.master.run_fade()

            self.__class__.operator = None

            self.purge_shapes()
            self.remove_deletables()

            if self.active_obj_name:
                context.view_layer.objects.active = bpy.data.objects[self.active_obj_name]

            for obj_data in self.object_params_select:
                obj = bpy.data.objects[obj_data.object_name]
                obj.parent = bpy.data.objects[obj_data.parent_name] if obj_data.parent_name else None
                obj.matrix_parent_inverse = obj_data.matrix_parent_inverse
                obj.matrix_world = obj_data.object_matrix
                obj.hide_set(obj_data.hide)
                obj.select_set(True)

            if self.active_obj_name:
                context.view_layer.objects.active = bpy.data.objects[self.active_obj_name]

            self.object_params_select.clear()
            self.object_params_active.clear()
            self.created_shapes.clear()
            self.hide_shapes.clear()
            self.enable_mods.clear()
            self.deletable.clear()

            return {'CANCELLED'}

        elif event.type == 'A' and event.value == 'PRESS':

            current_mouse_state = self.mouse_state
            current_scroll_state = self.scroll_state
            message = ''

            if self.primitive_type == 'Cylinder':
                if self.mouse_state == Mouse_states.cyl_diameter:

                    self.mouse_state = self.mouse_def_states[self.primitive_type]
                    self.scroll_state = self.scroll_def_states[self.primitive_type]
                else:
                    self.scroll_state = Scroll_states.cyl_segments
                    self.mouse_state = Mouse_states.cyl_diameter
                    message = 'Adjusting Cylinder'

            elif self.primitive_type == 'Sphere':
                self.scroll_state = Scroll_states.sphere_adjust if self.scroll_state != Scroll_states.sphere_adjust else Scroll_states.none
                message = 'Adjusting Sphere'

            elif self.primitive_type == 'Quad_Sphere':
                self.scroll_state = Scroll_states.quad_sphere_adjust if self.scroll_state != Scroll_states.quad_sphere_adjust else Scroll_states.none
                message = 'Adjusting Quad Sphere'

            elif self.primitive_type == 'Convex_Hull':
                self.mouse_state = Mouse_states.hull_dissolve if self.mouse_state != Mouse_states.hull_dissolve else Mouse_states.none
                message ='Adjusting Dissolve Angle'

            # elif self.primitive_type == 'Plane':
            #     self.mouse_state = Mouse_states.plane_offset if self.mouse_state != Mouse_states.plane_offset else Mouse_states.none
            #     message = 'Adjusting Offset'

            if self.mouse_state == self.mouse_def_states[self.primitive_type] and self.scroll_state == self.scroll_def_states[self.primitive_type]:
                message = 'Adjusment: OFF'

            if self.mouse_state == current_mouse_state and self.scroll_state == current_scroll_state:
                message = ''

            if message:
                self.notify(message)

        elif event.type == 'B' and event.value == 'PRESS':
            if self.primitive_type not in self.no_bevel_club:
                self.copy_1st_bvl = self.enum_scroll('copy_1st_bvl')
                self.create_shape(context)

                self.notify(F'Copy 1st bevel: {self.copy_1st_bvl}')

        elif event.type == 'C' and event.value == 'PRESS':
            if self.primitive_type == 'Decap':
                self.decap_cap_type = self.enum_scroll('decap_cap_type')
                self.create_shape(context)

                self.notify(F'Cap Type: {self.decap_cap_type}')

            elif self.primitive_type == 'Curve':
                self.curve_type = self.enum_scroll('curve_type')
                self.create_shape(context)

                self.notify(f'Curve Type: {self.curve_type}')

        elif event.type == 'D' and event.value == 'PRESS':
            if context.mode == 'OBJECT':
                self.display_original = not self.display_original

                for obj_data in self.object_params_select:
                    bpy.data.objects[obj_data.object_name].hide_set(not self.display_original)

                self.notify(F'Display Original: {self.display_original}')

        elif event.type == 'E' and event.value == 'PRESS':
            if self.primitive_type not in {'Convex_Hull','Decap'}:
                self.equalize_mode = self.enum_scroll('equalize_mode')
                self.create_shape(context)
                self.notify(F'Equalize: {self.equalize_mode}')

        elif event.type == 'F' and event.value == 'PRESS':
            if self.primitive_type == 'Decap':
                self.decap_fill_mode = self.enum_scroll('decap_fill_mode')
                self.create_shape(context)

                self.notify(F'Fill mode: {self.decap_fill_mode}')

        elif event.type == 'G' and event.value == 'PRESS':
            if event.ctrl:
                self.mouse_state = self.mouse_def_states[self.primitive_type]
                self.shape_offset = 0
                self.create_shape(context)
                self.notify('Offset reset')

            else:

                if self.mouse_state != Mouse_states.shape_offset:
                    self.mouse_state = Mouse_states.shape_offset
                    self.notify('Offset Adjust: ON')
                else:
                    self.mouse_state = self.mouse_def_states[self.primitive_type]
                    self.notify('Offset Adjust: OFF')

                    if self.primitive_type == 'Cylinder' and self.scroll_state == Scroll_states.cyl_segments:
                        self.mouse_state = Mouse_states.cyl_diameter

        elif event.type == 'I' and event.value == 'PRESS':
            self.individual = not self.individual
            self.create_shape(context)
            self.notify(F'Mode: {"Individual" if self.individual else "Combined"} ')

        elif event.type == 'P' and event.value == 'PRESS':
            self.parent_mode = self.enum_scroll('parent_mode')

            for obj_data in self.object_params_select:
                obj = bpy.data.objects[obj_data.object_name]
                obj.parent = bpy.data.objects[obj_data.parent_name] if obj_data.parent_name else None
                obj.matrix_parent_inverse = obj_data.matrix_parent_inverse
                obj.matrix_world = obj_data.object_matrix

            self.create_shape(context, flair = False)

            self.notify(F'Parenting: {self.parent_mode}')

        elif event.type == 'R' and event.value == 'PRESS':
            # if self.primitive_type == 'Plane':
            #     self.plane_offset = annotations['plane_offset'][1]['default']

            if self.primitive_type == 'Cylinder':
                self.cyl_segments = self.cyl_segments_default
                self.cyl_diameter1 = self.cyl_diameter1_default
                self.cyl_diameter2 = self.cyl_diameter2_default

            elif self.primitive_type == 'Sphere':
                self.sphere_segments = self.sphere_segments_default
                self.sphere_rings = self.sphere_rings_default

            elif self.primitive_type == 'Quad_Sphere':
                self.quad_sphere_divisions = self.quad_sphere_divisions_default

            elif self.primitive_type == 'Convex_Hull':
                self.dissolve_angle = self.dissolve_angle_default

            elif self.primitive_type == 'Decap':
                self.decap_thickness = self.decap_thickness_default
                self.decap_center = self.decap_center_default
                self.decap_solidify = self.decap_solidify_default
                self.decap_solidify_multi = self.decap_solidify_multi_default

            self.mouse_state = self.mouse_def_states[self.primitive_type]
            self.scroll_state = self.scroll_def_states[self.primitive_type]

            self.create_shape(context)

            self.notify(F'Reset {self.primitive_type} adjustments')


        elif event.type == 'S' and event.value == 'PRESS':

            if event.ctrl:
                self.mouse_state = self.mouse_def_states[self.primitive_type]
                self.scale = 1
                self.create_shape(context)
                self.notify('Scale reset')

            else:

                if self.mouse_state != Mouse_states.scale:
                    self.mouse_state = Mouse_states.scale
                    self.notify('Scale Adjust: ON')
                else:
                    self.mouse_state = self.mouse_def_states[self.primitive_type]
                    self.notify('Scale Adjust: OFF')

                    if self.primitive_type == 'Cylinder' and self.scroll_state == Scroll_states.cyl_segments:
                        self.mouse_state = Mouse_states.cyl_diameter

        elif event.type == 'T' and event.value == 'PRESS':
            if self.primitive_type == 'Decap':
                if self.decap_solidify:
                    self.decap_solidify = False
                    self.mouse_state = self.mouse_def_states[self.primitive_type]
                    self.decap_thickness = self.last_decap_thick

                    self.notify('ZERO Thickness: OFF')
                else:
                    self.decap_solidify = True
                    self.last_decap_thick = self.decap_thickness
                    self.decap_thickness = 0
                    self.decap_cap_type = 'NO'
                    self.decap_fill_mode = 'POS'
                    self.mouse_state = Mouse_states.decap_solidify

                    self.notify('ZERO Thickness: ON')

                self.create_shape(context)


        elif event.type == 'W' and event.value == 'PRESS':
            if event.shift:
                self.shade_wire = not self.shade_wire
                self.create_shape(context)
                self.notify(f'Shade {"Wire" if self.shade_wire else "Solid" }')

            else:
                self.modified = not self.modified
                self.create_shape(context)
                self.notify(F'Modified: {self.modified}')

        elif event.type == 'X' and event.value == 'PRESS':
            prop_name = ''
            message = ''

            if self.primitive_type == 'Plane':
                prop_name = 'plane_alignment'
                message = 'Axis:'

            elif self.primitive_type in {'Cylinder', 'Sphere'}:
                prop_name = 'alignment'
                message = 'Alignment:'

            elif self.primitive_type == 'Empty':
                prop_name = 'empty_display'
                message = 'Display:'

            elif self.primitive_type == 'Decap':
                prop_name = 'decap_axis'
                message = 'Axis:'

            if prop_name:
                setattr(self, prop_name, self.enum_scroll(prop_name))
                self.create_shape(context)
                self.notify(F'{message} {getattr(self, prop_name)}')

        elif event.type == 'Y' and event.value == 'PRESS':
            if self.object_params_active:
                self.active_only = not self.active_only

            self.notify (F'Active Only: {self.active_only}')
            self.create_shape(context)

        elif  event.type in self.segment_presets and event.value == 'PRESS':
            if self.primitive_type == 'Cylinder':
                preset = self.segment_presets[event.type]
                self.cyl_segments = preset
                self.notify(f'Segments: {self.cyl_segments}')
                self.create_shape(context)

            elif self.primitive_type == 'Sphere':
                preset = self.segment_presets[ event.type ]
                self.sphere_segments = preset
                self.sphere_rings = int(preset/2)
                self.notify(F'Segments: {self.sphere_segments}. Rings: {self.sphere_rings}')
                self.create_shape(context)

        self.draw_ui(context)
        context.area.tag_redraw()

        return {'RUNNING_MODAL'}

    def create_shape(self, context, flair=True):
        self.flair = flair

        self.purge_shapes()
        self.execute(context)

    def execute (self, context):
        self.created_shapes.clear()
        self.hide_shapes.clear()
        self.enable_mods.clear()

        object_params = self.object_params_active if self.active_only else self.object_params_select

        if self.individual:

            for obj_data in object_params:
                obj = bpy.data.objects[obj_data.object_name]

                if self.modified:

                    mesh_location = obj_data.center_final
                    dimensions = obj_data.dimensions_final

                else:

                    mesh_location = obj_data.center_base
                    dimensions = obj_data.dimensions_base

                obj_scale = obj.matrix_world.to_scale()
                dimensions_world = self.elem_multi(dimensions, obj_scale)

                if self.primitive_type == 'Decap':
                    if not obj.data : continue

                    if self.modified:

                        mesh_location = obj_data.center_decap_final
                        dimensions = obj_data.dimensions_decap_final

                    else:

                        mesh_location = obj_data.center_decap_base
                        dimensions = obj_data.dimensions_decap_base

                    primitives = self.decap(context, center = mesh_location , objects = [obj], dimensions=dimensions, scale_object = obj_scale ,  world=False )

                    primitive = primitives[0]

                    offset_vectors = [Vector((0,0,0,)), Vector((0,0,0,)), Vector((0,0,0,)),]

                    if self.decap_cap_type == 'ARRAY':

                        axis_vect = [self.decap_axis == a for a in 'XYZ']

                        array_offset = self.elem_multi(primitive.dimensions, axis_vect)

                        offset_vectors = [
                            Vector((0,0,0,)),
                            +array_offset,
                            -array_offset
                        ]

                        mod = primitive.modifiers.new(name='Array', type ='ARRAY')
                        mod.count = 1
                        mod.use_merge_vertices = True
                        mod.use_relative_offset = True
                        mod.use_constant_offset = False
                        mod.use_object_offset = False
                        mod.relative_offset_displace = axis_vect
                        mod.end_cap = primitives[1]
                        mod.start_cap = primitives[2]
                        mod.show_viewport = False

                        self.hide_shapes.add(primitives[1])
                        self.hide_shapes.add(primitives[2])
                        self.enable_mods.add(mod)

                    matrix = matrix_transfrom (obj.matrix_world, scale= Vector ((1,1,1)) )
                    quat = matrix.to_quaternion()
                    origin = obj.matrix_world @ mesh_location
                    origin_offset = quat @ self.elem_multi(primitive.dimensions/2, -self.offset_map[self.shape_offset_axis])

                    for prim, cap_offset in zip (primitives, offset_vectors):
                        prim.matrix_world = matrix.copy()
                        self.set_origin(prim, origin + quat @ cap_offset + origin_offset)

                        prim.matrix_world.translation += quat @ self.elem_multi(dimensions_world/2, self.offset_map[self.shape_offset_axis]) - origin_offset
                        prim.matrix_world.translation += quat @ self.offset_map[self.shape_offset_axis] * self.shape_offset

                        if not len(prim.data.vertices) and prim is not primitive:
                            me = prim.data
                            bpy.data.objects.remove(prim)
                            bpy.data.meshes.remove(me)
                            self.hide_shapes.discard(prim)
                            continue

                        self.created_shapes.add(prim)

                    if self.shape_offset_axis == 'ORIGIN':
                        primitive.matrix_world.translation += obj_data.object_matrix.translation - primitive.matrix_world.translation

                elif self.primitive_type == 'Empty':
                    location = obj.matrix_world @ mesh_location
                    primitive = self.add_empty(context, obj,  dimensions)
                    primitive.empty_display_size *= max (obj_scale)
                    primitive.empty_display_size *= self.scale
                    primitive.matrix_world = matrix_transfrom (obj.matrix_world, location=location, scale= Vector ((1,1,1)) )

                    quat = primitive.matrix_world.to_quaternion()

                    primitive.matrix_world.translation += quat @ self.elem_multi(dimensions_world/2, self.offset_map[self.shape_offset_axis])
                    primitive.matrix_world.translation += quat @ self.offset_map[self.shape_offset_axis] * self.shape_offset

                else:
                    if self.primitive_type == 'Convex_Hull':
                        if not obj_data.has_data or not obj_data.convex_valid: continue

                        primitive = self.convex_hull(context, center = mesh_location , objects = [obj], scale_object = obj_scale)
                        primitive_dimensions = primitive.dimensions

                    elif self.primitive_type == 'Curve':
                        primitive, primitive_dimensions = self.add_curve(context, obj, mesh_location, dimensions, obj_scale)

                    else:
                        primitive = self.pirmitive_add(context, obj, mesh_location, dimensions, scale_object= obj_scale)
                        primitive_dimensions = primitive.dimensions

                    primitive.matrix_world = matrix_transfrom(obj.matrix_world, scale=Vector ((1,1,1)) )

                    quat = primitive.matrix_world.to_quaternion()
                    origin = obj.matrix_world @ mesh_location
                    origin_offset = quat @ self.elem_multi(primitive_dimensions/2, -self.offset_map[self.shape_offset_axis])

                    if self.primitive_type == 'Plane' and self.plane_alignment == self.shape_offset_axis[1]:
                        origin_offset = Vector((0,0,0))

                    self.set_origin(primitive, origin + origin_offset)

                    primitive.matrix_world.translation += quat @ self.elem_multi(dimensions_world/2, self.offset_map[self.shape_offset_axis]) - origin_offset
                    primitive.matrix_world.translation += quat @ self.offset_map[self.shape_offset_axis] * self.shape_offset

                self.deselect(obj)
                self.set_active(primitive)

                self.created_shapes.add(primitive)

                if self.shape_offset_axis == 'ORIGIN' and self.primitive_type != 'Decap':
                    primitive.matrix_world.translation = obj_data.object_matrix.translation


                if self.shade_wire:
                    primitive.display_type = 'WIRE'
                    primitive.hide_render = True

                if self.parent_mode == 'ON':
                    set_parent (primitive, obj)

                elif self.parent_mode == 'INVERSE':
                    if self.active_only:
                        for obj_data in self.object_params_select:
                            set_parent(bpy.data.objects[obj_data.object_name], primitive)
                    else:
                        set_parent(obj, primitive)

        else:

            bbox_array=[]
            bounds_attr = 'bounds'

            if self.primitive_type == 'Decap':
                bounds_attr += '_decap'

            if self.modified:
                bounds_attr += '_final'

            else:
                bounds_attr += '_base'

            for obj_data in object_params:
                obj = bpy.data.objects[obj_data.object_name]
                bounds = getattr(obj_data, bounds_attr)

                bbox_array.extend([obj_data.object_matrix @ vec for vec in bounds])
                self.deselect(obj)

            if len (bbox_array) <2:
                return {'FINISHED'}

            bounds = math.coords_to_bounds(bbox_array)
            mesh_location = math.coords_to_center(bounds)
            dimensions = math.dimensions(bbox_array)

            if self.primitive_type == 'Decap':
                valid_objects = [bpy.data.objects[obj_data.object_name] for obj_data in object_params if obj_data.has_data]

                if not valid_objects:
                    return {'FINISHED'}

                primitives = self.decap(context, dimensions=dimensions, center = mesh_location , objects = valid_objects, world=True)

                primitive = primitives[0]

                offset_vectors = [Vector((0,0,0,)), Vector((0,0,0,)), Vector((0,0,0,)),]

                if self.decap_cap_type == 'ARRAY':

                    axis_vect = [self.decap_axis == a for a in 'XYZ']

                    array_offset = self.elem_multi(primitive.dimensions, axis_vect)

                    offset_vectors = [
                        Vector((0,0,0,)),
                        +array_offset,
                        -array_offset
                    ]

                    mod = primitive.modifiers.new(name='Array', type ='ARRAY')
                    mod.count = 1
                    mod.use_merge_vertices = True
                    mod.use_relative_offset = True
                    mod.use_constant_offset = False
                    mod.use_object_offset = False
                    mod.relative_offset_displace = axis_vect
                    mod.end_cap = primitives[1]
                    mod.start_cap = primitives[2]
                    mod.show_viewport = False

                    self.hide_shapes.add(primitives[1])
                    self.hide_shapes.add(primitives[2])
                    self.enable_mods.add(mod)

                origin_offset = self.elem_multi(primitive.dimensions/2, -self.offset_map[self.shape_offset_axis])

                for prim, cap_offset in zip (primitives, offset_vectors):
                    prim.matrix_world = Matrix()

                    self.set_origin(prim, mesh_location + cap_offset + origin_offset)

                    prim.matrix_world.translation += self.elem_multi(dimensions/2, self.offset_map[self.shape_offset_axis]) - origin_offset

                    prim.matrix_world.translation += self.offset_map[self.shape_offset_axis] * self.shape_offset

                    if not len(prim.data.vertices) and prim is not primitive:
                        me = prim.data
                        bpy.data.objects.remove(prim)
                        bpy.data.meshes.remove(me)
                        self.hide_shapes.discard(prim)
                        continue

                    self.created_shapes.add(prim)

                if self.shape_offset_axis == 'ORIGIN':
                    loc = sum([obj_data.object_matrix.translation for obj_data in object_params], Vector()) / len(object_params)
                    for p in primitives: p.matrix_world.translation += loc - p.matrix_world.translation

            elif self.primitive_type == 'Empty':
                primitive = self.add_empty(context, obj, dimensions)
                primitive.empty_display_size *= self.scale
                primitive.matrix_world = matrix_transfrom (Matrix(), location=mesh_location, scale= Vector ((1,1,1)) )

                primitive.matrix_world.translation += self.elem_multi(dimensions/2, self.offset_map[self.shape_offset_axis])
                primitive.matrix_world.translation += self.offset_map[self.shape_offset_axis] * self.shape_offset

            else:

                if self.primitive_type == 'Convex_Hull':
                    valid_objects = [bpy.data.objects[obj_data.object_name] for obj_data in object_params if obj_data.has_data and obj_data.convex_valid]

                    if not valid_objects:
                        return {'FINISHED'}

                    primitive = self.convex_hull(context, center = mesh_location , objects = valid_objects, world=True)
                    primitive_dimensions = primitive.dimensions

                elif self.primitive_type == 'Curve':
                    primitive, primitive_dimensions = self.add_curve(context, obj, mesh_location, dimensions)

                else:
                    primitive = self.pirmitive_add(context, obj,  mesh_location, dimensions)
                    primitive_dimensions = primitive.dimensions

                origin_offset = self.elem_multi(primitive_dimensions/2, -self.offset_map[self.shape_offset_axis])

                if self.primitive_type == 'Plane' and self.plane_alignment == self.shape_offset_axis[1]:
                    origin_offset = Vector((0,0,0))

                self.set_origin(primitive, mesh_location + origin_offset)

                primitive.matrix_world.translation += self.elem_multi(dimensions/2, self.offset_map[self.shape_offset_axis]) - origin_offset
                primitive.matrix_world.translation += self.offset_map[self.shape_offset_axis] * self.shape_offset

            self.created_shapes.add(primitive)

            if self.shape_offset_axis == 'ORIGIN' and self.primitive_type != 'Decap':
                loc = sum([obj_data.object_matrix.translation for obj_data in object_params], Vector()) / len(object_params)
                primitive.matrix_world.translation = loc


            if self.parent_mode == 'ON':
                if self.active_obj_name:
                    set_parent (primitive, bpy.data.objects[self.active_obj_name])

            elif self.parent_mode == 'INVERSE':

                if self.active_only:
                    for obj_data in self.object_params_select:
                        set_parent(bpy.data.objects[obj_data.object_name], primitive)
                else:
                    for obj_data in object_params:
                        obj = bpy.data.objects[obj_data.object_name]
                        set_parent(obj, primitive)

            self.set_active(primitive)

            if self.shade_wire:
                primitive.display_type = 'WIRE'
                primitive.hide_render = True

        if self.flair:
            override = context_copy(context)
            override['edit_object'] = None
            override['selected_objects'] = list(self.created_shapes)
            override['mode'] = 'OBJECT'

            operator_override(context, bpy.ops.hops.draw_wire_mesh_launcher, override,'INVOKE_DEFAULT', target='SELECTED')

        if not self.running:
            if context.mode == 'OBJECT':
                for obj_data in self.object_params_select:
                    bpy.data.objects[obj_data.object_name].hide_set(not self.display_original)

        return {'FINISHED'}


    def pirmitive_add (self, context, object, vector, dimensions, scale_object = Vector((1,1,1))) :
        primitive = self.primitive_type
        primitive_mesh = bpy.data.meshes.new(primitive)
        primitive_obj = bpy.data.objects.new(primitive, primitive_mesh)
        bm = bmesh.new()

        fit_matrix  = math.get_sca_matrix(scale_object) @ Matrix.Translation(vector) @ math.get_sca_matrix(dimensions)

        scale_center = Vector((0,0,0))

        max_vec = fit_matrix @ Vector((0.5,0.5,0.5))
        min_vec = fit_matrix @ Vector((-0.5,-0.5,-0.5))

        final_dimesnions = math.dimensions(  (min_vec, max_vec) )
        final_dim_max = axle = max(final_dimesnions)

        if primitive == 'Cube':
            bmesh.ops.create_cube(bm)

        elif primitive == 'Cylinder':
            self.create_cylinder(bm)

        elif primitive == 'Monkey':
            bmesh.ops.create_monkey(bm, matrix = math.get_sca_matrix([0.5, 0.5 ,0.5]))

        elif primitive == 'Plane':
           bm = self.plane_mesh ()
           bm.faces.ensure_lookup_table()
           scale_center = bm.faces[0].calc_center_median()

        elif primitive == 'Sphere':
            self.create_uvsphere(bm)

        elif primitive == 'Quad_Sphere':
            bmesh.ops.create_cube(bm, size=2.0)
            bmesh.ops.subdivide_edges(bm, edges=bm.edges[:], cuts=self.quad_sphere_divisions, use_grid_fill=True)

            for v in bm.verts:
                x = v.co.x * sqrt(1 - (v.co.y * v.co.y / 2 ) - ( v.co.z * v.co.z / 2 ) + ( (v.co.y * v.co.y * v.co.z * v.co.z ) / 3 ) ) * 0.5
                y = v.co.y * sqrt(1 - (v.co.z * v.co.z / 2 ) - ( v.co.x * v.co.x / 2 ) + ( (v.co.z * v.co.z * v.co.x * v.co.x ) / 3 ) ) * 0.5
                z = v.co.z * sqrt(1 - (v.co.x * v.co.x / 2 ) - ( v.co.y * v.co.y / 2 ) + ( (v.co.x * v.co.x * v.co.y * v.co.y ) / 3 ) ) * 0.5

                v.co.x = x
                v.co.y = y
                v.co.z = z


        if primitive in {'Cylinder', 'Sphere'}:
            XYZ = 'XYZ'

            if self.alignment == 'AUTO_OBJECT':

                index = list(final_dimesnions).index(axle)

                alignment = XYZ[index]

            elif self.alignment == 'AUTO_MESH':

                index = list(dimensions).index(max(dimensions))

                alignment = XYZ[index]

            else:
                alignment = self.alignment
                index = XYZ.index(self.alignment)

            if alignment == 'X':
                eul = Euler((0.0, radians(90.0), 0.0), XYZ)
            elif alignment == 'Y':
                eul = Euler((radians(-90.0), 0.0, 0.0), XYZ)
            elif alignment == 'Z':
                eul = Euler((0.0, 0.0, 0.0), XYZ)

            axle = final_dimesnions[index]

            bmesh.ops.transform(bm, matrix = math.get_rot_matrix(eul.to_quaternion()), verts = bm.verts)

        bmesh.ops.transform(bm, matrix = fit_matrix , verts = bm.verts)

        scale_vector = Vector((1,1,1))

        if self.equalize_mode != 'OFF':

            if self.equalize_mode == 'RADIUS':

                dims = list(final_dimesnions)
                dims.remove(axle)
                max_dim = max(dims)

            else:

                axle = max_dim = final_dim_max

            for i in range(3):
                if final_dimesnions[i] == axle:
                    continue
                scale_vector[i] = max_dim/final_dimesnions[i] if final_dimesnions[i] else final_dimesnions[i]

        scale_vector*=self.scale

        bmesh.ops.scale(bm, vec = scale_vector, verts = bm.verts, space = Matrix.Translation(-(fit_matrix @ scale_center)) )

        if self.copy_1st_bvl != 'OFF' and primitive not in self.no_bevel_club:
            if self.individual:
                source = object
            else:
                source = bpy.data.objects[self.active_obj_name] if self.active_obj_name else None

            if source:
                bevels = [mod for mod in source.modifiers if mod.type =='BEVEL']
                if bevels:
                    source_bvl = bevels[0]

                    if self.copy_1st_bvl == 'ANGLE':
                        angles = [bvl for bvl in bevels if bvl.limit_method =='ANGLE']
                        if angles:
                            source_bvl = angles[0]
                        else:
                            source_bvl = None

                    if source_bvl:

                        stored_source = modifier.stored(source_bvl)

                        modifier.new(primitive_obj, name=source_bvl.name, mod = stored_source)

                        del stored_source

                        dest_bvl = primitive_obj.modifiers[0]

                        if dest_bvl.limit_method not in {'NONE', 'ANGLE'}:
                            dest_bvl.limit_method = 'ANGLE'
                            dest_bvl.angle_limit = radians(30)
                        primitive_mesh.use_auto_smooth = True
                        primitive_mesh.auto_smooth_angle = getattr(object.data, 'auto_smooth_angle', radians(30))
                        for face in bm.faces:
                            face.smooth = True

        bm.to_mesh(primitive_mesh)
        bm. free()

        col = collections.find_collection(context, object, must_be_in_view_layer=True)
        col.objects.link(primitive_obj)
        return primitive_obj

    def convex_hull(self, context, center = Vector((0, 0, 0)) , objects = [], scale_object = Vector((1,1,1)), world = False):
        primitive = self.primitive_type
        primitive_mesh = bpy.data.meshes.new(primitive)
        primitive_obj = bpy.data.objects.new(primitive, primitive_mesh)
        bm = bmesh.new()

        if self.modified:
            for obj in objects:
                smooth_store =  getattr(obj.data, 'use_auto_smooth', False)

                if smooth_store : obj.data.use_auto_smooth = False

                depsgraph = context.evaluated_depsgraph_get()
                eval_obj = obj.evaluated_get(depsgraph)

                temp_mesh = eval_obj.to_mesh()

                if smooth_store : obj.data.use_auto_smooth = smooth_store
                primitive_mesh.use_auto_smooth = smooth_store
                primitive_mesh.auto_smooth_angle = temp_mesh.auto_smooth_angle

                matrix = obj.matrix_world if world else Matrix()
                temp_mesh.transform(matrix)

                bm.from_mesh(temp_mesh)
                obj.to_mesh_clear()

        else:
            for obj in objects:
                smooth_store =  getattr(obj.data, 'use_auto_smooth', False)
                if smooth_store : obj.data.use_auto_smooth = False

                temp_mesh = obj.to_mesh()

                if smooth_store : obj.data.use_auto_smooth = smooth_store
                primitive_mesh.use_auto_smooth = smooth_store
                primitive_mesh.auto_smooth_angle = temp_mesh.auto_smooth_angle

                matrix = obj.matrix_world if world else Matrix()
                temp_mesh.transform(matrix)

                bm.from_mesh(temp_mesh)
                obj.to_mesh_clear()


        if context.mode != 'OBJECT':
            verts = [v for v in bm.verts if not v.select]
            bmesh.ops.delete(bm, geom = verts, context = 'VERTS')

        ret= bmesh.ops.convex_hull(bm, input =bm.verts, use_existing_faces = True)

        filter = set(ret['geom'])

        del_verts = [v for v in bm.verts if v not in filter]

        bmesh.ops.delete(bm, geom = del_verts, context = 'VERTS')

        bmesh.ops.dissolve_limit(bm, angle_limit =radians(self.dissolve_angle) , use_dissolve_boundaries = False, verts = bm.verts , edges = bm.edges)

        fit_matrix = math.get_sca_matrix(scale_object)

        bmesh.ops.transform(bm, matrix = fit_matrix, verts = bm.verts)

        scale = Vector((self.scale,self.scale,self.scale))
        bmesh.ops.scale(bm, vec = scale , verts = bm.verts, space = Matrix.Translation(-(fit_matrix @ center)) )

        bm.to_mesh(primitive_mesh)
        bm.free()

        col = collections.find_collection(context, objects[0], must_be_in_view_layer=True)
        col.objects.link(primitive_obj)
        return primitive_obj

    def decap(self, context, objects = [], dimensions= Vector((1, 1, 1)) ,  center = Vector((0, 0, 0)), scale_object = Vector((1,1,1)), world = False):

        source = bmesh.new()

        smooth = False
        smooth_angle = radians(30)

        if self.modified:
            for obj in objects:
                #self.mesh_update(obj)

                smooth_store =  getattr(obj.data, 'use_auto_smooth', False)
                if smooth_store : obj.data.use_auto_smooth = False

                depsgraph = context.evaluated_depsgraph_get()
                eval_obj = obj.evaluated_get(depsgraph)
                temp_mesh = eval_obj.to_mesh()

                if smooth_store : obj.data.use_auto_smooth = smooth_store
                smooth = smooth_store
                smooth_angle = temp_mesh.auto_smooth_angle

                matrix = obj.matrix_world if world else Matrix()
                temp_mesh.transform(matrix)

                source.from_mesh(temp_mesh)
                obj.to_mesh_clear()
                #obj.hide_set(self.decap_hide_orig and context.mode == 'OBJECT')

        else:

            for obj in objects:
                #self.mesh_update(obj)

                smooth_store =  getattr(obj.data, 'use_auto_smooth', False)
                if smooth_store : obj.data.use_auto_smooth = False

                temp_mesh = obj.to_mesh()

                if smooth_store : obj.data.use_auto_smooth = smooth_store
                smooth = smooth_store
                smooth_angle = temp_mesh.auto_smooth_angle

                matrix = obj.matrix_world if world else Matrix()
                temp_mesh.transform(matrix)

                source.from_mesh(temp_mesh)
                obj.to_mesh_clear()

                #obj.hide_set(self.decap_hide_orig and context.mode == 'OBJECT')

        bms = []


        bm_main = source.copy()
        bms.append ( (bm_main, '') )

        decap_fill_neg = self.decap_fill_mode in {'NEG', 'BOTH'}
        decap_fill_pos = self.decap_fill_mode in {'POS', 'BOTH'}

        slicer(bm_main, dimensions=dimensions, center=center , axis = self.decap_axis,
                slice_thick=self.decap_thickness, cut_offset=self.decap_center,
                fill_neg=decap_fill_neg, fill_pos=decap_fill_pos)

        if self.decap_cap_type != 'NO':

            neg_thick = (self.decap_center - self.decap_thickness/2)*2
            pos_thick = (1 - (self.decap_center + self.decap_thickness/2))*2

            bm_pos = source.copy()

            slicer(bm_pos, dimensions=dimensions, center=center , axis = self.decap_axis,
                    slice_thick=pos_thick, cut_offset= 1,
                    fill_neg=decap_fill_pos, fill_pos=False)

            if bm_pos.verts:
                bms.append( (bm_pos, '_pos') )
            else:
                bm_pos.free()
                bms.append( (bmesh.new(), '_pos') )

            bm_neg = source.copy()

            slicer(bm_neg, dimensions=dimensions, center=center , axis = self.decap_axis,
                    slice_thick=neg_thick, cut_offset= 0,
                    fill_neg=False, fill_pos=decap_fill_neg)

            if bm_neg.verts:
                bms.append( (bm_neg, '_neg') )
            else:
                bm_neg.free()
                bms.append((bmesh.new(), '_neg' ))


        scale = Vector((self.scale, self.scale, self.scale))
        fit_matrix = math.get_sca_matrix(scale_object)

        if self.decap_solidify and not self.decap_thickness and bms[0][0].faces:
            bm = bms[0][0]
            index = 'XYZ'.index(self.decap_axis)
            axis_vector = Vector([self.decap_axis == v for v in 'XYZ'])

            offset = self.elem_multi(dimensions, axis_vector) * self.decap_center * self.decap_solidify_multi

            bmesh.ops.transform(bm, verts = bm.verts  ,matrix = matrix.Translation(-offset))

            for face in bm.faces:
                face.normal = axis_vector

            bmesh.ops.solidify(bm, geom = bm.faces, thickness= -dimensions[index]* self.decap_solidify_multi )


        for bm, _ in bms:

            bmesh.ops.transform(bm, matrix = fit_matrix, verts = bm.verts)
            bmesh.ops.scale(bm, vec = scale , verts = bm.verts, space = Matrix.Translation(-(fit_matrix @ center)) )


        primitives = []
        for bm, suffix in bms:
            primitive = self.primitive_type + suffix
            primitive_mesh = bpy.data.meshes.new(primitive)
            primitive_obj = bpy.data.objects.new(primitive, primitive_mesh)

            primitive_mesh.use_auto_smooth = smooth
            primitive_mesh.auto_smooth_angle = smooth_angle

            bm.to_mesh(primitive_mesh)
            bm.free()

            col = collections.find_collection(context, objects[0], must_be_in_view_layer=True)
            col.objects.link(primitive_obj)
            primitive_obj.select_set(True)

            primitives.append(primitive_obj)

        source.free()

        return primitives


    def get_coords_obj(self, context, obj, matrix = Matrix(), modified = False):
        if modified:
            bb = [matrix @ Vector(v) for v in obj.bound_box]

        else:

            tmp = bpy.data.objects.new("Bounding Box", obj.data)
            bb = [matrix @ Vector (v) for v in tmp.bound_box]
            bpy.data.objects.remove(tmp)

        return bb

    def get_coords_edit (self, context, obj, matrix = Matrix(), modified = False ):

        coords = []
        if modified:
            depsgraph = context.evaluated_depsgraph_get()
            obj_eval = obj.evaluated_get(depsgraph)
            data_eval = obj_eval.to_mesh()
            coords = [matrix @ v.co for v in data_eval.vertices if v.select]
            obj_eval.to_mesh_clear()
        if not coords :
            bm = bmesh.from_edit_mesh(obj.data)
            coords = [matrix @ v.co for v in bm.verts if v.select]

        return coords


    def set_origin (self, obj, center):
        obj.data.transform(Matrix.Translation( -(obj.matrix_world.inverted() @ center)))
        obj.matrix_world = matrix_transfrom (obj.matrix_world, location= center)

    def center_origin(self, obj, center = None) :

        center = object.center(obj) if not center else center
        obj.data.transform(Matrix.Translation(-center))
        obj.matrix_world = matrix_transfrom (obj.matrix_world, location=obj.matrix_world @ center)


    def plane_mesh (self):
        bm = bmesh.new()
        # bmesh.ops.create_cube(bm)
        # if self.plane_alignment == '+X':
        #     verts = [v for v in bm.verts if v.co[0]==-0.5]
        # elif self.plane_alignment == '+Y':
        #     verts = [v for v in bm.verts if v.co[1]==-0.5]
        # elif self.plane_alignment == '+Z':
        #     verts = [v for v in bm.verts if v.co[2]==-0.5]
        # elif self.plane_alignment == '-X':
        #     verts = [v for v in bm.verts if v.co[0]==0.5]
        # elif self.plane_alignment == '-Y':
        #     verts = [v for v in bm.verts if v.co[1]==0.5]
        # elif self.plane_alignment == '-Z':
        #     verts = [v for v in bm.verts if v.co[2]==0.5]

        # bmesh.ops.delete(bm, geom = verts, context = 'VERTS')

        # offset = self.plane_offset
        # if self.plane_alignment in {'-X', '-Y', '-Z'}:
        #     offset*= -1
        # if self.plane_alignment in {'+X', '-X'}:
        #     bmesh.ops.transform(bm, matrix = Matrix.Translation(Vector((offset,0,0))), verts = bm.verts)
        # elif self.plane_alignment in {'+Y', '-Y'}:
        #     bmesh.ops.transform(bm, matrix = Matrix.Translation(Vector((0,offset,0))), verts = bm.verts)
        # elif self.plane_alignment in {'+Z', '-Z'}:
        #     bmesh.ops.transform(bm, matrix = Matrix.Translation(Vector((0,0,offset))), verts = bm.verts)

        alignment = self.plane_alignment
        XYZ = 'XYZ'
        if alignment == 'X':
            eul = Euler((0.0, radians(90.0), 0.0), XYZ)
        elif alignment == 'Y':
            eul = Euler((radians(-90.0), 0.0, 0.0), XYZ)
        elif alignment == 'Z':
            eul = Euler((0.0, 0.0, 0.0), XYZ)

        bmesh.ops.create_grid(bm, x_segments =1, y_segments = 1, matrix = eul.to_matrix().to_4x4(), size = 0.5)

        return bm

    def add_empty(self, context, object, dimensions):
        primitive = self.primitive_type
        primitive_obj = bpy.data.objects.new(primitive, None)
        col = collections.find_collection(context, object, must_be_in_view_layer=True)
        col.objects.link(primitive_obj)
        primitive_obj.empty_display_type = self.empty_display
        primitive_obj.empty_display_size = max (dimensions)*0.6

        return primitive_obj

    def add_curve(self, context, object, center, dimensions, scale = Vector((1, 1, 1))):
        primitive = self.primitive_type
        primitive_data = bpy.data.curves.new(primitive, 'CURVE')
        primitive_obj = bpy.data.objects.new(primitive, primitive_data)
        primitive_obj.show_in_front = True
        col = collections.find_collection(context, object, must_be_in_view_layer=True)
        col.objects.link(primitive_obj)
        primitive_data.dimensions = '3D'

        center  = self.elem_multi(scale, center)
        dimensions = self.elem_multi(dimensions, scale) * self.scale
        index = 'XYZ'.index(self.curve_axis)

        for i in range(3):
            if i == index: continue
            dimensions[i] = 0

        _max = dimensions / 2
        points = (-_max, _max)
        count = 2

        primitive_data.splines.clear()
        spline = primitive_data.splines.new(self.curve_type)
        spline.use_endpoint_u = True

        if self.curve_type == 'BEZIER':
            spline.bezier_points.add(count - 1)
            handle_offset = ((points[-1] - points[0]) / count) * 0.5

            for point, vec in zip(spline.bezier_points, points):
                vec += center
                point.co = vec
                point.radius = 1

                point.handle_left_type = 'ALIGNED'
                point.handle_right_type = 'ALIGNED'
                point.handle_right = vec + handle_offset
                point.handle_left = vec - handle_offset

        else:
            spline.points.add(count - 1)
            #spline.order_u = order_u

            for point, vec in zip(spline.points, points):
                vec += center
                point.co[0] = vec[0]
                point.co[1] = vec[1]
                point.co[2] = vec[2]
                point.co[3] = 1
                point.radius = 1

        return primitive_obj, dimensions

    def purge_shapes(self):
        self.remove_deletables()
        self.deletable.update(self.created_shapes)

        for obj in self.created_shapes:
            obj.name += '_'
            if obj.data:
                obj.data.name += '_'

            for col in obj.users_collection[:]:
                col.objects.unlink(obj)

        self.created_shapes.clear()
        self.hide_shapes.clear()
        self.enable_mods.clear()

    def remove_deletables(self):
        for obj in self.deletable:
            data = obj.data
            type = obj.type

            bpy.data.objects.remove(obj)

            if data:
                if type == 'MESH':
                    bpy.data.meshes.remove(data)

                else:
                    bpy.data.curves.remove(data)

        self.deletable.clear()

    def enum_scroll(self, prop, value = 1):
        attribute = getattr(self, prop + '_items')
        items = [item[0] for item in attribute]
        return items[ (items.index(getattr(self, prop)) + value ) % len(items) ]


    def draw_ui(self, context):

        self.master.setup()

        # -- Fast UI -- #
        if not self.master.should_build_fast_ui(): return

        # Main
        win_list = []
        if addon.preference().ui.Hops_modal_fast_ui_loc_options != 1:
            win_list.append(self.primitive_type)
            win_list.append(self.shape_offset_axis)
            if self.shape_offset > 0:
                win_list.append(F'{self.shape_offset:.3f}')
            if self.individual:
                win_list.append('I' if self.individual else 'C')
            if not self.modified:
                win_list.append(F"M: {self.modified}")
            if self.equalize_mode != "OFF":
                if self.primitive_type not in {'Empty', 'Decap', 'Convex_Hull' }:
                    win_list.append(self.equalize_mode)
            if self.scale != 1:
                win_list.append(F"{self.scale:.3f}")
            if self.parent_mode != "OFF":
                win_list.append(self.parent_mode)

            if self.primitive_type == 'Plane':
                win_list.append(self.plane_alignment)
                #win_list.append(F'{self.plane_offset:.3f}')

            elif self.primitive_type == 'Cylinder':
                #win_list.append(self.alignment)
                win_list.append(self.cyl_segments)

            elif self.primitive_type == 'Sphere':
                #win_list.append(self.alignment)
                win_list.append(self.sphere_segments)
                win_list.append(self.sphere_rings)

            elif self.primitive_type == 'Quad_Sphere':
                #win_list.append(self.alignment)
                win_list.append(self.quad_sphere_divisions)

            # elif self.primitive_type == 'Empty':
            #     win_list.append(self.empty_display)

            elif self.primitive_type == 'Decap':
                win_list.append(self.decap_axis)
                if self.decap_fill_mode != 'NO':
                    win_list.append(self.decap_fill_mode)
                if self.decap_cap_type != 'NO':
                    win_list.append(self.decap_cap_type)
                win_list.append(f'{self.decap_thickness:.3f}' if not self.decap_solidify else 'Z')
                win_list.append(f'{self.decap_center:.3f}')

            elif self.primitive_type == 'Convex_Hull':
                win_list.append(f'{self.dissolve_angle:.1f}')

            elif self.primitive_type == 'Curve':
                win_list.append(self.curve_type[0])
                win_list.append(self.curve_axis)


            # if self.primitive_type not in self.no_bevel_club:
            #     win_list.append(self.copy_1st_bvl)

        else:
            win_list.append(self.primitive_type)
            win_list.append(self.shape_offset_axis)
            win_list.append(F'Offset[G]: {self.shape_offset:.3f}')
            #win_list.append(F"Mode[I]: {'Individual' if self.individual else 'Combined'}")
            #win_list.append(F"Modified: {self.modified}")
            # if self.primitive_type not in {'Empty', 'Decap', 'Convex_Hull' }:
            #     win_list.append(F"Equalize: {self.equalize_mode}")
            win_list.append(F"Scale[S]: {self.scale:.3f}")
            #win_list.append(f"Parenting: {self.parent_mode}")
            #win_list.append(f"Show Original: {self.display_original}")

            if self.primitive_type == 'Plane':
                win_list.append(F'Axis: {self.plane_alignment}')
                #win_list.append(F'Offset: {self.plane_offset:.3f}')

            elif self.primitive_type == 'Cylinder':
                win_list.append(F'Alignment: {self.alignment}')
                win_list.append(F'Segments: {self.cyl_segments}')

            elif self.primitive_type == 'Sphere':
                win_list.append(F'Alignment: {self.alignment}')
                win_list.append(F'Segments: {self.sphere_segments}')
                win_list.append(F'Rings: {self.sphere_rings}')

            elif self.primitive_type == 'Quad_Sphere':
                win_list.append(F'Divisions: {self.quad_sphere_divisions}')

            elif self.primitive_type == 'Empty':
                win_list.append(F'Display: {self.empty_display}')

            elif self.primitive_type == 'Decap':
                win_list.append(F'Axis: {self.decap_axis}')
                #win_list.append(F"Fill: {self.decap_fill_mode}")
                #win_list.append(F"Caps: {self.decap_cap_type}")
                win_list.append(F'Thickness: {self.decap_thickness:.3f}' if not self.decap_solidify else 'ZERO')
                win_list.append(F'Center: {self.decap_center:.3f}')

            elif self.primitive_type == 'Convex_Hull':
                win_list.append(F'Dissolve:{self.dissolve_angle:.1f}')

            elif self.primitive_type == 'Curve':
                win_list.append(F'Type[C]: {self.curve_type}')
                win_list.append(F'Axis: {self.curve_axis}')

            # if self.primitive_type not in self.no_bevel_club:
            #     win_list.append(F'Copy 1st BVL: {self.copy_1st_bvl}')

        # Help
        help_items = {"GLOBAL" : [], "STANDARD" : []}

        help_items["GLOBAL"] = [
            ("M", "Toggle mods list"),
            ("H", "Toggle help"),
            ("~", "Toggle UI Display Type"),
            ("O", "Toggle viewport rendering")]

        help_append = help_items["STANDARD"].append

        if self.primitive_type == 'Decap':
            help_append(["C", f"Caps mode - {self.decap_cap_type}"])
            help_append(["F", f"Fill Mode - {self.decap_fill_mode}"])
            help_append(["T", "Toggle ZERO thickness mode"])

        elif self.primitive_type == 'Curve':
            help_append(["C", f"Type - {self.curve_type}"])

        if self.primitive_type not in self.no_bevel_club:
            help_append(["B", f"1st bevel - {self.copy_1st_bvl}"])

        if self.primitive_type in {'Plane', 'Sphere', 'Cylinder', 'Decap'}:
            help_append(["X  ", 'Adjust alignment'])

        help_append(["Y",  F"Active Only: {self.active_only}"])
        help_append(["Ctrl+S", "Reset Scale"])
        help_append(["Ctrl+G", "Reset Offset"])
        help_append(["G", "Toggle Offset adjust"])
        help_append(["S", "Toggle Scale adjust"])
        help_append(["P", f"Parenting Mode {self.parent_mode}"])
        help_append(["I", f"Toggle {'Individual' if self.individual else 'Combined'}"])
        help_append(["R", "Reset shape Adjustment"])
        help_append(["A", "Toggle Adjustment mode"])
        help_append(["D", F"Display original: {self.display_original}"])
        help_append(["E", f"Equalize mode - {self.equalize_mode}"])
        help_append(["Shift+W", f"Shade {'Solid' if self.shade_wire else 'Wire'}"])
        help_append(["W", f"Toggle Modified {self.modified}"])
        help_append(["LMB", "Apply"])
        help_append(["RMB", "Cancel"])

        # scroll
        if self.scroll_state == Scroll_states.none:
            if self.primitive_type in {'Plane', 'Sphere', 'Cylinder', 'Decap'}:
                help_append(["Shift+Scroll  ", 'Adjust alignment'])

            elif self.primitive_type == 'Empty':
                help_append(["Shift+Scroll ", 'Adjust Display type'])

            help_append(["Ctrl+Scroll  ", "Adjust offset direction"])
            help_append(["Scroll", "Cycle Shape"])

        elif self.scroll_state == Scroll_states.cyl_segments:
            help_append(["Scroll ", 'Adjust Segments'])

        elif self.scroll_state == Scroll_states.sphere_adjust:
            help_append(["Shift+Scroll ", 'Adjust Rings'])
            help_append(["Scroll", 'Adjust Segmnets'])

        elif self.scroll_state == Scroll_states.sphere_adjust:
            help_append(["Scroll", 'Adjust Division'])

        # mouse
        if self.mouse_state == Mouse_states.hull_dissolve:
            help_append(["Mouse", "Adjust Dissolve angle"])

        elif self.mouse_state == Mouse_states.shape_offset:
            help_append(["Mouse", "Adjust Offset"])

        elif self.mouse_state == Mouse_states.decap_adjsut:
            help_append(["Ctrl+Mouse ", "Adjust Center"])
            help_append(["Mouse", "Adjust Thickness"])

        elif self.mouse_state == Mouse_states.decap_solidify:
            help_append(["Ctrl+Mouse ", "Adjust Center"])
            help_append(["Mouse", "Adjust Solidify"])

        elif self.mouse_state == Mouse_states.scale:
            help_append(["Mouse", "Adjust Scale"])

        elif self.mouse_state == Mouse_states.cyl_diameter:
            help_append(["Alt+Mouse ", "Adjust Diameter 2"])
            help_append(["Ctrl+Mouse ", "Adjust Diameter 1"])
            help_append(["Mouse", "Adjust Diameter"])

        if 'SPACE' in self.base_controls.popover_keys:
            help_items["STANDARD"].append(('Space', 'Open Select Menu'))
        elif 'TAB' in self.base_controls.popover_keys:
            help_items["STANDARD"].append(('TAB', 'Open Select Menu'))

        # Mods
        mods_list = get_mods_list(mods=bpy.context.active_object.modifiers) if context.active_object else []

        self.master.receive_fast_ui(win_list=win_list, help_list=help_items, image="Tthick", mods_list=mods_list)

        self.master.finished()

    ####################################################
    #   CURSOR WARP
    ####################################################

    def safe_draw_shader(self, context):
        method_handler(self.draw_shader,
            arguments = (context,),
            identifier = 'UI Framework',
            exit_method = self.remove_shader)


    def remove_shader(self):
        '''Remove shader handle.'''

        if self.draw_handle:
            self.draw_handle = bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW")


    def draw_shader(self, context):
        '''Draw shader handle.'''

        draw_modal_frame(context)

def set_parent (child, parent):
    buffer = child.matrix_world.copy()
    child.parent = parent
    child.matrix_parent_inverse = parent.matrix_world.inverted()
    child.matrix_world = buffer

def matrix_transfrom (matrix, location = None, rotation = None, scale = None):
    loc, rot, sca = matrix.decompose()
    loc = location if location else loc
    rot = rotation if rotation else rot
    sca = scale if scale else sca

    mat_loc = Matrix.Translation(loc)
    mat_rot = rot.to_matrix().to_4x4()
    mat_sca = math.get_sca_matrix (sca)

    return mat_loc @ mat_rot @  mat_sca

def apply_scale_matrix(obj):
    obj.data.transform(math.get_sca_matrix(obj.scale))
    obj.matrix_world = matrix_transfrom(obj.matrix_world, scale= Vector((1,1,1,)) )

def set_active(obj):
    obj.select_set(True)
    bpy.context.view_layer.objects.active = obj


def slicer(bm, dimensions = Vector((1,1,1,)), center = Vector((0,0,0,)) ,  axis = 'Z', slice_thick = 0.1, cut_offset = 0.5, fill_neg = True, fill_pos = True ):
    slice_thick /=2

    index = 'XYZ'.index(axis)

    dimension = dimensions[index]

    normal = Vector( [axis == s for s in 'XYZ'] )

    offset_unit = normal*dimension

    bottom = center -(offset_unit/2)

    positive_slice = bottom + (offset_unit * slice_thick) + (offset_unit * cut_offset)

    negative_slice = bottom - (offset_unit * slice_thick) + (offset_unit * cut_offset)

    if slice_thick:

        positive_cut = bmesh.ops.bisect_plane(bm, geom = bm.verts[:]+ bm.edges[:] + bm.faces[:] ,
            dist = 0, plane_co =positive_slice , plane_no = normal,
            use_snap_center= False, clear_outer = True, clear_inner= False)

        if fill_pos:
            positive_edges = [e for e in positive_cut['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
            ret =  bmesh.ops.holes_fill(bm, edges = positive_edges)


        negative_cut = bmesh.ops.bisect_plane(bm, geom = bm.verts[:]+ bm.edges[:] + bm.faces[:] ,
            dist = 0, plane_co =negative_slice , plane_no = normal,
            use_snap_center= False, clear_outer = False, clear_inner= True)

        if fill_neg:
            negative_edges = [e for e in negative_cut['geom_cut'] if isinstance(e, bmesh.types.BMEdge)]
            bmesh.ops.holes_fill(bm, edges = negative_edges)

    else:
        plane_co = (positive_slice + negative_slice)/2

        normal_signed = normal *-1 if plane_co[index]>0.5 else normal

        center_cut = bmesh.ops.bisect_plane(bm, geom = bm.verts[:]+ bm.edges[:] + bm.faces[:] ,
            dist = 0, plane_co = plane_co, plane_no = normal_signed,
            use_snap_center= False, clear_outer = True, clear_inner= True)
        bmesh.ops.remove_doubles(bm, verts = bm.verts, dist = 0.0001)

        if fill_neg or fill_pos:
            bmesh.ops.holes_fill(bm, edges = bm.edges)

class Obj_data():
    def __init__(self):
        self.object_name = None
        self.object_matrix = None
        self.has_data = False

        self.dimensions_base = None
        self.bounds_base = None
        self.center_base = None

        self.dimensions_final = None
        self.bounds_final = None
        self.center_final = None

        self.parent_name = None
        self.matrix_parent_inverse = None

        self.hide = None

def popup_draw(self, context):
    layout = self.layout

    data = HOPS_OT_Conv_To_Shape_1_5.operator
    if not data: return {'CANCELLED'}

    layout.label(text= 'Selector')
    vals = (item[0] for item in data.primitive_type_items)

    for val in vals:
        row = layout.row()
        row.scale_y = 2
        row.prop_enum(data, 'primitive_type', val)