1
2import bpy
3import bgl
4import gpu
5from gpu_extras.batch import batch_for_shader
6import math
7import sys
8import random
9import bmesh
10from mathutils import (
11    Euler,
12    Matrix,
13    Vector,
14    Quaternion,
15)
16from mathutils.geometry import (
17    intersect_line_plane,
18)
19
20from math import (
21    sin,
22    cos,
23    pi,
24    )
25
26import bpy_extras
27
28from bpy_extras import view3d_utils
29from bpy_extras.view3d_utils import (
30    region_2d_to_vector_3d,
31    region_2d_to_location_3d,
32    location_3d_to_region_2d,
33)
34
35# Cut Square
36def CreateCutSquare(self, context):
37    """ Create a rectangle mesh """
38    far_limit = 10000.0
39    faces=[]
40
41    # Get the mouse coordinates
42    coord = self.mouse_path[0][0], self.mouse_path[0][1]
43
44    # New mesh
45    me = bpy.data.meshes.new('CMT_Square')
46    bm = bmesh.new()
47    bm.from_mesh(me)
48
49    # New object and link it to the scene
50    ob = bpy.data.objects.new('CMT_Square', me)
51    self.CurrentObj = ob
52    context.collection.objects.link(ob)
53
54    # Scene information
55    region = context.region
56    rv3d = context.region_data
57    depth_location = region_2d_to_vector_3d(region, rv3d, coord)
58    self.ViewVector = depth_location
59
60    # Get a point on a infinite plane and its direction
61    plane_normal = depth_location
62    plane_direction = plane_normal.normalized()
63
64    if self.snapCursor:
65        plane_point = context.scene.cursor.location
66    else:
67        plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
68
69    # Find the intersection of a line going thru each vertex and the infinite plane
70    for v_co in self.rectangle_coord:
71        vec = region_2d_to_vector_3d(region, rv3d, v_co)
72        p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
73        p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
74        faces.append(bm.verts.new(intersect_line_plane(p0, p1, plane_point, plane_direction)))
75
76    # Update vertices index
77    bm.verts.index_update()
78    # New faces
79    t_face = bm.faces.new(faces)
80    # Set mesh
81    bm.to_mesh(me)
82
83
84# Cut Line
85def CreateCutLine(self, context):
86    """ Create a polygon mesh """
87    far_limit = 10000.0
88    vertices = []
89    faces = []
90    loc = []
91
92    # Get the mouse coordinates
93    coord = self.mouse_path[0][0], self.mouse_path[0][1]
94
95    # New mesh
96    me = bpy.data.meshes.new('CMT_Line')
97    bm = bmesh.new()
98    bm.from_mesh(me)
99
100    # New object and link it to the scene
101    ob = bpy.data.objects.new('CMT_Line', me)
102    self.CurrentObj = ob
103    context.collection.objects.link(ob)
104
105    # Scene information
106    region = context.region
107    rv3d = context.region_data
108    depth_location = region_2d_to_vector_3d(region, rv3d, coord)
109    self.ViewVector = depth_location
110
111    # Get a point on a infinite plane and its direction
112    plane_normal = depth_location
113    plane_direction = plane_normal.normalized()
114
115    if self.snapCursor:
116        plane_point = context.scene.cursor.location
117    else:
118        plane_point = self.OpsObj.location if self.OpsObj is not None else Vector((0.0, 0.0, 0.0))
119
120    # Use dict to remove doubles
121    # Find the intersection of a line going thru each vertex and the infinite plane
122    for idx, v_co in enumerate(list(dict.fromkeys(self.mouse_path))):
123        vec = region_2d_to_vector_3d(region, rv3d, v_co)
124        p0 = region_2d_to_location_3d(region, rv3d,v_co, vec)
125        p1 = region_2d_to_location_3d(region, rv3d,v_co, vec) + plane_direction * far_limit
126        loc.append(intersect_line_plane(p0, p1, plane_point, plane_direction))
127        vertices.append(bm.verts.new(loc[idx]))
128
129        if idx > 0:
130            bm.edges.new([vertices[idx-1],vertices[idx]])
131
132        faces.append(vertices[idx])
133
134    # Update vertices index
135    bm.verts.index_update()
136
137    # Nothing is selected, create close geometry
138    if self.CreateMode:
139        if self.Closed and len(vertices) > 1:
140            bm.edges.new([vertices[-1], vertices[0]])
141            bm.faces.new(faces)
142    else:
143        # Create faces if more than 2 vertices
144        if len(vertices) > 1 :
145            bm.edges.new([vertices[-1], vertices[0]])
146            bm.faces.new(faces)
147
148    bm.to_mesh(me)
149
150# Cut Circle
151def CreateCutCircle(self, context):
152    """ Create a circle mesh """
153    far_limit = 10000.0
154    FacesList = []
155
156    # Get the mouse coordinates
157    mouse_pos_x = self.mouse_path[0][0]
158    mouse_pos_y = self.mouse_path[0][1]
159    coord = self.mouse_path[0][0], self.mouse_path[0][1]
160
161    # Scene information
162    region = context.region
163    rv3d = context.region_data
164    depth_location = region_2d_to_vector_3d(region, rv3d, coord)
165    self.ViewVector = depth_location
166
167    # Get a point on a infinite plane and its direction
168    plane_point = context.scene.cursor.location if self.snapCursor else Vector((0.0, 0.0, 0.0))
169    plane_normal = depth_location
170    plane_direction = plane_normal.normalized()
171
172    # New mesh
173    me = bpy.data.meshes.new('CMT_Circle')
174    bm = bmesh.new()
175    bm.from_mesh(me)
176
177    # New object and link it to the scene
178    ob = bpy.data.objects.new('CMT_Circle', me)
179    self.CurrentObj = ob
180    context.collection.objects.link(ob)
181
182    # Create a circle using a tri fan
183    tris_fan, indices = draw_circle(self, mouse_pos_x, mouse_pos_y)
184
185    # Remove the vertex in the center to get the outer line of the circle
186    verts = tris_fan[1:]
187
188    # Find the intersection of a line going thru each vertex and the infinite plane
189    for vert in verts:
190        vec = region_2d_to_vector_3d(region, rv3d, vert)
191        p0 = region_2d_to_location_3d(region, rv3d, vert, vec)
192        p1 = p0 + plane_direction * far_limit
193        loc0 = intersect_line_plane(p0, p1, plane_point, plane_direction)
194        t_v0 = bm.verts.new(loc0)
195        FacesList.append(t_v0)
196
197    bm.verts.index_update()
198    bm.faces.new(FacesList)
199    bm.to_mesh(me)
200
201
202def create_2d_circle(self, step, radius, rotation = 0):
203    """ Create the vertices of a 2d circle at (0,0) """
204    verts = []
205    for angle in range(0, 360, step):
206        verts.append(math.cos(math.radians(angle + rotation)) * radius)
207        verts.append(math.sin(math.radians(angle + rotation)) * radius)
208        verts.append(0.0)
209    verts.append(math.cos(math.radians(0.0 + rotation)) * radius)
210    verts.append(math.sin(math.radians(0.0 + rotation)) * radius)
211    verts.append(0.0)
212    return(verts)
213
214
215def draw_circle(self, mouse_pos_x, mouse_pos_y):
216    """ Return the coordinates + indices of a circle using a triangle fan """
217    tris_verts = []
218    indices = []
219    segments = int(360 / self.stepAngle[self.step])
220    radius = self.mouse_path[1][0] - self.mouse_path[0][0]
221    rotation = (self.mouse_path[1][1] - self.mouse_path[0][1]) / 2
222
223    # Get the vertices of a 2d circle
224    verts = create_2d_circle(self, self.stepAngle[self.step], radius, rotation)
225
226    # Create the first vertex at mouse position for the center of the circle
227    tris_verts.append(Vector((mouse_pos_x + self.xpos , mouse_pos_y + self.ypos)))
228
229    # For each vertex of the circle, add the mouse position and the translation
230    for idx in range(int(len(verts) / 3) - 1):
231        tris_verts.append(Vector((verts[idx * 3] + mouse_pos_x + self.xpos, \
232                                  verts[idx * 3 + 1] + mouse_pos_y + self.ypos)))
233        i1 = idx+1
234        i2 = idx+2 if idx+2 <= segments else 1
235        indices.append((0,i1,i2))
236
237    return(tris_verts, indices)
238
239# Object dimensions (SCULPT Tools tips)
240def objDiagonal(obj):
241    return ((obj.dimensions[0]**2) + (obj.dimensions[1]**2) + (obj.dimensions[2]**2))**0.5
242
243
244# Bevel Update
245def update_bevel(context):
246    selection = context.selected_objects.copy()
247    active = context.active_object
248
249    if len(selection) > 0:
250        for obj in selection:
251            bpy.ops.object.select_all(action='DESELECT')
252            obj.select_set(True)
253            context.view_layer.objects.active = obj
254
255            # Test object name
256            # Subdive mode : Only bevel weight
257            if obj.data.name.startswith("S_") or obj.data.name.startswith("S "):
258                bpy.ops.object.mode_set(mode='EDIT')
259                bpy.ops.mesh.region_to_loop()
260                bpy.ops.transform.edge_bevelweight(value=1)
261                bpy.ops.object.mode_set(mode='OBJECT')
262
263            else:
264                # No subdiv mode : bevel weight + Crease + Sharp
265                CreateBevel(context, obj)
266
267    bpy.ops.object.select_all(action='DESELECT')
268
269    for obj in selection:
270        obj.select_set(True)
271    context.view_layer.objects.active = active
272
273# Create bevel
274def CreateBevel(context, CurrentObject):
275    # Save active object
276    SavActive = context.active_object
277
278    # Test if initial object has bevel
279    bevel_modifier = False
280    for modifier in SavActive.modifiers:
281        if modifier.name == 'Bevel':
282            bevel_modifier = True
283
284    if bevel_modifier:
285        # Active "CurrentObject"
286        context.view_layer.objects.active = CurrentObject
287
288        bpy.ops.object.mode_set(mode='EDIT')
289
290        # Edge mode
291        bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
292        # Clear all
293        bpy.ops.mesh.select_all(action='SELECT')
294        bpy.ops.mesh.mark_sharp(clear=True)
295        bpy.ops.transform.edge_crease(value=-1)
296        bpy.ops.transform.edge_bevelweight(value=-1)
297
298        bpy.ops.mesh.select_all(action='DESELECT')
299
300        # Select (in radians) all 30° sharp edges
301        bpy.ops.mesh.edges_select_sharp(sharpness=0.523599)
302        # Apply bevel weight + Crease + Sharp to the selected edges
303        bpy.ops.mesh.mark_sharp()
304        bpy.ops.transform.edge_crease(value=1)
305        bpy.ops.transform.edge_bevelweight(value=1)
306
307        bpy.ops.mesh.select_all(action='DESELECT')
308
309        bpy.ops.object.mode_set(mode='OBJECT')
310
311        CurrentObject.data.use_customdata_edge_bevel = True
312
313        for i in range(len(CurrentObject.data.edges)):
314            if CurrentObject.data.edges[i].select is True:
315                CurrentObject.data.edges[i].bevel_weight = 1.0
316                CurrentObject.data.edges[i].use_edge_sharp = True
317
318        bevel_modifier = False
319        for m in CurrentObject.modifiers:
320            if m.name == 'Bevel':
321                bevel_modifier = True
322
323        if bevel_modifier is False:
324            bpy.ops.object.modifier_add(type='BEVEL')
325            mod = context.object.modifiers[-1]
326            mod.limit_method = 'WEIGHT'
327            mod.width = 0.01
328            mod.profile = 0.699099
329            mod.use_clight_overlap = False
330            mod.segments = 3
331            mod.loop_slide = False
332
333        bpy.ops.object.shade_smooth()
334
335        context.object.data.use_auto_smooth = True
336        context.object.data.auto_smooth_angle = 1.0471975
337
338        # Restore the active object
339        context.view_layer.objects.active = SavActive
340
341
342def MoveCursor(qRot, location, self):
343    """ In brush mode : Draw a circle around the brush """
344    if qRot is not None:
345        verts = create_2d_circle(self, 10, 1)
346        self.CLR_C.clear()
347        vc = Vector()
348        for idx in range(int(len(verts) / 3)):
349            vc.x = verts[idx * 3]
350            vc.y = verts[idx * 3 + 1]
351            vc.z = verts[idx * 3 + 2]
352            vc = qRot @ vc
353            self.CLR_C.append(vc.x)
354            self.CLR_C.append(vc.y)
355            self.CLR_C.append(vc.z)
356
357
358def rot_axis_quat(vector1, vector2):
359    """ Find the rotation (quaternion) from vector 1 to vector 2"""
360    vector1 = vector1.normalized()
361    vector2 = vector2.normalized()
362    cosTheta = vector1.dot(vector2)
363    rotationAxis = Vector((0.0, 0.0, 0.0))
364    if (cosTheta < -1 + 0.001):
365        v = Vector((0.0, 1.0, 0.0))
366        #Get the vector at the right angles to both
367        rotationAxis = vector1.cross(v)
368        rotationAxis = rotationAxis.normalized()
369        q = Quaternion()
370        q.w = 0.0
371        q.x = rotationAxis.x
372        q.y = rotationAxis.y
373        q.z = rotationAxis.z
374    else:
375        rotationAxis = vector1.cross(vector2)
376        s = math.sqrt((1.0 + cosTheta) * 2.0)
377        invs = 1 / s
378        q = Quaternion()
379        q.w = s * 0.5
380        q.x = rotationAxis.x * invs
381        q.y = rotationAxis.y * invs
382        q.z = rotationAxis.z * invs
383    return q
384
385
386# Picking (template)
387def Picking(context, event):
388    """ Put the 3d cursor on the closest object"""
389
390    # get the context arguments
391    scene = context.scene
392    region = context.region
393    rv3d = context.region_data
394    coord = event.mouse_region_x, event.mouse_region_y
395
396    # get the ray from the viewport and mouse
397    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
398    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
399    ray_target = ray_origin + view_vector
400
401    def visible_objects_and_duplis():
402        depsgraph = context.evaluated_depsgraph_get()
403        for dup in depsgraph.object_instances:
404            if dup.is_instance:  # Real dupli instance
405                obj = dup.instance_object.original
406                yield (obj, dup.matrix.copy())
407            else:  # Usual object
408                obj = dup.object.original
409                yield (obj, obj.matrix_world.copy())
410
411    def obj_ray_cast(obj, matrix):
412        # get the ray relative to the object
413        matrix_inv = matrix.inverted()
414        ray_origin_obj = matrix_inv @ ray_origin
415        ray_target_obj = matrix_inv @ ray_target
416        ray_direction_obj = ray_target_obj - ray_origin_obj
417        # cast the ray
418        success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)
419        if success:
420            return location, normal, face_index
421        return None, None, None
422
423    # cast rays and find the closest object
424    best_length_squared = -1.0
425    best_obj = None
426
427    # cast rays and find the closest object
428    for obj, matrix in visible_objects_and_duplis():
429        if obj.type == 'MESH':
430            hit, normal, face_index = obj_ray_cast(obj, matrix)
431            if hit is not None:
432                hit_world = matrix @ hit
433                length_squared = (hit_world - ray_origin).length_squared
434                if best_obj is None or length_squared < best_length_squared:
435                    scene.cursor.location = hit_world
436                    best_length_squared = length_squared
437                    best_obj = obj
438            else:
439                if best_obj is None:
440                    depth_location = region_2d_to_vector_3d(region, rv3d, coord)
441                    loc = region_2d_to_location_3d(region, rv3d, coord, depth_location)
442                    scene.cursor.location = loc
443
444
445def Pick(context, event, self, ray_max=10000.0):
446    region = context.region
447    rv3d = context.region_data
448    coord = event.mouse_region_x, event.mouse_region_y
449    view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
450    ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
451    ray_target = ray_origin + (view_vector * ray_max)
452
453    def obj_ray_cast(obj, matrix):
454        matrix_inv = matrix.inverted()
455        ray_origin_obj = matrix_inv @ ray_origin
456        ray_target_obj = matrix_inv @ ray_target
457        success, hit, normal, face_index = obj.ray_cast(ray_origin_obj, ray_target_obj)
458        if success:
459            return hit, normal, face_index
460        return None, None, None
461
462    best_length_squared = ray_max * ray_max
463    best_obj = None
464    for obj in self.CList:
465        matrix = obj.matrix_world
466        hit, normal, face_index = obj_ray_cast(obj, matrix)
467        rotation = obj.rotation_euler.to_quaternion()
468        if hit is not None:
469            hit_world = matrix @ hit
470            length_squared = (hit_world - ray_origin).length_squared
471            if length_squared < best_length_squared:
472                best_length_squared = length_squared
473                best_obj = obj
474                hits = hit_world
475                ns = normal
476                fs = face_index
477
478    if best_obj is not None:
479        return hits, ns, rotation
480
481    return None, None, None
482
483def SelectObject(self, copyobj):
484    copyobj.select_set(True)
485
486    for child in copyobj.children:
487        SelectObject(self, child)
488
489    if copyobj.parent is None:
490        bpy.context.view_layer.objects.active = copyobj
491
492# Undo
493def printUndo(self):
494    for l in self.UList:
495        print(l)
496
497
498def UndoAdd(self, type, obj):
499    """ Create a backup mesh before apply the action to the object """
500    if obj is None:
501        return
502
503    if type != "DUPLICATE":
504        bm = bmesh.new()
505        bm.from_mesh(obj.data)
506        self.UndoOps.append((obj, type, bm))
507    else:
508        self.UndoOps.append((obj, type, None))
509
510
511def UndoListUpdate(self):
512    self.UList.append((self.UndoOps.copy()))
513    self.UList_Index += 1
514    self.UndoOps.clear()
515
516
517def Undo(self):
518    if self.UList_Index < 0:
519        return
520    # get previous mesh
521    for o in self.UList[self.UList_Index]:
522        if o[1] == "MESH":
523            bm = o[2]
524            bm.to_mesh(o[0].data)
525
526    SelectObjList = bpy.context.selected_objects.copy()
527    Active_Obj = bpy.context.active_object
528    bpy.ops.object.select_all(action='TOGGLE')
529
530    for o in self.UList[self.UList_Index]:
531        if o[1] == "REBOOL":
532            o[0].select_set(True)
533            o[0].hide_viewport = False
534
535        if o[1] == "DUPLICATE":
536            o[0].select_set(True)
537            o[0].hide_viewport = False
538
539    bpy.ops.object.delete(use_global=False)
540
541    for so in SelectObjList:
542        bpy.data.objects[so.name].select_set(True)
543    bpy.context.view_layer.objects.active = Active_Obj
544
545    self.UList_Index -= 1
546    self.UList[self.UList_Index + 1:] = []
547
548
549def duplicateObject(self):
550    if self.Instantiate:
551        bpy.ops.object.duplicate_move_linked(
552            OBJECT_OT_duplicate={
553                "linked": True,
554                "mode": 'TRANSLATION',
555            },
556            TRANSFORM_OT_translate={
557                "value": (0, 0, 0),
558            },
559        )
560    else:
561        bpy.ops.object.duplicate_move(
562            OBJECT_OT_duplicate={
563                "linked": False,
564                "mode": 'TRANSLATION',
565            },
566            TRANSFORM_OT_translate={
567                "value": (0, 0, 0),
568            },
569        )
570
571    ob_new = bpy.context.active_object
572
573    ob_new.location = self.CurLoc
574    v = Vector()
575    v.x = v.y = 0.0
576    v.z = self.BrushDepthOffset
577    ob_new.location += self.qRot * v
578
579    if self.ObjectMode:
580        ob_new.scale = self.ObjectBrush.scale
581    if self.ProfileMode:
582        ob_new.scale = self.ProfileBrush.scale
583
584    e = Euler()
585    e.x = e.y = 0.0
586    e.z = self.aRotZ / 25.0
587
588    # If duplicate with a grid, no random rotation (each mesh in the grid is already rotated randomly)
589    if (self.alt is True) and ((self.nbcol + self.nbrow) < 3):
590        if self.RandomRotation:
591            e.z += random.random()
592
593    qe = e.to_quaternion()
594    qRot = self.qRot * qe
595    ob_new.rotation_mode = 'QUATERNION'
596    ob_new.rotation_quaternion = qRot
597    ob_new.rotation_mode = 'XYZ'
598
599    if (ob_new.display_type == "WIRE") and (self.BrushSolidify is False):
600        ob_new.hide_viewport = True
601
602    if self.BrushSolidify:
603        ob_new.display_type = "SOLID"
604        ob_new.show_in_front = False
605
606    for o in bpy.context.selected_objects:
607        UndoAdd(self, "DUPLICATE", o)
608
609    if len(bpy.context.selected_objects) > 0:
610        bpy.ops.object.select_all(action='TOGGLE')
611    for o in self.all_sel_obj_list:
612        o.select_set(True)
613
614    bpy.context.view_layer.objects.active = self.OpsObj
615
616
617def update_grid(self, context):
618    """
619    Thanks to batFINGER for his help :
620    source : http://blender.stackexchange.com/questions/55864/multiple-meshes-not-welded-with-pydata
621    """
622    verts = []
623    edges = []
624    faces = []
625    numface = 0
626
627    if self.nbcol < 1:
628        self.nbcol = 1
629    if self.nbrow < 1:
630        self.nbrow = 1
631    if self.gapx < 0:
632        self.gapx = 0
633    if self.gapy < 0:
634        self.gapy = 0
635
636    # Get the data from the profils or the object
637    if self.ProfileMode:
638        brush = bpy.data.objects.new(
639                    self.Profils[self.nProfil][0],
640                    bpy.data.meshes[self.Profils[self.nProfil][0]]
641                    )
642        obj = bpy.data.objects["CT_Profil"]
643        obfaces = brush.data.polygons
644        obverts = brush.data.vertices
645        lenverts = len(obverts)
646    else:
647        brush = bpy.data.objects["CarverBrushCopy"]
648        obj = context.selected_objects[0]
649        obverts = brush.data.vertices
650        obfaces = brush.data.polygons
651        lenverts = len(brush.data.vertices)
652
653    # Gap between each row / column
654    gapx = self.gapx
655    gapy = self.gapy
656
657    # Width of each row / column
658    widthx = brush.dimensions.x * self.scale_x
659    widthy = brush.dimensions.y * self.scale_y
660
661    # Compute the corners so the new object will be always at the center
662    left = -((self.nbcol - 1) * (widthx + gapx)) / 2
663    start = -((self.nbrow - 1) * (widthy + gapy)) / 2
664
665    for i in range(self.nbrow * self.nbcol):
666        row = i % self.nbrow
667        col = i // self.nbrow
668        startx = left + ((widthx + gapx) * col)
669        starty = start + ((widthy + gapy) * row)
670
671        # Add random rotation
672        if (self.RandomRotation) and not (self.GridScaleX or self.GridScaleY):
673            rotmat = Matrix.Rotation(math.radians(360 * random.random()), 4, 'Z')
674            for v in obverts:
675                v.co = v.co @ rotmat
676
677        verts.extend([((v.co.x - startx, v.co.y - starty, v.co.z)) for v in obverts])
678        faces.extend([[v + numface * lenverts for v in p.vertices] for p in obfaces])
679        numface += 1
680
681    # Update the mesh
682    # Create mesh data
683    mymesh = bpy.data.meshes.new("CT_Profil")
684    # Generate mesh data
685    mymesh.from_pydata(verts, edges, faces)
686    # Calculate the edges
687    mymesh.update(calc_edges=True)
688    # Update data
689    obj.data = mymesh
690    # Make the object active to remove doubles
691    context.view_layer.objects.active = obj
692
693
694def boolean_operation(bool_type="DIFFERENCE"):
695    ActiveObj = bpy.context.active_object
696    sel_index = 0 if bpy.context.selected_objects[0] != bpy.context.active_object else 1
697
698    # bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
699    bool_name = "CT_" + bpy.context.selected_objects[sel_index].name
700    BoolMod = ActiveObj.modifiers.new(bool_name, "BOOLEAN")
701    BoolMod.object = bpy.context.selected_objects[sel_index]
702    BoolMod.operation = bool_type
703    bpy.context.selected_objects[sel_index].display_type = 'WIRE'
704    while ActiveObj.modifiers.find(bool_name) > 0:
705        bpy.ops.object.modifier_move_up(modifier=bool_name)
706
707
708def Rebool(context, self):
709
710    target_obj = context.active_object
711
712    Brush = context.selected_objects[1]
713    Brush.display_type = "WIRE"
714
715    #Deselect all
716    bpy.ops.object.select_all(action='TOGGLE')
717
718    target_obj.display_type = "SOLID"
719    target_obj.select_set(True)
720    bpy.ops.object.duplicate()
721
722    rebool_obj = context.active_object
723
724    m = rebool_obj.modifiers.new("CT_INTERSECT", "BOOLEAN")
725    m.operation = "INTERSECT"
726    m.object = Brush
727
728    m = target_obj.modifiers.new("CT_DIFFERENCE", "BOOLEAN")
729    m.operation = "DIFFERENCE"
730    m.object = Brush
731
732    for mb in target_obj.modifiers:
733        if mb.type == 'BEVEL':
734            mb.show_viewport = False
735
736    if self.ObjectBrush or self.ProfileBrush:
737        rebool_obj.show_in_front = False
738        try:
739            bpy.ops.object.modifier_apply(modifier="CT_SOLIDIFY")
740        except:
741            exc_type, exc_value, exc_traceback = sys.exc_info()
742            self.report({'ERROR'}, str(exc_value))
743
744    if self.dont_apply_boolean is False:
745        try:
746            bpy.ops.object.modifier_apply(modifier="CT_INTERSECT")
747        except:
748            exc_type, exc_value, exc_traceback = sys.exc_info()
749            self.report({'ERROR'}, str(exc_value))
750
751    bpy.ops.object.select_all(action='TOGGLE')
752
753    for mb in target_obj.modifiers:
754        if mb.type == 'BEVEL':
755            mb.show_viewport = True
756
757    context.view_layer.objects.active = target_obj
758    target_obj.select_set(True)
759    if self.dont_apply_boolean is False:
760        try:
761            bpy.ops.object.modifier_apply(modifier="CT_DIFFERENCE")
762        except:
763            exc_type, exc_value, exc_traceback = sys.exc_info()
764            self.report({'ERROR'}, str(exc_value))
765
766    bpy.ops.object.select_all(action='TOGGLE')
767
768    rebool_obj.select_set(True)
769
770def createMeshFromData(self):
771    if self.Profils[self.nProfil][0] not in bpy.data.meshes:
772        # Create mesh and object
773        me = bpy.data.meshes.new(self.Profils[self.nProfil][0])
774        # Create mesh from given verts, faces.
775        me.from_pydata(self.Profils[self.nProfil][2], [], self.Profils[self.nProfil][3])
776        me.validate(verbose=True, clean_customdata=True)
777        # Update mesh with new data
778        me.update()
779
780    if "CT_Profil" not in bpy.data.objects:
781        ob = bpy.data.objects.new("CT_Profil", bpy.data.meshes[self.Profils[self.nProfil][0]])
782        ob.location = Vector((0.0, 0.0, 0.0))
783
784        # Link object to scene and make active
785        bpy.context.collection.objects.link(ob)
786        bpy.context.view_layer.update()
787        bpy.context.view_layer.objects.active = ob
788        ob.select_set(True)
789        ob.location = Vector((10000.0, 0.0, 0.0))
790        ob.display_type = "WIRE"
791
792        self.SolidifyPossible = True
793    else:
794        bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]]
795
796def Selection_Save_Restore(self):
797    if "CT_Profil" in bpy.data.objects:
798        Selection_Save(self)
799        bpy.ops.object.select_all(action='DESELECT')
800        bpy.data.objects["CT_Profil"].select_set(True)
801        bpy.context.view_layer.objects.active = bpy.data.objects["CT_Profil"]
802        if bpy.data.objects["CT_Profil"] in self.all_sel_obj_list:
803            self.all_sel_obj_list.remove(bpy.data.objects["CT_Profil"])
804        bpy.ops.object.delete(use_global=False)
805        Selection_Restore(self)
806
807def Selection_Save(self):
808    obj_name = getattr(bpy.context.active_object, "name", None)
809    self.all_sel_obj_list = bpy.context.selected_objects.copy()
810    self.save_active_obj = obj_name
811
812
813def Selection_Restore(self):
814    for o in self.all_sel_obj_list:
815        o.select_set(True)
816    if self.save_active_obj:
817        bpy.context.view_layer.objects.active = bpy.data.objects.get(self.save_active_obj, None)
818
819def Snap_Cursor(self, context, event, mouse_pos):
820    """ Find the closest position on the overlay grid and snap the mouse on it """
821    # Get the context arguments
822    region = context.region
823    rv3d = context.region_data
824
825    # Get the VIEW3D area
826    for i, a in enumerate(context.screen.areas):
827        if a.type == 'VIEW_3D':
828            space = context.screen.areas[i].spaces.active
829
830    # Get the grid overlay for the VIEW_3D
831    grid_scale = space.overlay.grid_scale
832    grid_subdivisions = space.overlay.grid_subdivisions
833
834    # Use the grid scale and subdivision to get the increment
835    increment = (grid_scale / grid_subdivisions)
836    half_increment = increment / 2
837
838    # Convert the 2d location of the mouse in 3d
839    for index, loc in enumerate(reversed(mouse_pos)):
840        mouse_loc_3d = region_2d_to_location_3d(region, rv3d, loc, (0, 0, 0))
841
842        # Get the remainder from the mouse location and the ratio
843        # Test if the remainder > to the half of the increment
844        for i in range(3):
845            modulo = mouse_loc_3d[i] % increment
846            if modulo < half_increment:
847                modulo = - modulo
848            else:
849                modulo = increment - modulo
850
851            # Add the remainder to get the closest location on the grid
852            mouse_loc_3d[i] = mouse_loc_3d[i] + modulo
853
854        # Get the snapped 2d location
855        snap_loc_2d = location_3d_to_region_2d(region, rv3d, mouse_loc_3d)
856
857        # Replace the last mouse location by the snapped location
858        if len(self.mouse_path) > 0:
859            self.mouse_path[len(self.mouse_path) - (index + 1) ] = tuple(snap_loc_2d)
860
861def mini_grid(self, context, color):
862    """ Draw a snap mini grid around the cursor based on the overlay grid"""
863    # Get the context arguments
864    region = context.region
865    rv3d = context.region_data
866
867    # Get the VIEW3D area
868    for i, a in enumerate(context.screen.areas):
869        if a.type == 'VIEW_3D':
870            space = context.screen.areas[i].spaces.active
871            screen_height = context.screen.areas[i].height
872            screen_width = context.screen.areas[i].width
873
874    #Draw the snap grid, only in ortho view
875    if not space.region_3d.is_perspective :
876        grid_scale = space.overlay.grid_scale
877        grid_subdivisions = space.overlay.grid_subdivisions
878        increment = (grid_scale / grid_subdivisions)
879
880        # Get the 3d location of the mouse forced to a snap value in the operator
881        mouse_coord = self.mouse_path[len(self.mouse_path) - 1]
882
883        snap_loc = region_2d_to_location_3d(region, rv3d, mouse_coord, (0, 0, 0))
884
885        # Add the increment to get the closest location on the grid
886        snap_loc[0] += increment
887        snap_loc[1] += increment
888
889        # Get the 2d location of the snap location
890        snap_loc = location_3d_to_region_2d(region, rv3d, snap_loc)
891        origin = location_3d_to_region_2d(region, rv3d, (0,0,0))
892
893        # Get the increment value
894        snap_value = snap_loc[0] - mouse_coord[0]
895
896        grid_coords = []
897
898        # Draw lines on X and Z axis from the cursor through the screen
899        grid_coords = [
900        (0, mouse_coord[1]), (screen_width, mouse_coord[1]),
901        (mouse_coord[0], 0), (mouse_coord[0], screen_height)
902        ]
903
904        # Draw a mlini grid around the cursor to show the snap options
905        grid_coords += [
906        (mouse_coord[0] + snap_value, mouse_coord[1] + 25 + snap_value),
907        (mouse_coord[0] + snap_value, mouse_coord[1] - 25 - snap_value),
908        (mouse_coord[0] + 25 + snap_value, mouse_coord[1] + snap_value),
909        (mouse_coord[0] - 25 - snap_value, mouse_coord[1] + snap_value),
910        (mouse_coord[0] - snap_value, mouse_coord[1] + 25 + snap_value),
911        (mouse_coord[0] - snap_value, mouse_coord[1] - 25 - snap_value),
912        (mouse_coord[0] + 25 + snap_value, mouse_coord[1] - snap_value),
913        (mouse_coord[0] - 25 - snap_value, mouse_coord[1] - snap_value),
914        ]
915        draw_shader(self, color, 0.3, 'LINES', grid_coords, size=2)
916
917
918def draw_shader(self, color, alpha, type, coords, size=1, indices=None):
919    """ Create a batch for a draw type """
920    bgl.glEnable(bgl.GL_BLEND)
921    bgl.glEnable(bgl.GL_LINE_SMOOTH)
922    if type =='POINTS':
923        bgl.glPointSize(size)
924    else:
925        bgl.glLineWidth(size)
926    try:
927        if len(coords[0])>2:
928            shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
929        else:
930            shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
931        batch = batch_for_shader(shader, type, {"pos": coords}, indices=indices)
932        shader.bind()
933        shader.uniform_float("color", (color[0], color[1], color[2], alpha))
934        batch.draw(shader)
935        bgl.glLineWidth(1)
936        bgl.glPointSize(1)
937        bgl.glDisable(bgl.GL_LINE_SMOOTH)
938        bgl.glDisable(bgl.GL_BLEND)
939    except:
940        exc_type, exc_value, exc_traceback = sys.exc_info()
941        self.report({'ERROR'}, str(exc_value))
942