Skip to content
Snippets Groups Projects
carver_utils.py 26.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    import bpy
    import bgl
    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,
    )
    
    from math import (
    	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_clight_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()
    
    403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    		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()
    
    
    def Undo(self):
    	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(apply_as='DATA', 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)
    
    
    def Rebool(context, self):
    
    	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(apply_as='DATA', 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(apply_as='DATA', 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(apply_as='DATA', 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.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 """
    	bgl.glEnable(bgl.GL_BLEND)
    	bgl.glEnable(bgl.GL_LINE_SMOOTH)
    	if type =='POINTS':
    		bgl.glPointSize(size)
    	else:
    		bgl.glLineWidth(size)
    	try:
    		if len(coords[0])>2:
    			shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR')
    		else:
    			shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
    		batch = batch_for_shader(shader, type, {"pos": coords}, indices=indices)
    		shader.bind()
    		shader.uniform_float("color", (color[0], color[1], color[2], alpha))
    		batch.draw(shader)
    		bgl.glLineWidth(1)
    		bgl.glPointSize(1)
    		bgl.glDisable(bgl.GL_LINE_SMOOTH)
    		bgl.glDisable(bgl.GL_BLEND)
    	except:
    		exc_type, exc_value, exc_traceback = sys.exc_info()
    		self.report({'ERROR'}, str(exc_value))