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