CAD Snap Utilities

Just press ALT+E (it’s in extrude menu)

1 Like

drag longer …

it is not working well

An internal solution is in the works.

1 Like

Yep, indeed. So extrude and reshape could be like temporal solution until built-in version.

Before “Extrude and reshape” was working better
and “Destructive Extrude” worked perfectly.
Now is working nothing.

Try this.
Save it as

import bpy
from bpy_extras import view3d_utils
from mathutils import Vector, kdtree
from mathutils.geometry import intersect_line_plane
from mathutils.bvhtree import BVHTree

import numpy as np

bl_info = {
	"name": "Extrude Pull",
	"location": "Edit Mode: Mesh > Extrude > Extrude Pull Geometry",
	"description": "Extrude unwanted geometry away",
	"author": "Vladislav Kindushov, Martin Capitanio",
	"version": (1, 0, 6),
	"blender": (2, 80, 0),
	"category": "Mesh",

def Snap(self, context, location, normal, index, object, matrix):
	# Find nearest element for snap.
	location = object.matrix_world @ location
	BestLocation, tresh4, tresh5 = self.KDTreeSnap.find(location)

	# Find nearest direction.
	tresh1, BestDirection, tresh2, tresh3 = self.BVHTree.find_nearest(BestLocation)
	BestVertex, tresh4, tresh5 = self.KDTree.find(BestLocation)

	ToVertex = BestLocation
	FromVertex = BestVertex
	dvec = ToVertex - BestDirection
	dnormal =, BestDirection)
	SnapPoint = FromVertex + Vector(dnormal * BestDirection)
	SnapDistance = (FromVertex - SnapPoint).length

	if self.NormalMove:
		return SnapDistance
		return SnapPoint

def RayCast(self, event, context):
	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).normalized()
	ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
	ray_target = ray_origin + (view_vector * 10000)

	matrix = self.MainObject.matrix_world
	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

	result, location, normal, index = self.MainObject.ray_cast(
		ray_origin_obj, ray_direction_obj

	if result:
		value = Snap(self, context, location, normal, index, self.MainObject, matrix)
		if value is None:
			return GetMouseLocation(self, event, context) - self.StartMouseLocation
			return value
		return GetMouseLocation(self, event, context) - self.StartMouseLocation

def CreateBVHTree(self, context):
	bvh = BVHTree.FromObject(
	self.BVHTree = bvh
	size = len(
	kd = kdtree.KDTree(size)

	for i in
		kd.insert(self.ExtrudeObject.matrix_world @, i.index)

	self.KDTree = kd

	size = len(
	size2 = len(
	size3 = len(
	kd2 = kdtree.KDTree(size + size2 + size3)

	for i in
		kd2.insert(self.MainObject.matrix_world @, i.index)

	for i in
		pos = ([i.vertices[0]].co +[i.vertices[1]].co
		) / 2
		kd2.insert(self.MainObject.matrix_world @ pos, i.index + size)

	for i in
		kd2.insert(self.MainObject.matrix_world @, i.index + size + size2)

	self.KDTreeSnap = kd2

def CursorPosition(self, context, is_Set=False):
	if is_Set and self.CursorLocation != 'NONE':
		context.scene.cursor.location = self.CursorLocation
		bpy.context.scene.tool_settings.transform_pivot_point = self.PivotPoint
		self.CursorLocation = context.scene.cursor.location
		self.PivotPoint = context.scene.tool_settings.transform_pivot_point

def CreateNewObject(self, context):
	# Duplicate the object.
	self.ExtrudeObject = context.selected_objects[-1]

	# Clear modifiers.
	while len(self.ExtrudeObject.modifiers) != 0:

def GetVisualSetings(self, context, isSet=False):
	if isSet:
		context.active_object.show_all_edges = self.ShowAllEdges
		context.active_object.show_wire = self.ShowAllEdges
		self.ShowAllEdges = context.active_object.show_all_edges
		self.ShowAllEdges = context.active_object.show_wire

def SetVisualSetings(self, context):
	self.MainObject.show_all_edges = True
	self.MainObject.show_wire = True

	self.ExtrudeObject.display_type = 'WIRE'

def GetVisualModifiers(self, context, isSet=False):
	if isSet:
		for i in self.MainObject.modifiers:
			if in self.VisibilityModifiers:
				i.show_viewport = True
		for i in self.MainObject.modifiers:
			if i.show_viewport:
				i.show_viewport = False

def CreateModifier(self, context):
	# Set Boolean. = self.MainObject
	self.bool ='DestructiveBoolean', 'BOOLEAN')
	bpy.context.object.modifiers["DestructiveBoolean"].operation = 'DIFFERENCE'
	bpy.context.object.modifiers["DestructiveBoolean"].object = self.ExtrudeObject
	bpy.context.object.modifiers["DestructiveBoolean"].show_viewport = True
	# Set Solidify. = self.ExtrudeObject'DestructiveSolidify', 'SOLIDIFY')
	context.object.modifiers['DestructiveSolidify'].use_even_offset = True
	context.object.modifiers['DestructiveSolidify'].offset = -0.99959

def GetMouseLocation(self, event, context):
	region = bpy.context.region
	rv3d = bpy.context.region_data
	coord = event.mouse_region_x, event.mouse_region_y
	view_vector_mouse = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
	ray_origin_mouse = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
	V_a = ray_origin_mouse + view_vector_mouse
	V_b = rv3d.view_rotation @ Vector((0.0, 0.0, -1.0))
	pointLoc = intersect_line_plane(ray_origin_mouse, V_a, context.object.location, V_b)
	loc = (self.GeneralNormal @ pointLoc) * -1
	return loc

def SetSolidifyValue(self, context, value):
	self.ExtrudeObject.modifiers[-1].thickness = value

def CalculateNormal(self, context):
	for i in
		self.GeneralNormal += i.normal.copy()

def TransformObject(self, context):
	selObj = context.selected_objects
	self.ExtrudeObject.select_set(True) = self.ExtrudeObject
	bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)

	bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR'
	self.ExtrudeObject.scale = Vector((1.001, 1.001, 1.001))

	for i in selObj:
		i.select_set(True) = self.MainObject

def GetFaceNormal(self, context):
	for i in

def GetMainVertsIndex(self, context):
	for i in

def SetForAxis(self, context):
	GetMainVertsIndex(self, context) = self.ExtrudeObject
	for i in range(0, len(self.MainVertsIndex) - 1):
	index = []
	for f in
		normal = f.normal
		for v in f.vertices:
			if v not in index:[
				].co = normal * 0.02 +[v].co.copy()

	self.ExtrudeObject.modifiers[0].thickness = 0.00
	self.ExtrudeObject.modifiers[0].offset = 0
		apply_as='DATA', modifier=self.ExtrudeObject.modifiers[0].name
	for i in range(len(self.MainVertsIndex) - 1, len(
		self.StartVertsPos.append([i].co.copy()) = self.MainObject

def ReturnStartPosition(self, context):
	for i in range(len(self.MainVertsIndex) - 1, len([i].co = self.StartVertsPos[i]

def AxisMove(self, context, value):
	axis = Vector()
	if self.AxisMove == 'X':
		axis = Vector((-1.0, 0.0, 0.0))
	elif self.AxisMove == 'Y':
		axis = Vector((0.0, -1.0, 0.0))
	elif self.AxisMove == 'Z':
		axis = Vector((0.0, 0.0, -1.0))

	for i in range(len(self.MainVertsIndex), len(
		vertPos = ((axis * value) + self.StartVertsPos[i])[i].co = vertPos

def Cancel(self, context): = self.MainObject
	GetVisualSetings(self, context, True)
	GetVisualModifiers(self, context, True)

def Finish(self, context, BevelUpdate=False):
	rayCastFace = []
	if self.NormalMove: = self.ExtrudeObject
		GetMainVertsIndex(self, context)
			apply_as='DATA', modifier=self.ExtrudeObject.modifiers[0].name
		bpy.ops.object.mode_set(mode='OBJECT') = self.MainObject
	bpy.ops.object.modifier_apply(apply_as='DATA', modifier='DestructiveBoolean')
	context.active_object.update_tag(refresh={'OBJECT', 'DATA', 'TIME'})

	for f in
		faceCenter = self.ExtrudeObject.matrix_world @
		faceNormal = self.ExtrudeObject.matrix_world @ f.normal
		StartPoint = ((faceNormal * -1) * 0.003) + faceCenter

		center = self.MainObject.matrix_world.inverted() @ StartPoint
		normal = self.MainObject.matrix_world.inverted() @ faceNormal
		result, location, normal, index = self.MainObject.ray_cast(
			center, normal, distance=0.005
		if result:
			rayCastFace.append(index)[index].select = True

	bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR'
	bpy.ops.transform.resize(value=(1 - 0.001, 1 - 0.001, 1 - 0.001))
	bpy.context.scene.tool_settings.transform_pivot_point = 'MEDIAN_POINT'

	for f in = False

	for f in
		lose = False
		for v in f.vertices:
			if v in self.MainVertsIndex:
				lose = True
		if lose:
			faceCenter = self.ExtrudeObject.matrix_world @
			faceNormal = self.ExtrudeObject.matrix_world @ f.normal
			StartPoint = ((faceNormal * -1) * 0.003) + faceCenter

			center = self.MainObject.matrix_world.inverted() @ StartPoint
			normal = self.MainObject.matrix_world.inverted() @ faceNormal
			result, location, normal, index = self.MainObject.ray_cast(
				center, normal, distance=0.005
			if result:[index].select = True
	GetVisualSetings(self, context, True)
	GetVisualModifiers(self, context, True)
	bpy.ops.mesh.remove_doubles(threshold=0.001, use_unselected=True)

class ExtrudePull(bpy.types.Operator):
	bl_idname = "mesh.extrude_pull"
	bl_label = "Extrude Pull Geometry"
	bl_options = {"REGISTER", "UNDO", "GRAB_CURSOR", "BLOCKING"}
	bl_description = "Extrude unwanted geometry away"

	def poll(cls, context):
		# Disable for Vertex and Edge select mode.
		if tuple(bpy.context.tool_settings.mesh_select_mode) == (False, False, True):
			if[2] > 0:
				return (context.mode == "EDIT_MESH")
		return False

	def modal(self, context, event):
		if event.type == 'MOUSEMOVE':
			value = GetMouseLocation(self, event, context) - self.StartMouseLocation
			if self.NormalMove:
				SetSolidifyValue(self, context, value)
				AxisMove(self, context, value)

		if event.ctrl:
			value = RayCast(self, event, context)
			if self.NormalMove:
				SetSolidifyValue(self, context, value)
				AxisMove(self, context, value)

		if event.type == 'X':
			if self.NormalMove:
				SetForAxis(self, context)
				self.NormalMove = False
			ReturnStartPosition(self, context)
			self.AxisMove = 'X'

		if event.type == 'Y':
			if self.NormalMove:
				SetForAxis(self, context)
				self.NormalMove = False
			ReturnStartPosition(self, context)
			self.AxisMove = 'Y'

		if event.type == 'Z':
			if self.NormalMove:
				SetForAxis(self, context)
				self.NormalMove = False
			ReturnStartPosition(self, context)
			self.AxisMove = 'Z'

		if event.type == 'LEFTMOUSE':
			Finish(self, context, BevelUpdate=False)
			return {'FINISHED'}

		if event.type in {'RIGHTMOUSE', 'ESC'}:
			Cancel(self, context)
			return {'CANCELLED'}
		return {'RUNNING_MODAL'}

	def invoke(self, context, event):
		if context.space_data.type == 'VIEW_3D':
			self.KDTreeSnap = None
			self.KDTree = None
			self.BVHTree = None
			self.PivotPoint = None
			self.MainVertsIndex = []
			self.AxisMove = 'Z'
			self.StartVertsPos = []
			self.NormalMove = True
			self.GeneralNormal = Vector((0.0, 0.0, 0.0))
			self.FaceNormal = []
			self.ShowAllEdges = None
			self.ShowWire = None
			self.CursorLocation = None
			self.VisibilityModifiers = []
			self.MainObject = context.active_object
			self.ExtrudeObject = None
			self.SaveSelectFaceForCancel = None

			GetVisualModifiers(self, context)
			GetVisualSetings(self, context)
			CursorPosition(self, context)
			CreateNewObject(self, context)
			CreateBVHTree(self, context)
			CreateModifier(self, context)
			SetVisualSetings(self, context)
			TransformObject(self, context)
			CalculateNormal(self, context)
			self.StartMouseLocation = GetMouseLocation(self, event, context)
			# print('StartMouseLocation', self.StartMouseLocation)

			return {'RUNNING_MODAL'}
		else:{'WARNING'}, "The operator is not called in 3D Viewport.")
			return {'CANCELLED'}

classes = (ExtrudePull)

def operator_draw(self, context):
	layout = self.layout
	col = layout.column(align=True)
	self.layout.operator_context = 'INVOKE_REGION_WIN'
	col.operator("mesh.extrude_pull", text="Extrude Pull Geometry")

def register():

def unregister():

if __name__ == "__main__":

Thanks, partially works, but there are a lot of mistakes.

Edit: but for easy tasks it is working well.
Specially making holes through is really good.

But still it is very easy to make bad topology.

Did you make that one by yourself? (Don’t recognize the names in the info block)

No, I didn’t write the script.

But I just see that there is a new version (1.07).

It would be nice to get an updated video showing how the same features of the deprecated paid addon can be accessed in blender stock version if they have been merged. It’s a little confusing as it is right now

1 Like

Hopefully when the re-do the boolean operations in blender, that will fix the extrude issues as well…
Although Undo is a bigger issue for me than anything else… waiting 10-20-30 seconds per object undo.

That’s fixed for me not for all case but in the 2.83 beta , i can see big improvements in undo speed.
The last operation that are problematic are describe in their to do (undo duplicate object and delete are still slow in some case). But i can model on big object and undo modeling task as quick as i was on a low poly.

1 Like

Thank you for the awesome addon! It’s really useful.

Is there a way to remap hotkeys for Y/Z/X constraints?
I tried to change all the values from ‘Y’,‘Z’,‘X’ to ‘A’,'S,‘D’ in two files: and However it cause some errors.

Hello, I’m currently using this add-on in Blender 2.82, and I cannot apply textures to anything I make with it.

If I use the Add function in Edit mode to make a Plane, I can apply textures to it just fine with this method shown here:

If I use the Make Line tool to make almost the exact same plane (with the Create Faces function on), doing that method just results in weird colors and no visible texture.

Do I need to use a different method to apply textures with the Make Lines Tool?

The move tool from this addon is exactly what I have been looking for having come to Blender from CAD software. I see that the Snap Utilities addon is no longer available. Does anyone know of another addon that has similar functionality as the move tool (and perhaps the rotate tool)?

Hi Xayzer,
look at this

Thank you very much, @renderthings! This looks just like what I’m looking for!

1 Like


thanks for addon!
Can it make perpendicular cut from starting point? like it does to the destination edge

and option to cut only mesh will be very useful (to not make points in space)


Hi mano,
the addon-on looks great.
If I understood correctly, the full add-on will be intergrated into a future Blender release?
That’s the reason purchasing of the add-on is disabled on gumroad? Is there any way to test?


I can’t speak for @mano-wii. The line part of the add-on is integrated in Blender. You can activate it in the add-on page. It’s not perfect and is probably not developed anymore, but I use it often. The other parts are nowhere to be seen.

There is a similar add-on called Construction Lines that may or may not be new to you. It’s similar to the full CAD Snap Utilities and with version 0.9.5 that is soon to be released, Construction Lines will probably cover a lot of your needs in a easy way. You can find it in this forum on this page.

As for CAD Snap Utilities, I would love to see it being developed as a add-on or a part of Blender, but it does not seam lightly.