Skip to content
Snippets Groups Projects
carver_utils.py 30.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
    
    import bpy
    import gpu
    from gpu_extras.batch import batch_for_shader
    import math
    import sys
    import random
    import bmesh
    from mathutils import (
    
        Euler,
        Matrix,
        Vector,
        Quaternion,
    
    )
    from mathutils.geometry import (
    
        intersect_line_plane,
    
        sin,
        cos,
        pi,
        )
    
    
    import bpy_extras
    
    from bpy_extras import view3d_utils
    from bpy_extras.view3d_utils import (
    
        region_2d_to_vector_3d,
        region_2d_to_location_3d,
        location_3d_to_region_2d,
    
    )
    
    # Cut Square
    def CreateCutSquare(self, context):
    
        """ Create a rectangle mesh """
        far_limit = 10000.0
        faces=[]
    
        # Get the mouse coordinates
        coord = self.mouse_path[0][0], self.mouse_path[0][1]
    
        # New mesh
        me = bpy.data.meshes.new('CMT_Square')
        bm = bmesh.new()
        bm.from_mesh(me)
    
        # New object and link it to the scene
        ob = bpy.data.objects.new('CMT_Square', me)
        self.CurrentObj = ob
        context.collection.objects.link(ob)
    
        # Scene information
        region = context.region
        rv3d = context.region_data
        depth_location = region_2d_to_vector_3d(region, rv3d, coord)
        self.ViewVector = depth_location
    
        # Get a point on a infinite plane and its direction
        plane_normal = depth_location
        plane_direction = plane_normal.normalized()
    
        if self.snapCursor:
            plane_point = context.scene.cursor.location
        else:
            plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
    
        # Find the intersection of a line going thru each vertex and the infinite plane
        for v_co in self.rectangle_coord:
            vec = region_2d_to_vector_3d(region, rv3d, v_co)
            p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
            p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
            faces.append(bm.verts.new(intersect_line_plane(p0, p1, plane_point, plane_direction)))
    
        # Update vertices index
        bm.verts.index_update()
        # New faces
        t_face = bm.faces.new(faces)
        # Set mesh
        bm.to_mesh(me)
    
    
    
    # Cut Line
    def CreateCutLine(self, context):
    
        """ Create a polygon mesh """
        far_limit = 10000.0
        vertices = []
        faces = []
        loc = []
    
        # Get the mouse coordinates
        coord = self.mouse_path[0][0], self.mouse_path[0][1]
    
        # New mesh
        me = bpy.data.meshes.new('CMT_Line')
        bm = bmesh.new()
        bm.from_mesh(me)
    
        # New object and link it to the scene
        ob = bpy.data.objects.new('CMT_Line', me)
        self.CurrentObj = ob
        context.collection.objects.link(ob)
    
        # Scene information
        region = context.region
        rv3d = context.region_data
        depth_location = region_2d_to_vector_3d(region, rv3d, coord)
        self.ViewVector = depth_location
    
        # Get a point on a infinite plane and its direction
        plane_normal = depth_location
        plane_direction = plane_normal.normalized()
    
        if self.snapCursor:
            plane_point = context.scene.cursor.location
        else:
            plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
    
        # Use dict to remove doubles
        # Find the intersection of a line going thru each vertex and the infinite plane
        for idx, v_co in enumerate(list(dict.fromkeys(self.mouse_path))):
            vec = region_2d_to_vector_3d(region, rv3d, v_co)
            p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
            p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
            loc.append(intersect_line_plane(p0, p1, plane_point, plane_direction))
            vertices.append(bm.verts.new(loc[idx]))
    
            if idx > 0:
                bm.edges.new([vertices[idx-1],vertices[idx]])
    
            faces.append(vertices[idx])
    
        # Update vertices index
        bm.verts.index_update()
    
        # Nothing is selected, create close geometry
        if self.CreateMode:
            if self.Closed and len(vertices) > 1:
                bm.edges.new([vertices[-1], vertices[0]])
                bm.faces.new(faces)
        else:
            # Create faces if more than 2 vertices
            if len(vertices) > 1 :
                bm.edges.new([vertices[-1], vertices[0]])
                bm.faces.new(faces)
    
        bm.to_mesh(me)
    
    
    # Cut Circle
    def CreateCutCircle(self, context):
    
        """ Create a circle mesh """
        far_limit = 10000.0
        FacesList = []
    
        # Get the mouse coordinates
        mouse_pos_x = self.mouse_path[0][0]
        mouse_pos_y = self.mouse_path[0][1]
        coord = self.mouse_path[0][0], self.mouse_path[0][1]
    
        # Scene information
        region = context.region
        rv3d = context.region_data
        depth_location = region_2d_to_vector_3d(region, rv3d, coord)
        self.ViewVector = depth_location
    
        # Get a point on a infinite plane and its direction
        plane_point = context.scene.cursor.location if self.snapCursor else Vector((0.0, 0.0, 0.0))
        plane_normal = depth_location
        plane_direction = plane_normal.normalized()
    
        # New mesh
        me = bpy.data.meshes.new('CMT_Circle')
        bm = bmesh.new()
        bm.from_mesh(me)
    
        # New object and link it to the scene
        ob = bpy.data.objects.new('CMT_Circle', me)
        self.CurrentObj = ob
        context.collection.objects.link(ob)
    
        # Create a circle using a tri fan
        tris_fan, indices = draw_circle(self, mouse_pos_x, mouse_pos_y)
    
        # Remove the vertex in the center to get the outer line of the circle
        verts = tris_fan[1:]
    
        # Find the intersection of a line going thru each vertex and the infinite plane
        for vert in verts:
            vec = region_2d_to_vector_3d(region, rv3d, vert)
            p0 = region_2d_to_location_3d(region, rv3d, vert, vec)
            p1 = p0 + plane_direction * far_limit
            loc0 = intersect_line_plane(p0, p1, plane_point, plane_direction)
            t_v0 = bm.verts.new(loc0)
            FacesList.append(t_v0)
    
        bm.verts.index_update()
        bm.faces.new(FacesList)
        bm.to_mesh(me)
    
    
    
    def create_2d_circle(self, step, radius, rotation = 0):
    
        """ Create the vertices of a 2d circle at (0,0) """
        verts = []
        for angle in range(0, 360, step):
            verts.append(math.cos(math.radians(angle + rotation)) * radius)
            verts.append(math.sin(math.radians(angle + rotation)) * radius)
            verts.append(0.0)
        verts.append(math.cos(math.radians(0.0 + rotation)) * radius)
        verts.append(math.sin(math.radians(0.0 + rotation)) * radius)
        verts.append(0.0)
        return(verts)
    
    
    
    def draw_circle(self, mouse_pos_x, mouse_pos_y):
    
        """ Return the coordinates + indices of a circle using a triangle fan """
        tris_verts = []
        indices = []
        segments = int(360 / self.stepAngle[self.step])
        radius = self.mouse_path[1][0] - self.mouse_path[0][0]
        rotation = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 2
    
        # Get the vertices of a 2d circle
        verts = create_2d_circle(self, self.stepAngle[self.step], radius, rotation)
    
        # Create the first vertex at mouse position for the center of the circle
        tris_verts.append(Vector((mouse_pos_x + self.xpos , mouse_pos_y + self.ypos)))
    
        # For each vertex of the circle, add the mouse position and the translation
        for idx in range(int(len(verts) / 3) - 1):
            tris_verts.append(Vector((verts[idx * 3] + mouse_pos_x + self.xpos, \
                                      verts[idx * 3 + 1] + mouse_pos_y + self.ypos)))
            i1 = idx+1
            i2 = idx+2 if idx+2 <= segments else 1
            indices.append((0,i1,i2))
    
        return(tris_verts, indices)
    
    
    # Object dimensions (SCULPT Tools tips)
    def objDiagonal(obj):
    
        return ((obj.dimensions[0]**2) + (obj.dimensions[1]**2) + (obj.dimensions[2]**2))**0.5
    
    
    
    # Bevel Update
    def update_bevel(context):
    
        selection = context.selected_objects.copy()
        active = context.active_object
    
        if len(selection) > 0:
            for obj in selection:
                bpy.ops.object.select_all(action='DESELECT')
                obj.select_set(True)
                context.view_layer.objects.active = obj
    
                # Test object name
                # Subdive mode : Only bevel weight
                if obj.data.name.startswith("S_") or obj.data.name.startswith("S "):
                    bpy.ops.object.mode_set(mode='EDIT')
                    bpy.ops.mesh.region_to_loop()
                    bpy.ops.transform.edge_bevelweight(value=1)
                    bpy.ops.object.mode_set(mode='OBJECT')
    
                else:
                    # No subdiv mode : bevel weight + Crease + Sharp
                    CreateBevel(context, obj)
    
        bpy.ops.object.select_all(action='DESELECT')
    
        for obj in selection:
            obj.select_set(True)
        context.view_layer.objects.active = active
    
    
    # Create bevel
    def CreateBevel(context, CurrentObject):
    
        # Save active object
        SavActive = context.active_object
    
        # Test if initial object has bevel
        bevel_modifier = False
        for modifier in SavActive.modifiers:
            if modifier.name == 'Bevel':
                bevel_modifier = True
    
        if bevel_modifier:
            # Active "CurrentObject"
            context.view_layer.objects.active = CurrentObject
    
            bpy.ops.object.mode_set(mode='EDIT')
    
            # Edge mode
            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
            # Clear all
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.mark_sharp(clear=True)
            bpy.ops.transform.edge_crease(value=-1)
            bpy.ops.transform.edge_bevelweight(value=-1)
    
            bpy.ops.mesh.select_all(action='DESELECT')
    
            # Select (in radians) all 30° sharp edges
            bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
            # Apply bevel weight + Crease + Sharp to the selected edges
            bpy.ops.mesh.mark_sharp()
            bpy.ops.transform.edge_crease(value=1)
            bpy.ops.transform.edge_bevelweight(value=1)
    
            bpy.ops.mesh.select_all(action='DESELECT')
    
            bpy.ops.object.mode_set(mode='OBJECT')
    
            CurrentObject.data.use_customdata_edge_bevel = True
    
            for i in range(len(CurrentObject.data.edges)):
                if CurrentObject.data.edges[i].select is True:
                    CurrentObject.data.edges[i].bevel_weight = 1.0
                    CurrentObject.data.edges[i].use_edge_sharp = True
    
            bevel_modifier = False
            for m in CurrentObject.modifiers:
                if m.name == 'Bevel':
                    bevel_modifier = True
    
            if bevel_modifier is False:
                bpy.ops.object.modifier_add(type='BEVEL')
                mod = context.object.modifiers[-1]
                mod.limit_method = 'WEIGHT'
                mod.width = 0.01
                mod.profile = 0.699099
    
                mod.use_clamp_overlap = False
    
                mod.segments = 3
                mod.loop_slide = False
    
            bpy.ops.object.shade_smooth()
    
            context.object.data.use_auto_smooth = True
            context.object.data.auto_smooth_angle = 1.0471975
    
            # Restore the active object
            context.view_layer.objects.active = SavActive
    
    
    
    def MoveCursor(qRot, location, self):
    
        """ In brush mode : Draw a circle around the brush """
        if qRot is not None:
            verts = create_2d_circle(self, 10, 1)
            self.CLR_C.clear()
            vc = Vector()
            for idx in range(int(len(verts) / 3)):
                vc.x = verts[idx * 3]
                vc.y = verts[idx * 3 + 1]
                vc.z = verts[idx * 3 + 2]
                vc = qRot @ vc
                self.CLR_C.append(vc.x)
                self.CLR_C.append(vc.y)
                self.CLR_C.append(vc.z)
    
    
    
    def rot_axis_quat(vector1, vector2):
    
        """ Find the rotation (quaternion) from vector 1 to vector 2"""
        vector1 = vector1.normalized()
        vector2 = vector2.normalized()
        cosTheta = vector1.dot(vector2)
        rotationAxis = Vector((0.0, 0.0, 0.0))
        if (cosTheta < -1 + 0.001):
            v = Vector((0.0, 1.0, 0.0))
            #Get the vector at the right angles to both
            rotationAxis = vector1.cross(v)
            rotationAxis = rotationAxis.normalized()
            q = Quaternion()
            q.w = 0.0
            q.x = rotationAxis.x
            q.y = rotationAxis.y
            q.z = rotationAxis.z
        else:
            rotationAxis = vector1.cross(vector2)
            s = math.sqrt((1.0 + cosTheta) * 2.0)
            invs = 1 / s
            q = Quaternion()
            q.w = s * 0.5
            q.x = rotationAxis.x * invs
            q.y = rotationAxis.y * invs
            q.z = rotationAxis.z * invs
        return q
    
    
    
    # Picking (template)
    def Picking(context, event):
    
        """ Put the 3d cursor on the closest object"""
    
        # get the context arguments
        scene = context.scene
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y
    
        # get the ray from the viewport and mouse
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
        ray_target = ray_origin + view_vector
    
        def visible_objects_and_duplis():
            depsgraph = context.evaluated_depsgraph_get()
            for dup in depsgraph.object_instances:
                if dup.is_instance:  # Real dupli instance
                    obj = dup.instance_object.original
                    yield (obj, dup.matrix.copy())
                else:  # Usual object
                    obj = dup.object.original
                    yield (obj, obj.matrix_world.copy())
    
        def obj_ray_cast(obj, matrix):
            # get the ray relative to the object
            matrix_inv = matrix.inverted()
            ray_origin_obj = matrix_inv @ ray_origin
            ray_target_obj = matrix_inv @ ray_target
            ray_direction_obj = ray_target_obj - ray_origin_obj
            # cast the ray
            success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
            if success:
                return location, normal, face_index
            return None, None, None
    
        # cast rays and find the closest object
        best_length_squared = -1.0
        best_obj = None
    
        # cast rays and find the closest object
        for obj, matrix in visible_objects_and_duplis():
            if obj.type == 'MESH':
                hit, normal, face_index = obj_ray_cast(obj, matrix)
                if hit is not None:
                    hit_world = matrix @ hit
                    length_squared = (hit_world - ray_origin).length_squared
                    if best_obj is None or length_squared < best_length_squared:
                        scene.cursor.location = hit_world
                        best_length_squared = length_squared
                        best_obj = obj
                else:
                    if best_obj is None:
                        depth_location = region_2d_to_vector_3d(region, rv3d, coord)
                        loc = region_2d_to_location_3d(region, rv3d, coord, depth_location)
                        scene.cursor.location = loc
    
    
    
    def Pick(context, event, self, ray_max=10000.0):
    
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y
        view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
        ray_target = ray_origin + (view_vector * ray_max)
    
        def obj_ray_cast(obj, matrix):
            matrix_inv = matrix.inverted()
            ray_origin_obj = matrix_inv @ ray_origin
            ray_target_obj = matrix_inv @ ray_target
            success, hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
            if success:
                return hit, normal, face_index
            return None, None, None
    
        best_length_squared = ray_max * ray_max
        best_obj = None
        for obj in self.CList:
            matrix = obj.matrix_world
            hit, normal, face_index = obj_ray_cast(obj, matrix)
            rotation = obj.rotation_euler.to_quaternion()
            if hit is not None:
                hit_world = matrix @ hit
                length_squared = (hit_world - ray_origin).length_squared
                if length_squared < best_length_squared:
                    best_length_squared = length_squared
                    best_obj = obj
                    hits = hit_world
                    ns = normal
                    fs = face_index
    
        if best_obj is not None:
            return hits, ns, rotation
    
        return None, None, None
    
    
    def SelectObject(self, copyobj):
    
        copyobj.select_set(True)
    
        for child in copyobj.children:
            SelectObject(self, child)
    
        if copyobj.parent is None:
            bpy.context.view_layer.objects.active = copyobj
    
    
    # Undo
    def printUndo(self):
    
        for l in self.UList:
            print(l)
    
    
    
    def UndoAdd(self, type, obj):
    
        """ Create a backup mesh before apply the action to the object """
        if obj is None:
            return
    
        if type != "DUPLICATE":
            bm = bmesh.new()
            bm.from_mesh(obj.data)
            self.UndoOps.append((obj, type, bm))
        else:
            self.UndoOps.append((obj, type, None))
    
    
    
    def UndoListUpdate(self):
    
        self.UList.append((self.UndoOps.copy()))
        self.UList_Index += 1
        self.UndoOps.clear()
    
        if self.UList_Index < 0:
            return
        # get previous mesh
        for o in self.UList[self.UList_Index]:
            if o[1] == "MESH":
                bm = o[2]
                bm.to_mesh(o[0].data)
    
        SelectObjList = bpy.context.selected_objects.copy()
        Active_Obj = bpy.context.active_object
        bpy.ops.object.select_all(action='TOGGLE')
    
        for o in self.UList[self.UList_Index]:
            if o[1] == "REBOOL":
                o[0].select_set(True)
                o[0].hide_viewport = False
    
            if o[1] == "DUPLICATE":
                o[0].select_set(True)
                o[0].hide_viewport = False
    
        bpy.ops.object.delete(use_global=False)
    
        for so in SelectObjList:
            bpy.data.objects[so.name].select_set(True)
        bpy.context.view_layer.objects.active = Active_Obj
    
        self.UList_Index -= 1
        self.UList[self.UList_Index + 1:] = []
    
    
    
    def duplicateObject(self):
    
        if self.Instantiate:
            bpy.ops.object.duplicate_move_linked(
                OBJECT_OT_duplicate={
                    "linked": True,
                    "mode": 'TRANSLATION',
                },
                TRANSFORM_OT_translate={
                    "value": (0, 0, 0),
                },
            )
        else:
            bpy.ops.object.duplicate_move(
                OBJECT_OT_duplicate={
                    "linked": False,
                    "mode": 'TRANSLATION',
                },
                TRANSFORM_OT_translate={
                    "value": (0, 0, 0),
                },
            )
    
        ob_new = bpy.context.active_object
    
        ob_new.location = self.CurLoc
        v = Vector()
        v.x = v.y = 0.0
        v.z = self.BrushDepthOffset
        ob_new.location += self.qRot * v
    
        if self.ObjectMode:
            ob_new.scale = self.ObjectBrush.scale
        if self.ProfileMode:
            ob_new.scale = self.ProfileBrush.scale
    
        e = Euler()
        e.x = e.y = 0.0
        e.z = self.aRotZ / 25.0
    
        # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
        if (self.alt is True) and ((self.nbcol + self.nbrow) < 3):
            if self.RandomRotation:
                e.z += random.random()
    
        qe = e.to_quaternion()
        qRot = self.qRot * qe
        ob_new.rotation_mode = 'QUATERNION'
        ob_new.rotation_quaternion = qRot
        ob_new.rotation_mode = 'XYZ'
    
        if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False):
            ob_new.hide_viewport = True
    
        if self.BrushSolidify:
            ob_new.display_type = "SOLID"
            ob_new.show_in_front = False
    
        for o in bpy.context.selected_objects:
            UndoAdd(self, "DUPLICATE", o)
    
        if len(bpy.context.selected_objects) > 0:
            bpy.ops.object.select_all(action='TOGGLE')
        for o in self.all_sel_obj_list:
            o.select_set(True)
    
        bpy.context.view_layer.objects.active = self.OpsObj
    
    
    
    def update_grid(self, context):
    
        """
        Thanks to batFINGER for his help :
        source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
        """
        verts = []
        edges = []
        faces = []
        numface = 0
    
        if self.nbcol < 1:
            self.nbcol = 1
        if self.nbrow < 1:
            self.nbrow = 1
        if self.gapx < 0:
            self.gapx = 0
        if self.gapy < 0:
            self.gapy = 0
    
        # Get the data from the profils or the object
        if self.ProfileMode:
            brush = bpy.data.objects.new(
                        self.Profils[self.nProfil][0],
                        bpy.data.meshes[self.Profils[self.nProfil][0]]
                        )
            obj = bpy.data.objects["CT_Profil"]
            obfaces = brush.data.polygons
            obverts = brush.data.vertices
            lenverts = len(obverts)
        else:
            brush = bpy.data.objects["CarverBrushCopy"]
            obj = context.selected_objects[0]
            obverts = brush.data.vertices
            obfaces = brush.data.polygons
            lenverts = len(brush.data.vertices)
    
        # Gap between each row / column
        gapx = self.gapx
        gapy = self.gapy
    
        # Width of each row / column
        widthx = brush.dimensions.x * self.scale_x
        widthy = brush.dimensions.y * self.scale_y
    
        # Compute the corners so the new object will be always at the center
        left = -((self.nbcol - 1) * (widthx + gapx)) / 2
        start = -((self.nbrow - 1) * (widthy + gapy)) / 2
    
        for i in range(self.nbrow * self.nbcol):
            row = i % self.nbrow
            col = i // self.nbrow
            startx = left + ((widthx + gapx) * col)
            starty = start + ((widthy + gapy) * row)
    
            # Add random rotation
            if (self.RandomRotation) and not (self.GridScaleX or self.GridScaleY):
                rotmat = Matrix.Rotation(math.radians(360 * random.random()), 4, 'Z')
                for v in obverts:
                    v.co = v.co @ rotmat
    
            verts.extend([((v.co.x - startx, v.co.y - starty, v.co.z)) for v in obverts])
            faces.extend([[v + numface * lenverts for v in p.vertices] for p in obfaces])
            numface += 1
    
        # Update the mesh
        # Create mesh data
        mymesh = bpy.data.meshes.new("CT_Profil")
        # Generate mesh data
        mymesh.from_pydata(verts, edges, faces)
        # Calculate the edges
        mymesh.update(calc_edges=True)
        # Update data
        obj.data = mymesh
        # Make the object active to remove doubles
        context.view_layer.objects.active = obj
    
    
    
    def boolean_operation(bool_type="DIFFERENCE"):
    
        ActiveObj = bpy.context.active_object
        sel_index = 0 if bpy.context.selected_objects[0] != bpy.context.active_object else 1
    
        # bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
    
        bool_name = "CT_" + bpy.context.selected_objects[sel_index].name
        BoolMod = ActiveObj.modifiers.new(bool_name, "BOOLEAN")
        BoolMod.object = bpy.context.selected_objects[sel_index]
        BoolMod.operation = bool_type
        bpy.context.selected_objects[sel_index].display_type = 'WIRE'
        while ActiveObj.modifiers.find(bool_name) > 0:
            bpy.ops.object.modifier_move_up(modifier=bool_name)
    
        target_obj = context.active_object
    
        Brush = context.selected_objects[1]
        Brush.display_type = "WIRE"
    
        #Deselect all
        bpy.ops.object.select_all(action='TOGGLE')
    
        target_obj.display_type = "SOLID"
        target_obj.select_set(True)
        bpy.ops.object.duplicate()
    
        rebool_obj = context.active_object
    
        m = rebool_obj.modifiers.new("CT_INTERSECT", "BOOLEAN")
        m.operation = "INTERSECT"
        m.object = Brush
    
        m = target_obj.modifiers.new("CT_DIFFERENCE", "BOOLEAN")
        m.operation = "DIFFERENCE"
        m.object = Brush
    
        for mb in target_obj.modifiers:
            if mb.type == 'BEVEL':
                mb.show_viewport = False
    
        if self.ObjectBrush or self.ProfileBrush:
            rebool_obj.show_in_front = False
            try:
    
                bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
    
            except:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                self.report({'ERROR'}, str(exc_value))
    
        if self.dont_apply_boolean is False:
            try:
    
                bpy.ops.object.modifier_apply(modifier="CT_INTERSECT")
    
            except:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                self.report({'ERROR'}, str(exc_value))
    
        bpy.ops.object.select_all(action='TOGGLE')
    
        for mb in target_obj.modifiers:
            if mb.type == 'BEVEL':
                mb.show_viewport = True
    
        context.view_layer.objects.active = target_obj
        target_obj.select_set(True)
        if self.dont_apply_boolean is False:
            try:
    
                bpy.ops.object.modifier_apply(modifier="CT_DIFFERENCE")
    
            except:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                self.report({'ERROR'}, str(exc_value))
    
        bpy.ops.object.select_all(action='TOGGLE')
    
        rebool_obj.select_set(True)
    
    
    def createMeshFromData(self):
    
        if self.Profils[self.nProfil][0] not in bpy.data.meshes:
            # Create mesh and object
            me = bpy.data.meshes.new(self.Profils[self.nProfil][0])
            # Create mesh from given verts, faces.
            me.from_pydata(self.Profils[self.nProfil][2], [], self.Profils[self.nProfil][3])
            me.validate(verbose=True, clean_customdata=True)
            # Update mesh with new data
            me.update()
    
        if "CT_Profil" not in bpy.data.objects:
            ob = bpy.data.objects.new("CT_Profil", bpy.data.meshes[self.Profils[self.nProfil][0]])
            ob.location = Vector((0.0, 0.0, 0.0))
    
            # Link object to scene and make active
            bpy.context.collection.objects.link(ob)
            bpy.context.view_layer.update()
            bpy.context.view_layer.objects.active = ob
            ob.select_set(True)
            ob.location = Vector((10000.0, 0.0, 0.0))
            ob.display_type = "WIRE"
    
            self.SolidifyPossible = True
        else:
            bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]]
    
    
    def Selection_Save_Restore(self):
    
        if "CT_Profil" in bpy.data.objects:
            Selection_Save(self)
            bpy.ops.object.select_all(action='DESELECT')
            bpy.data.objects["CT_Profil"].select_set(True)
            bpy.context.view_layer.objects.active = bpy.data.objects["CT_Profil"]
            if bpy.data.objects["CT_Profil"] in self.all_sel_obj_list:
                self.all_sel_obj_list.remove(bpy.data.objects["CT_Profil"])
            bpy.ops.object.delete(use_global=False)
            Selection_Restore(self)
    
    
    def Selection_Save(self):
    
        obj_name = getattr(bpy.context.active_object, "name", None)
        self.all_sel_obj_list = bpy.context.selected_objects.copy()
        self.save_active_obj = obj_name
    
    
    
    def Selection_Restore(self):
    
        for o in self.all_sel_obj_list:
            o.select_set(True)
        if self.save_active_obj:
            bpy.context.view_layer.objects.active = bpy.data.objects.get(self.save_active_obj, None)
    
    
    def Snap_Cursor(self, context, event, mouse_pos):
    
        """ Find the closest position on the overlay grid and snap the mouse on it """
        # Get the context arguments
        region = context.region
        rv3d = context.region_data
    
        # Get the VIEW3D area
        for i, a in enumerate(context.screen.areas):
            if a.type == 'VIEW_3D':
                space = context.screen.areas[i].spaces.active
    
        # Get the grid overlay for the VIEW_3D
        grid_scale = space.overlay.grid_scale
        grid_subdivisions = space.overlay.grid_subdivisions
    
        # Use the grid scale and subdivision to get the increment
        increment = (grid_scale / grid_subdivisions)
        half_increment = increment / 2
    
        # Convert the 2d location of the mouse in 3d
        for index, loc in enumerate(reversed(mouse_pos)):
            mouse_loc_3d = region_2d_to_location_3d(region, rv3d, loc, (0, 0, 0))
    
            # Get the remainder from the mouse location and the ratio
            # Test if the remainder > to the half of the increment
            for i in range(3):
                modulo = mouse_loc_3d[i] % increment
                if modulo < half_increment:
                    modulo = - modulo
                else:
                    modulo = increment - modulo
    
                # Add the remainder to get the closest location on the grid
                mouse_loc_3d[i] = mouse_loc_3d[i] + modulo
    
            # Get the snapped 2d location
            snap_loc_2d = location_3d_to_region_2d(region, rv3d, mouse_loc_3d)
    
            # Replace the last mouse location by the snapped location
            if len(self.mouse_path) > 0:
                self.mouse_path[len(self.mouse_path) - (index + 1) ] = tuple(snap_loc_2d)
    
    
    def mini_grid(self, context, color):
    
        """ Draw a snap mini grid around the cursor based on the overlay grid"""
        # Get the context arguments
        region = context.region
        rv3d = context.region_data
    
        # Get the VIEW3D area
        for i, a in enumerate(context.screen.areas):
            if a.type == 'VIEW_3D':
                space = context.screen.areas[i].spaces.active
                screen_height = context.screen.areas[i].height
                screen_width = context.screen.areas[i].width
    
        #Draw the snap grid, only in ortho view
        if not space.region_3d.is_perspective :
            grid_scale = space.overlay.grid_scale
            grid_subdivisions = space.overlay.grid_subdivisions
            increment = (grid_scale / grid_subdivisions)
    
            # Get the 3d location of the mouse forced to a snap value in the operator
            mouse_coord = self.mouse_path[len(self.mouse_path) - 1]
    
            snap_loc = region_2d_to_location_3d(region, rv3d, mouse_coord, (0, 0, 0))
    
            # Add the increment to get the closest location on the grid
            snap_loc[0] += increment
            snap_loc[1] += increment
    
            # Get the 2d location of the snap location
            snap_loc = location_3d_to_region_2d(region, rv3d, snap_loc)
            origin = location_3d_to_region_2d(region, rv3d, (0,0,0))
    
            # Get the increment value
            snap_value = snap_loc[0] - mouse_coord[0]
    
            grid_coords = []
    
            # Draw lines on X and Z axis from the cursor through the screen
            grid_coords = [
            (0, mouse_coord[1]), (screen_width, mouse_coord[1]),
            (mouse_coord[0], 0), (mouse_coord[0], screen_height)
            ]
    
            # Draw a mlini grid around the cursor to show the snap options
            grid_coords += [
            (mouse_coord[0] + snap_value, mouse_coord[1] + 25 + snap_value),
            (mouse_coord[0] + snap_value, mouse_coord[1] - 25 - snap_value),
            (mouse_coord[0] + 25 + snap_value, mouse_coord[1] + snap_value),
            (mouse_coord[0] - 25 - snap_value, mouse_coord[1] + snap_value),
            (mouse_coord[0] - snap_value, mouse_coord[1] + 25 + snap_value),
            (mouse_coord[0] - snap_value, mouse_coord[1] - 25 - snap_value),
            (mouse_coord[0] + 25 + snap_value, mouse_coord[1] - snap_value),
            (mouse_coord[0] - 25 - snap_value, mouse_coord[1] - snap_value),
            ]
            draw_shader(self, color, 0.3, 'LINES', grid_coords, size=2)
    
    
    
    def draw_shader(self, color, alpha, type, coords, size=1, indices=None):
    
        """ Create a batch for a draw type """
    
        gpu.state.blend_set('ALPHA')
    
        if type =='POINTS':
    
            gpu.state.program_point_size_set(False)
            gpu.state.point_size_set(size)
            shader = gpu.shader.from_builtin('UNIFORM_COLOR')
    
        else:
    
            shader = gpu.shader.from_builtin('POLYLINE_UNIFORM_COLOR')
            shader.uniform_float("viewportSize", gpu.state.viewport_get()[2:])
            shader.uniform_float("lineWidth", 1.0)
    
    
        try:
            shader.uniform_float("color", (color[0], color[1], color[2], alpha))
    
            batch = batch_for_shader(shader, type, {"pos": coords}, indices=indices)
    
            batch.draw(shader)
        except:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            self.report({'ERROR'}, str(exc_value))
    
    
        gpu.state.point_size_set(1.0)
        gpu.state.blend_set('NONE')