Skip to content
Snippets Groups Projects
carver_utils.py 26.95 KiB

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()
		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.update()
		bpy.context.view_layer.objects.active = ob
		ob.select_set(True)
		ob.location = Vector((10000.0, 0.0, 0.0))
		ob.display_type = "WIRE"

		self.SolidifyPossible = True
	else:
		bpy.data.objects["CT_Profil"].data = bpy.data.meshes[self.Profils[self.nProfil][0]]

def Selection_Save_Restore(self):
	if "CT_Profil" in bpy.data.objects:
		Selection_Save(self)
		bpy.ops.object.select_all(action='DESELECT')
		bpy.data.objects["CT_Profil"].select_set(True)
		bpy.context.view_layer.objects.active = bpy.data.objects["CT_Profil"]
		if bpy.data.objects["CT_Profil"] in self.all_sel_obj_list:
			self.all_sel_obj_list.remove(bpy.data.objects["CT_Profil"])
		bpy.ops.object.delete(use_global=False)
		Selection_Restore(self)

def Selection_Save(self):
	obj_name = getattr(bpy.context.active_object, "name", None)
	self.all_sel_obj_list = bpy.context.selected_objects.copy()
	self.save_active_obj = obj_name


def Selection_Restore(self):
	for o in self.all_sel_obj_list:
		o.select_set(True)
	if self.save_active_obj:
		bpy.context.view_layer.objects.active = bpy.data.objects.get(self.save_active_obj, None)

def Snap_Cursor(self, context, event, mouse_pos):
	""" Find the closest position on the overlay grid and snap the mouse on it """
	# Get the context arguments
	region = context.region
	rv3d = context.region_data

	# Get the VIEW3D area
	for i, a in enumerate(context.screen.areas):
		if a.type == 'VIEW_3D':
			space = context.screen.areas[i].spaces.active

	# Get the grid overlay for the VIEW_3D
	grid_scale = space.overlay.grid_scale
	grid_subdivisions = space.overlay.grid_subdivisions

	# Use the grid scale and subdivision to get the increment
	increment = (grid_scale / grid_subdivisions)
	half_increment = increment / 2

	# Convert the 2d location of the mouse in 3d
	for index, loc in enumerate(reversed(mouse_pos)):
		mouse_loc_3d = region_2d_to_location_3d(region, rv3d, loc, (0, 0, 0))

		# Get the remainder from the mouse location and the ratio
		# Test if the remainder > to the half of the increment
		for i in range(3):
			modulo = mouse_loc_3d[i] % increment
			if modulo < half_increment:
				modulo = - modulo
			else:
				modulo = increment - modulo

			# Add the remainder to get the closest location on the grid
			mouse_loc_3d[i] = mouse_loc_3d[i] + modulo

		# Get the snapped 2d location
		snap_loc_2d = location_3d_to_region_2d(region, rv3d, mouse_loc_3d)

		# Replace the last mouse location by the snapped location
		if len(self.mouse_path) > 0:
			self.mouse_path[len(self.mouse_path) - (index + 1) ] = tuple(snap_loc_2d)

def mini_grid(self, context, color):
	""" Draw a snap mini grid around the cursor based on the overlay grid"""
	# Get the context arguments
	region = context.region
	rv3d = context.region_data

	# Get the VIEW3D area
	for i, a in enumerate(context.screen.areas):
		if a.type == 'VIEW_3D':
			space = context.screen.areas[i].spaces.active
			screen_height = context.screen.areas[i].height
			screen_width = context.screen.areas[i].width

	#Draw the snap grid, only in ortho view
	if not space.region_3d.is_perspective :
		grid_scale = space.overlay.grid_scale
		grid_subdivisions = space.overlay.grid_subdivisions
		increment = (grid_scale / grid_subdivisions)

		# Get the 3d location of the mouse forced to a snap value in the operator
		mouse_coord = self.mouse_path[len(self.mouse_path) - 1]

		snap_loc = region_2d_to_location_3d(region, rv3d, mouse_coord, (0, 0, 0))

		# Add the increment to get the closest location on the grid
		snap_loc[0] += increment
		snap_loc[1] += increment

		# Get the 2d location of the snap location
		snap_loc = location_3d_to_region_2d(region, rv3d, snap_loc)
		origin = location_3d_to_region_2d(region, rv3d, (0,0,0))

		# Get the increment value
		snap_value = snap_loc[0] - mouse_coord[0]

		grid_coords = []

		# Draw lines on X and Z axis from the cursor through the screen
		grid_coords = [
		(0, mouse_coord[1]), (screen_width, mouse_coord[1]),
		(mouse_coord[0], 0), (mouse_coord[0], screen_height)
		]

		# Draw a mlini grid around the cursor to show the snap options
		grid_coords += [
		(mouse_coord[0] + snap_value, mouse_coord[1] + 25 + snap_value),
		(mouse_coord[0] + snap_value, mouse_coord[1] - 25 - snap_value),
		(mouse_coord[0] + 25 + snap_value, mouse_coord[1] + snap_value),
		(mouse_coord[0] - 25 - snap_value, mouse_coord[1] + snap_value),
		(mouse_coord[0] - snap_value, mouse_coord[1] + 25 + snap_value),
		(mouse_coord[0] - snap_value, mouse_coord[1] - 25 - snap_value),
		(mouse_coord[0] + 25 + snap_value, mouse_coord[1] - snap_value),
		(mouse_coord[0] - 25 - snap_value, mouse_coord[1] - snap_value),
		]
		draw_shader(self, color, 0.3, 'LINES', grid_coords, size=2)


def draw_shader(self, color, alpha, type, coords, size=1, indices=None):
	""" Create a batch for a draw type """
	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))