OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / src / operator / add / grid.py

557 lines · python ref:
import bpy
import math
import bmesh
from .... utility import addon, version
from mathutils import Matrix, Vector, Euler
from ....utility import math as hops_math

class Grid_common():

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

    selection_cutter : bpy.props.BoolProperty(
        name = 'Boolean',
        description = 'Adde boolean to the grid',
        default = True,
    )

    boolean_operation : bpy.props.EnumProperty(
        name = 'Mode',
        description = 'Mode' ,
        items = [
            ('INTERSECT', "INTERSECT", "For use with cutters"),
            ('DIFFERENCE', "DIFFERENCE", "For use with non-cutters"),
        ],
        default = 'INTERSECT',
    )

    replace : bpy.props.BoolProperty(
        name = 'Replace',
        description = 'Replace meshes of selection. Destructive.',
        default = False,
    )

    #circle

    circle_verts: bpy.props.IntProperty(
        name = 'Vert count',
        description = 'Number of vertices defining cercle',
        default = 16,
        min = 3,
    )

    circle_divisions: bpy.props.IntProperty(
        name = 'Divisions',
        description = 'How many times the grid is divided',
        default = 12,
        min = 1,
    )

    circle_radius: bpy.props.FloatProperty(
        name = 'Radius',
        description = 'Radius of the circle. Relative',
        default = 0.44,
        min = 0,
        max = 1
    )

    circle_checker : bpy.props.BoolProperty(
        name = 'Checker',
        description = 'Checker pattern',
        default = True,
    )

    def draw(self, context):

        if self.spawn_selection:
            self.layout.prop(self, 'axis')
            self.layout.prop(self, 'selection_cutter')
            if self.selection_cutter:
                self.layout.prop(self, 'boolean_operation')
        if self.__class__.__name__.endswith('circle'):
            if self.spawn_selection:
                self.layout.prop(self, 'replace')
            self.layout.prop(self, 'circle_verts')
            self.layout.prop(self, 'circle_divisions')
            self.layout.prop(self, 'circle_radius')
            self.layout.prop(self, 'circle_checker')

    def invoke(self, context, event):
        self.spawn_selection = True if event.shift else False
        self.replace = False if not self.spawn_selection else self.replace

        return self.execute(context)

    def to_selection(self, context, source_obj):


        for obj in self.selection:

            grid = source_obj.copy()
            grid.data = source_obj.data.copy()

            context.collection.objects.link(grid)

            context.view_layer.objects.active = grid

            center = hops_math.coords_to_center([ obj.matrix_world @ Vector(v) for v in obj.bound_box ])

            trans = Matrix.Translation(center)

            XYZ = 'XYZ'

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

            quat =  obj.matrix_world.to_quaternion()
            eul.rotate(quat)

            grid.matrix_world = trans @ eul.to_quaternion().to_matrix().to_4x4()

            if self.selection_cutter:
                intersect = grid.modifiers.new(type ='BOOLEAN', name ='INTERSECT')

                intersect.object = obj
                intersect.operation = self.boolean_operation

                if hasattr(intersect, 'solver'):
                    intersect.solver = version.bsolver_fast()

        bpy.data.objects.remove(source_obj)

    def replace_mesh(self, context, source_bm):

        XYZ = 'XYZ'

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

        rotation = eul.to_quaternion().to_matrix().to_4x4()

        bmesh.ops.transform(source_bm, verts = source_bm.verts, matrix =rotation)

        for obj in self.selection:

            if not obj.data or obj.type != 'MESH': continue

            source_bm.to_mesh(obj.data)
            obj.data.update()

        source_bm.free()


class HOPS_OT_ADD_grid_square(Grid_common, bpy.types.Operator):
    bl_idname = "hops.add_grid_square"
    bl_label = "Add smart grid_square"
    bl_options = {"REGISTER", "UNDO", "GRAB_CURSOR", "BLOCKING"}
    bl_description = """Create Smart grid_square
    shift - to_selection (placement and boolean)
    """


    def execute(self, context):
        self.selection = context.selected_objects[:]
        verts = [(-1, -1, 0)]
        obj = bpy.data.objects.new("Grid", bpy.data.meshes.new("Grid"))

        bpy.ops.object.select_all(action='DESELECT')
        context.collection.objects.link(obj)
        context.view_layer.objects.active = obj
        obj.select_set(True)

        bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)

        if addon.preference().behavior.cursor_boolshapes:
            obj.rotation_euler = bpy.context.scene.cursor.rotation_euler

        bm = bmesh.new()
        for v in verts:
            bm.verts.new(v)
        bm.to_mesh(context.object.data)
        bm.free()

        self.add_screw_modifier(obj, 'X', 'HOPS_screw_x')
        self.add_screw_modifier(obj, 'Y', 'HOPS_screw_y', True)
        # self.add_decimate_modifier(obj, 'HOPS_decimate')
        self.add_wireframe_modifier(obj)
        if addon.preference().behavior.auto_smooth:
            bpy.context.object.data.use_auto_smooth = True
            bpy.context.object.data.auto_smooth_angle = math.radians(30)

        if self.spawn_selection:

            self.to_selection( context, obj)

        return {"FINISHED"}


    def add_wireframe_modifier(self, object):
        modifier = object.modifiers.new(name="HOPS_wireframe_c", type="WIREFRAME")
        modifier.thickness = 0.02
        modifier.use_even_offset = True
        modifier.use_relative_offset = False
        modifier.use_replace = True
        modifier.use_boundary = True


    def add_screw_modifier(self, object, axis='X', name='HOPS_screw_x', flip=False):
        screw_modifier = object.modifiers.new(name=name, type="SCREW")
        screw_modifier.angle = math.radians(0)
        screw_modifier.axis = axis
        screw_modifier.steps = 10
        screw_modifier.render_steps = 10
        screw_modifier.screw_offset = 2
        screw_modifier.iterations = 1
        screw_modifier.use_smooth_shade = True
        screw_modifier.use_merge_vertices = True
        screw_modifier.use_normal_flip = flip


    def add_displace_modifier(self, object, axis='X', name='HOPS_displace_x'):
        displace_modifier = object.modifiers.new(name=name, type="DISPLACE")
        displace_modifier.direction = axis
        displace_modifier.strength = -1.5


    def add_decimate_modifier(self, object, name='HOPS_decimate_c'):
        modifier = object.modifiers.new('Decimate', 'DECIMATE')
        modifier.angle_limit = math.radians(5)
        modifier.decimate_type = 'DISSOLVE'


class HOPS_OT_ADD_grid_diamond(Grid_common, bpy.types.Operator):
    bl_idname = "hops.add_grid_diamond"
    bl_label = "Add smart grid_diamond"
    bl_options = {"REGISTER", "UNDO", "GRAB_CURSOR", "BLOCKING"}
    bl_description = """Create Smart grid_diamond
    shift - to_selection (placement and boolean)
    """

    def execute(self, context):
        self.selection = context.selected_objects[:]

        verts = [(-1, -1, 0)]
        obj = bpy.data.objects.new("Grid", bpy.data.meshes.new("Grid"))

        bpy.ops.object.select_all(action='DESELECT')
        context.collection.objects.link(obj)
        context.view_layer.objects.active = obj
        obj.select_set(True)

        bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
        if addon.preference().behavior.cursor_boolshapes:
            obj.rotation_euler = bpy.context.scene.cursor.rotation_euler

        bm = bmesh.new()
        for v in verts:
            bm.verts.new(v)
        bm.to_mesh(context.object.data)
        bm.free()

        self.add_screw_modifier(obj, 'X', 'HOPS_screw_x')
        self.add_screw_modifier(obj, 'Y', 'HOPS_screw_y', True)
        self.add_decimate_modifier(obj, 'DISSOLVE', 'HOPS_decimate')
        self.add_subsurf_modifier(obj, 'HOPS_subsurf')
        self.add_decimate_modifier(obj, 'UNSUBDIV', 'HOPS_unsubdiv')
        self.add_wireframe_modifier(obj)
        if addon.preference().behavior.auto_smooth:
            bpy.context.object.data.use_auto_smooth = True
            bpy.context.object.data.auto_smooth_angle = math.radians(30)

        if self.spawn_selection:

            self.to_selection(context, obj)

        return {"FINISHED"}


    def add_subsurf_modifier(self, object, name='HOPS_deform_z'):
        subsurf_mod = object.modifiers.new(name=name, type="SUBSURF")
        subsurf_mod.subdivision_type = 'SIMPLE'
        subsurf_mod.levels = 4


    def add_wireframe_modifier(self, object):
        modifier = object.modifiers.new(name="HOPS_wireframe_c", type="WIREFRAME")
        modifier.thickness = 0.02
        modifier.use_even_offset = True
        modifier.use_relative_offset = False
        modifier.use_replace = True
        modifier.use_boundary = True


    def add_screw_modifier(self, object, axis='X', name='HOPS_screw_x', flip=False):
        screw_modifier = object.modifiers.new(name=name, type="SCREW")
        screw_modifier.angle = math.radians(0)
        screw_modifier.axis = axis
        screw_modifier.steps = 3
        screw_modifier.render_steps = 3
        screw_modifier.screw_offset = 2
        screw_modifier.iterations = 1
        screw_modifier.use_smooth_shade = True
        screw_modifier.use_merge_vertices = True
        screw_modifier.use_normal_flip = flip


    def add_displace_modifier(self, object, axis='X', name='HOPS_displace_x'):
        displace_modifier = object.modifiers.new(name=name, type="DISPLACE")
        displace_modifier.direction = axis
        displace_modifier.strength = -1.5


    def add_decimate_modifier(self, object, types, name='HOPS_decimate_c'):
        modifier = object.modifiers.new('Decimate', 'DECIMATE')
        modifier.angle_limit = math.radians(5)
        modifier.decimate_type = types
        modifier.iterations = 1


class HOPS_OT_ADD_grid_honey(Grid_common, bpy.types.Operator):
    bl_idname = "hops.add_grid_honey"
    bl_label = "Add smart grid_honey"
    bl_options = {"REGISTER", "UNDO", "GRAB_CURSOR", "BLOCKING"}
    bl_description = """Create Smart grid_honey
    shift - to_selection (placement and boolean)
    """

    def execute(self, context):
        self.selection = context.selected_objects[:]

        verts = [(0, 0, 0)]
        obj = bpy.data.objects.new("Grid", bpy.data.meshes.new("Grid"))

        bpy.ops.object.select_all(action='DESELECT')
        context.collection.objects.link(obj)
        context.view_layer.objects.active = obj
        obj.select_set(True)

        bpy.ops.view3d.snap_selected_to_cursor(use_offset=False)
        if addon.preference().behavior.cursor_boolshapes:
            obj.rotation_euler = bpy.context.scene.cursor.rotation_euler

        bm = bmesh.new()
        for v in verts:
            bm.verts.new(v)
        bm.to_mesh(context.object.data)
        bm.free()

        self.add_displace_modifier(obj, 'X', 'HOPS_displace_x')
        self.add_screw_modifier(obj, 0, 'X', 'HOPS_screw_x', 0.02, 2)
        self.add_screw_modifier(obj, 360, 'Z', 'HOPS_screw_y', 0, 6, True)
        self.add_decimate_modifier(obj, 'DISSOLVE', 'HOPS_decimate')
        self.add_solidify_modifier(obj, 'HOPS_solidify_z')
        self.add_array_modifier(obj, 0, 1, 2, 'HOPS_array_1')
        self.add_array_modifier(obj, 0.75, 0.25, 2, 'HOPS_array_2')
        self.add_array_modifier(obj, 0.85, 0, 4, 'HOPS_array_3')
        self.add_array_modifier(obj, 0, 0.8, 3, 'HOPS_array_4')

        if addon.preference().behavior.auto_smooth:
            bpy.context.object.data.use_auto_smooth = True
            bpy.context.object.data.auto_smooth_angle = math.radians(30)


        if self.spawn_selection:

            self.to_selection( context, obj)

        return {"FINISHED"}


    def add_array_modifier(self, object, x, y, c, name='HOPS_array_z'):
        modifier = object.modifiers.new(name, "ARRAY")
        modifier.relative_offset_displace[0] = x
        modifier.relative_offset_displace[1] = y
        modifier.relative_offset_displace[2] = 0
        modifier.count = c
        modifier.merge_threshold = 0.01


    def add_subsurf_modifier(self, object, name='HOPS_deform_z'):
        subsurf_mod = object.modifiers.new(name=name, type="SUBSURF")
        subsurf_mod.subdivision_type = 'SIMPLE'
        subsurf_mod.levels = 2


    def add_wireframe_modifier(self, object):
        modifier = object.modifiers.new(name="HOPS_wireframe_c", type="WIREFRAME")
        modifier.thickness = 0.02
        modifier.use_even_offset = True
        modifier.use_relative_offset = False
        modifier.use_replace = True
        modifier.use_boundary = True


    def add_screw_modifier(self, object, angle=0, axis='X', name='HOPS_screw_x', offset=0.02, step=6, flip=False):
        screw_modifier = object.modifiers.new(name=name, type="SCREW")
        screw_modifier.angle = math.radians(angle)
        screw_modifier.axis = axis
        screw_modifier.steps = step
        screw_modifier.render_steps = step
        screw_modifier.screw_offset = offset
        screw_modifier.iterations = 1
        screw_modifier.use_smooth_shade = True
        screw_modifier.use_merge_vertices = True
        screw_modifier.use_normal_flip = flip


    def add_displace_modifier(self, object, axis='X', name='HOPS_displace_x'):
        displace_modifier = object.modifiers.new(name=name, type="DISPLACE")
        displace_modifier.direction = axis
        displace_modifier.strength = 0.20


    def add_decimate_modifier(self, object, types, name='HOPS_decimate_c'):
        modifier = object.modifiers.new('Decimate', 'DECIMATE')
        modifier.angle_limit = math.radians(5)
        modifier.decimate_type = types
        modifier.iterations = 1


    def add_solidify_modifier(self, object, name='HOPS_solidify_z'):
        solidify_modifier = object.modifiers.new(name, "SOLIDIFY")
        solidify_modifier.thickness = 0.02
        solidify_modifier.offset = 1
        solidify_modifier.use_even_offset = True
        solidify_modifier.use_quality_normals = True
        solidify_modifier.use_rim_only = False
        solidify_modifier.show_on_cage = True

class HOPS_OT_ADD_grid_circle(Grid_common, bpy.types.Operator):
    bl_idname = "hops.add_grid_circle"
    bl_label = "Add smart grid_circle"
    bl_options = {"REGISTER", "UNDO", "GRAB_CURSOR", "BLOCKING"}
    bl_description = """Create Smart circle grid
    shift - to_selection (placement and boolean)
    F9 for options
    """

    def execute(self, context):

        self.selection = context.selected_objects[:]
        circle_verts = self.circle_verts

        grid_divs = self.circle_divisions

        radius = self.circle_radius/2

        checker = self.circle_checker and grid_divs>1

        bm = bmesh.new()

        grid_size = 0.5
        grid = bmesh.ops.create_grid(bm, size=grid_size)

        bmesh.ops.subdivide_edges(bm, edges = bm.edges, cuts=1 , use_grid_fill = False)

        square_edges = set(bm.edges)


        y_pos = Vector((0, grid_size, 0))
        x_pos = Vector((grid_size, 0, 0))

        y_pos_vert = [v for v in bm.verts if v.co == y_pos][0]
        y_neg_vert = [v for v in bm.verts if v.co == -y_pos][0]

        x_pos_vert = [v for v in bm.verts if v.co == x_pos][0]
        x_neg_vert = [v for v in bm.verts if v.co == -x_pos][0]

        circle = bmesh.ops.create_circle(bm, cap_ends = 0, segments = circle_verts, radius = radius)

        verts_y = sorted(circle['verts'], key=lambda e:e.co.y)
        verts_x = sorted(circle['verts'], key=lambda e:e.co.x)

        bm.edges.new((y_pos_vert, verts_y[-1]))
        bm.edges.new((y_neg_vert, verts_y[0]))
        bm.edges.new((x_pos_vert, verts_x[-1]))
        bm.edges.new((x_neg_vert, verts_x[0]))

        edge_net = [e for e in bm.edges if e not in square_edges]

        bmesh.utils.face_split_edgenet(bm.faces[:][0], edge_net)

        _set = set((y_pos_vert, y_neg_vert, x_pos_vert, x_neg_vert))

        bmesh.ops.delete(bm, geom =[f for f in bm.faces if all([v not in set(f.verts) for v in _set])] , context = 'FACES')

        spin_offset = 1/grid_divs
        spin_steps = grid_divs-1


        if checker:

            grid = bmesh.ops.create_grid(bm, size=grid_size, matrix=Matrix.Translation((1,0,0)))
            edges = set()
            for v in grid['verts']:
                edges.update(v.link_edges)

            bmesh.ops.subdivide_edges(bm, edges=list(edges), cuts=1 , use_grid_fill=True)

            bmesh.ops.spin(bm, geom = bm.faces, cent = [0,0,0], axis = [0,0,1], dvec = [-1,-1,0], angle = math.pi, steps = 1, use_merge =0, use_normal_flip =0, use_duplicate = 1)
            spin_offset *=2
            spin_steps =int(spin_steps/2)

        bmesh.ops.scale(bm, verts = bm.verts , vec = Vector((1,1,0))/grid_divs )
        bmesh.ops.translate(bm, verts = bm.verts , vec = Vector((-.5,-.5,0))+ Vector((.5,.5,0))/grid_divs)

        bmesh.ops.spin(bm, geom = bm.faces, cent = [0,0,0], axis = [1,0,0], dvec = [spin_offset,0,0], angle =0, steps = spin_steps, use_merge =0, use_normal_flip =0, use_duplicate = 1)
        bmesh.ops.spin(bm, geom = bm.faces, cent = [0,0,0], axis = [0,spin_offset,0], dvec = [0,spin_offset,0], angle =0, steps = spin_steps, use_merge =0, use_normal_flip =0, use_duplicate = 1)

        if checker:

            def comparer(face):
                center = face.calc_center_median()
                return center.x > 0.5 or center.y > 0.5

            faces = [f for f in bm.faces if comparer(f)]
            bmesh.ops.delete(bm, geom = faces, context ='FACES')

        bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.000001)

        bmesh.ops.scale(bm, verts = bm.verts , vec = [2,2,0] )

        if not self.replace:
            bpy.ops.object.select_all(action='DESELECT')

            grid_mesh = bpy.data.meshes.new('circle Grid')
            grid = bpy.data.objects.new('Grid', grid_mesh )

            context.collection.objects.link(grid)
            grid.select_set(True)
            context.view_layer.objects.active = grid

            bm.to_mesh(grid_mesh)
            grid_mesh.update()

            bm.free()

        if self.spawn_selection:

            if self.replace:

                self.replace_mesh( context, bm)

            else:

                self.to_selection( context, grid)



        return {'FINISHED'}