Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormano-wii <germano.costa@ig.com.br>2018-10-30 06:11:22 +0300
committermano-wii <germano.costa@ig.com.br>2018-10-30 06:11:22 +0300
commit7fb1a1ca26c519d12ffb3973f68fa50f306d7456 (patch)
tree1361e10460c179f353f2b85059b8446aaa6940c9 /mesh_snap_utilities_line
parent747c0a3731f10972eb373a119171d75a35b95b7c (diff)
Addon Snap Utilities Line: Update to work in blender2.8
Changes: - The operator follows the same style of the tools in the toolbar; - Now a new "Make Line" tool appears next to "Add Cube"; - The operator only works in edit mode; - The "create new object" option has been removed;
Diffstat (limited to 'mesh_snap_utilities_line')
-rw-r--r--mesh_snap_utilities_line/__init__.py103
-rw-r--r--mesh_snap_utilities_line/common_classes.py334
-rw-r--r--mesh_snap_utilities_line/common_utilities.py269
-rw-r--r--mesh_snap_utilities_line/icons/ops.mesh.make_line.datbin0 -> 1340 bytes
-rw-r--r--mesh_snap_utilities_line/ops_line.py551
-rw-r--r--mesh_snap_utilities_line/preferences.py125
-rw-r--r--mesh_snap_utilities_line/snap_context_l/__init__.py439
-rw-r--r--mesh_snap_utilities_line/snap_context_l/mesh_drawing.py412
-rw-r--r--mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl24
-rw-r--r--mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl25
-rw-r--r--mesh_snap_utilities_line/snap_context_l/utils_projection.py216
11 files changed, 2498 insertions, 0 deletions
diff --git a/mesh_snap_utilities_line/__init__.py b/mesh_snap_utilities_line/__init__.py
new file mode 100644
index 00000000..390cbcd0
--- /dev/null
+++ b/mesh_snap_utilities_line/__init__.py
@@ -0,0 +1,103 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Contact for more information about the Addon:
+# Email: germano.costa@ig.com.br
+# Twitter: wii_mano @mano_wii
+
+bl_info = {
+ "name": "Snap_Utilities_Line",
+ "author": "Germano Cavalcante",
+ "version": (5, 8, 22),
+ "blender": (2, 80, 0),
+ "location": "View3D > TOOLS > Snap Utilities > snap utilities",
+ "description": "Extends Blender Snap controls",
+ "wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities",
+ "category": "Mesh"}
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(preferences)
+ importlib.reload(ops_line)
+else:
+ from . import preferences
+ from . import ops_line
+
+import bpy
+from bpy.utils.toolsystem import ToolDef
+
+@ToolDef.from_fn
+def tool_make_line():
+ import os
+ def draw_settings(context, layout, tool):
+ addon_prefs = context.user_preferences.addons["mesh_snap_utilities_line"].preferences
+
+ layout.prop(addon_prefs, "incremental")
+ layout.prop(addon_prefs, "increments_grid")
+ if addon_prefs.increments_grid:
+ layout.prop(addon_prefs, "relative_scale")
+ layout.prop(addon_prefs, "create_face")
+ layout.prop(addon_prefs, "outer_verts")
+ #props = tool.operator_properties("mesh.snap_utilities_line")
+ #layout.prop(props, "radius")
+
+ icons_dir = os.path.join(os.path.dirname(__file__), "icons")
+
+ return dict(
+ text="Make Line",
+ description=(
+ "Make Lines\n"
+ "Connect them to split faces"
+ ),
+ icon=os.path.join(icons_dir, "ops.mesh.make_line"),
+ widget=None,
+ operator="mesh.make_line",
+ keymap=(
+ ("mesh.make_line", None, dict(type='ACTIONMOUSE', value='PRESS')),
+ ),
+ draw_settings=draw_settings,
+ )
+
+
+def get_tool_list(space_type, context_mode):
+ from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+ cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
+ return cls._tools[context_mode]
+
+def register():
+ bpy.utils.register_class(preferences.SnapUtilitiesLinePreferences)
+ bpy.utils.register_class(ops_line.SnapUtilitiesLine)
+
+ bpy.utils.register_tool('VIEW_3D', 'EDIT_MESH', tool_make_line)
+
+ # Move tool to after 'Add Cube'
+ tools = get_tool_list('VIEW_3D', 'EDIT_MESH')
+ for index, tool in enumerate(tools):
+ if isinstance(tool, ToolDef) and tool.text == "Add Cube":
+ break
+ tools.insert(index + 1, tools.pop(-1))
+
+def unregister():
+ bpy.utils.unregister_tool('VIEW_3D', 'EDIT_MESH', tool_make_line)
+
+ bpy.utils.unregister_class(ops_line.SnapUtilitiesLine)
+ bpy.utils.unregister_class(preferences.SnapUtilitiesLinePreferences)
+
+if __name__ == "__main__":
+ __name__ = "mesh_snap_utilities_line"
+ __package__ = "mesh_snap_utilities_line"
+ register()
diff --git a/mesh_snap_utilities_line/common_classes.py b/mesh_snap_utilities_line/common_classes.py
new file mode 100644
index 00000000..09fc3a02
--- /dev/null
+++ b/mesh_snap_utilities_line/common_classes.py
@@ -0,0 +1,334 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+import bgl
+import gpu
+import numpy as np
+
+
+class SnapDrawn():
+ def __init__(self, out_color, face_color,
+ edge_color, vert_color, center_color,
+ perpendicular_color, constrain_shift_color,
+ axis_x_color, axis_y_color, axis_z_color):
+
+ self.out_color = out_color
+ self.face_color = face_color
+ self.edge_color = edge_color
+ self.vert_color = vert_color
+ self.center_color = center_color
+ self.perpendicular_color = perpendicular_color
+ self.constrain_shift_color = constrain_shift_color
+
+ self.axis_x_color = axis_x_color
+ self.axis_y_color = axis_y_color
+ self.axis_z_color = axis_z_color
+
+ self._format_pos = gpu.types.GPUVertFormat()
+ self._format_pos.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+
+ self._format_pos_and_color = gpu.types.GPUVertFormat()
+ self._format_pos_and_color.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+ self._format_pos_and_color.attr_add(id="color", comp_type='F32', len=4, fetch_mode='FLOAT')
+
+ self._program_unif_col = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
+ self._program_smooth_col = gpu.shader.from_builtin("3D_SMOOTH_COLOR")
+
+ self._batch_point = None
+ self._batch_circle = None
+ self._batch_vector = None
+
+
+ def batch_line_strip_create(self, coords):
+ vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords))
+ vbo.attr_fill(0, data = coords)
+ batch_lines = gpu.types.GPUBatch(type = "LINE_STRIP", buf = vbo)
+ return batch_lines
+
+ def batch_lines_smooth_color_create(self, coords, colors):
+ vbo = gpu.types.GPUVertBuf(self._format_pos_and_color, len = len(coords))
+ vbo.attr_fill(0, data = coords)
+ vbo.attr_fill(1, data = colors)
+ batch_lines = gpu.types.GPUBatch(type = "LINES", buf = vbo)
+ return batch_lines
+
+ def batch_triangles_create(self, coords):
+ vbo = gpu.types.GPUVertBuf(self._format_pos, len = len(coords))
+ vbo.attr_fill(0, data = coords)
+ batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo)
+ return batch_tris
+
+ def batch_point_get(self):
+ if self._batch_point is None:
+ vbo = gpu.types.GPUVertBuf(self._format_pos, len = 1)
+ vbo.attr_fill(0, ((0.0, 0.0, 0.0),))
+ self._batch_point = gpu.types.GPUBatch(type = "POINTS", buf = vbo)
+ return self._batch_point
+
+ def draw(self, type, location, list_verts_co, vector_constrain, prevloc):
+ # draw 3d point OpenGL in the 3D View
+ bgl.glEnable(bgl.GL_BLEND)
+ gpu.matrix.push()
+ self._program_unif_col.bind()
+
+ if list_verts_co:
+ # draw 3d line OpenGL in the 3D View
+ bgl.glDepthRange(0, 0.9999)
+ bgl.glLineWidth(3.0)
+
+ batch = self.batch_line_strip_create([v.to_tuple() for v in list_verts_co] + [location.to_tuple()])
+
+ self._program_unif_col.uniform_float("color", (1.0, 0.8, 0.0, 0.5))
+ batch.draw(self._program_unif_col)
+ del batch
+
+ bgl.glDisable(bgl.GL_DEPTH_TEST)
+
+ point_batch = self.batch_point_get()
+ if vector_constrain:
+ if prevloc:
+ bgl.glPointSize(5)
+ gpu.matrix.translate(prevloc)
+ self._program_unif_col.uniform_float("color", (1.0, 1.0, 1.0, 0.5))
+ point_batch.draw(self._program_unif_col)
+ gpu.matrix.translate(-prevloc)
+
+ if vector_constrain[2] == 'X':
+ Color4f = self.axis_x_color
+ elif vector_constrain[2] == 'Y':
+ Color4f = self.axis_y_color
+ elif vector_constrain[2] == 'Z':
+ Color4f = self.axis_z_color
+ else:
+ Color4f = self.constrain_shift_color
+ else:
+ if type == 'OUT':
+ Color4f = self.out_color
+ elif type == 'FACE':
+ Color4f = self.face_color
+ elif type == 'EDGE':
+ Color4f = self.edge_color
+ elif type == 'VERT':
+ Color4f = self.vert_color
+ elif type == 'CENTER':
+ Color4f = self.center_color
+ elif type == 'PERPENDICULAR':
+ Color4f = self.perpendicular_color
+ else: # type == None
+ Color4f = self.out_color
+
+ bgl.glPointSize(10)
+
+ gpu.matrix.translate(location)
+ self._program_unif_col.uniform_float("color", Color4f)
+ point_batch.draw(self._program_unif_col)
+
+ # restore opengl defaults
+ bgl.glDepthRange(0.0, 1.0)
+ bgl.glPointSize(1.0)
+ bgl.glLineWidth(1.0)
+ bgl.glEnable(bgl.GL_DEPTH_TEST)
+ bgl.glDisable(bgl.GL_BLEND)
+
+ gpu.matrix.pop()
+
+ def draw_elem(self, snap_obj, bm, elem):
+ from bmesh.types import(
+ BMVert,
+ BMEdge,
+ BMFace,
+ )
+ # draw 3d point OpenGL in the 3D View
+ bgl.glEnable(bgl.GL_BLEND)
+ bgl.glDisable(bgl.GL_DEPTH_TEST)
+
+ gpu.matrix.push()
+ gpu.matrix.multiply_matrix(snap_obj.mat)
+
+ if isinstance(elem, BMVert):
+ color = self.vert_color
+ edges = np.empty((len(elem.link_edges), 2), [("pos", "f4", 3), ("color", "f4", 4)])
+ edges["pos"][:, 0] = elem.co
+ edges["pos"][:, 1] = [e.other_vert(elem).co for e in elem.link_edges]
+ edges["color"][:, 0] = color
+ edges["color"][:, 1] = (color[0], color[1], color[2], 0.0)
+ edges.shape = -1
+
+ self._program_smooth_col.bind()
+ bgl.glLineWidth(3.0)
+ batch = self.batch_lines_smooth_color_create(edges["pos"], edges["color"])
+ batch.draw(self._program_smooth_col)
+ bgl.glLineWidth(1.0)
+ else:
+ self._program_unif_col.bind()
+
+ if isinstance(elem, BMEdge):
+ self._program_unif_col.uniform_float("color", self.edge_color)
+
+ bgl.glLineWidth(3.0)
+ batch = self.batch_line_strip_create([v.co for v in elem.verts])
+ batch.draw(self._program_unif_col)
+ bgl.glLineWidth(1.0)
+
+ elif isinstance(elem, BMFace):
+ face_color = self.face_color[0], self.face_color[1], self.face_color[2], self.face_color[3] * 0.2
+ self._program_unif_col.uniform_float("color", face_color)
+
+ tris = snap_obj.data[1].get_loop_tri_co_by_bmface(bm, elem)
+ tris.shape = (-1, 3)
+ batch = self.batch_triangles_create(tris)
+ batch.draw(self._program_unif_col)
+
+ # restore opengl defaults
+ bgl.glEnable(bgl.GL_DEPTH_TEST)
+ bgl.glDisable(bgl.GL_BLEND)
+
+ gpu.matrix.pop()
+
+
+class SnapNavigation():
+ @staticmethod
+ def debug_key(key):
+ for member in dir(key):
+ print(member, getattr(key, member))
+
+ @staticmethod
+ def convert_to_flag(shift, ctrl, alt):
+ return (shift << 0) | (ctrl << 1) | (alt << 2)
+
+ def __init__(self, context, use_ndof):
+ # TO DO:
+ # 'View Orbit', 'View Pan', 'NDOF Orbit View', 'NDOF Pan View'
+ self.use_ndof = use_ndof and context.user_preferences.inputs.use_ndof
+
+ self._rotate = set()
+ self._move = set()
+ self._zoom = set()
+
+ if self.use_ndof:
+ self._ndof_all = set()
+ self._ndof_orbit = set()
+ self._ndof_orbit_zoom = set()
+ self._ndof_pan = set()
+
+ for key in context.window_manager.keyconfigs.user.keymaps['3D View'].keymap_items:
+ if key.idname == 'view3d.rotate':
+ self._rotate.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
+ elif key.idname == 'view3d.move':
+ self._move.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value))
+ elif key.idname == 'view3d.zoom':
+ if key.type == 'WHEELINMOUSE':
+ self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELUPMOUSE', key.value, key.properties.delta))
+ elif key.type == 'WHEELOUTMOUSE':
+ self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), 'WHEELDOWNMOUSE', key.value, key.properties.delta))
+ else:
+ self._zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type, key.value, key.properties.delta))
+
+ elif self.use_ndof:
+ if key.idname == 'view3d.ndof_all':
+ self._ndof_all.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+ elif key.idname == 'view3d.ndof_orbit':
+ self._ndof_orbit.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+ elif key.idname == 'view3d.ndof_orbit_zoom':
+ self._ndof_orbit_zoom.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+ elif key.idname == 'view3d.ndof_pan':
+ self._ndof_pan.add((self.convert_to_flag(key.shift, key.ctrl, key.alt), key.type))
+
+
+ def run(self, context, event, snap_location):
+ evkey = (self.convert_to_flag(event.shift, event.ctrl, event.alt), event.type, event.value)
+
+ if evkey in self._rotate:
+ bpy.ops.view3d.rotate('INVOKE_DEFAULT')
+ return True
+
+ if evkey in self._move:
+ #if event.shift and self.vector_constrain and \
+ # self.vector_constrain[2] in {'RIGHT_SHIFT', 'LEFT_SHIFT', 'shift'}:
+ # self.vector_constrain = None
+ bpy.ops.view3d.move('INVOKE_DEFAULT')
+ return True
+
+ for key in self._zoom:
+ if evkey == key[0:3]:
+ if snap_location:
+ v3d = context.space_data
+ dist_range = (v3d.clip_start, v3d.clip_end)
+ rv3d = context.region_data
+ if (key[3] < 0 and rv3d.view_distance < dist_range[1]) or\
+ (key[3] > 0 and rv3d.view_distance > dist_range[0]):
+ rv3d.view_location += key[3] * (snap_location - rv3d.view_location) / 6
+ rv3d.view_distance -= key[3] * rv3d.view_distance / 6
+ context.area.tag_redraw()
+ else:
+ bpy.ops.view3d.zoom('INVOKE_DEFAULT', delta = key[3])
+ return True
+
+ if self.use_ndof:
+ ndofkey = evkey[:2]
+ if evkey in self._ndof_all:
+ bpy.ops.view3d.ndof_all('INVOKE_DEFAULT')
+ return True
+ if evkey in self._ndof_orbit:
+ bpy.ops.view3d.ndof_orbit('INVOKE_DEFAULT')
+ return True
+ if evkey in self._ndof_orbit_zoom:
+ bpy.ops.view3d.ndof_orbit_zoom('INVOKE_DEFAULT')
+ return True
+ if evkey in self._ndof_pan:
+ bpy.ops.view3d.ndof_pan('INVOKE_DEFAULT')
+ return True
+
+ return False
+
+
+class CharMap:
+ ascii = {
+ ".", ",", "-", "+", "1", "2", "3",
+ "4", "5", "6", "7", "8", "9", "0",
+ "c", "m", "d", "k", "h", "a",
+ " ", "/", "*", "'", "\""
+ # "="
+ }
+ type = {
+ 'BACK_SPACE', 'DEL',
+ 'LEFT_ARROW', 'RIGHT_ARROW'
+ }
+
+ @staticmethod
+ def modal(self, context, event):
+ c = event.ascii
+ if c:
+ if c == ",":
+ c = "."
+ self.length_entered = self.length_entered[:self.line_pos] + c + self.length_entered[self.line_pos:]
+ self.line_pos += 1
+ if self.length_entered:
+ if event.type == 'BACK_SPACE':
+ self.length_entered = self.length_entered[:self.line_pos - 1] + self.length_entered[self.line_pos:]
+ self.line_pos -= 1
+
+ elif event.type == 'DEL':
+ self.length_entered = self.length_entered[:self.line_pos] + self.length_entered[self.line_pos + 1:]
+
+ elif event.type == 'LEFT_ARROW':
+ self.line_pos = (self.line_pos - 1) % (len(self.length_entered) + 1)
+
+ elif event.type == 'RIGHT_ARROW':
+ self.line_pos = (self.line_pos + 1) % (len(self.length_entered) + 1)
+
diff --git a/mesh_snap_utilities_line/common_utilities.py b/mesh_snap_utilities_line/common_utilities.py
new file mode 100644
index 00000000..2fc0ce6d
--- /dev/null
+++ b/mesh_snap_utilities_line/common_utilities.py
@@ -0,0 +1,269 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+#python tip: from-imports don't save memory.
+#They execute and cache the entire module just like a regular import.
+
+import bpy
+import bmesh
+from .snap_context_l import SnapContext
+from mathutils import Vector
+from mathutils.geometry import (
+ intersect_point_line,
+ intersect_line_line,
+ intersect_line_plane,
+ intersect_ray_tri,
+ )
+
+
+def get_units_info(scale, unit_system, separate_units):
+ if unit_system == 'METRIC':
+ scale_steps = ((1000, 'km'), (1, 'm'), (1 / 100, 'cm'),
+ (1 / 1000, 'mm'), (1 / 1000000, '\u00b5m'))
+ elif unit_system == 'IMPERIAL':
+ scale_steps = ((5280, 'mi'), (1, '\''),
+ (1 / 12, '"'), (1 / 12000, 'thou'))
+ scale /= 0.3048 # BU to feet
+ else:
+ scale_steps = ((1, ' BU'),)
+ separate_units = False
+
+ return (scale, scale_steps, separate_units)
+
+
+def convert_distance(val, units_info, precision=5):
+ scale, scale_steps, separate_units = units_info
+ sval = val * scale
+ idx = 0
+ while idx < len(scale_steps) - 1:
+ if sval >= scale_steps[idx][0]:
+ break
+ idx += 1
+ factor, suffix = scale_steps[idx]
+ sval /= factor
+ if not separate_units or idx == len(scale_steps) - 1:
+ dval = str(round(sval, precision)) + suffix
+ else:
+ ival = int(sval)
+ dval = str(round(ival, precision)) + suffix
+ fval = sval - ival
+ idx += 1
+ while idx < len(scale_steps):
+ fval *= scale_steps[idx - 1][0] / scale_steps[idx][0]
+ if fval >= 1:
+ dval += ' ' \
+ + ("%.1f" % fval) \
+ + scale_steps[idx][1]
+ break
+ idx += 1
+
+ return dval
+
+
+def location_3d_to_region_2d(region, rv3d, coord):
+ prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
+ width_half = region.width / 2.0
+ height_half = region.height / 2.0
+ return Vector((width_half + width_half * (prj.x / prj.w),
+ height_half + height_half * (prj.y / prj.w),
+ prj.z / prj.w
+ ))
+
+
+def out_Location(rv3d, orig, vector):
+ view_matrix = rv3d.view_matrix
+ v1 = (int(view_matrix[0][0]*1.5), int(view_matrix[0][1]*1.5), int(view_matrix[0][2]*1.5))
+ v2 = (int(view_matrix[1][0]*1.5), int(view_matrix[1][1]*1.5), int(view_matrix[1][2]*1.5))
+
+ hit = intersect_ray_tri((1,0,0), (0,1,0), (0,0,0), (vector), (orig), False)
+ if hit is None:
+ hit = intersect_ray_tri(v1, v2, (0,0,0), (vector), (orig), False)
+ if hit is None:
+ hit = intersect_ray_tri(v1, v2, (0,0,0), (-vector), (orig), False)
+ if hit is None:
+ hit = Vector()
+ return hit
+
+
+def get_snap_bm_geom(sctx, main_snap_obj, mcursor):
+
+ r_snp_obj, r_loc, r_elem, r_elem_co = sctx.snap_get(mcursor, main_snap_obj)
+ r_view_vector, r_orig = sctx.last_ray
+ r_bm = None
+ r_bm_geom = None
+
+ if r_snp_obj is not None:
+ obj = r_snp_obj.data[0]
+
+ if obj.type == 'MESH' and obj.data.is_editmode:
+ r_bm = bmesh.from_edit_mesh(obj.data)
+ if len(r_elem) == 1:
+ r_bm_geom = r_bm.verts[r_elem[0]]
+
+ elif len(r_elem) == 2:
+ try:
+ v1 = r_bm.verts[r_elem[0]]
+ v2 = r_bm.verts[r_elem[1]]
+ r_bm_geom = r_bm.edges.get([v1, v2])
+ except IndexError:
+ r_bm.verts.ensure_lookup_table()
+
+ elif len(r_elem) == 3:
+ tri = [
+ r_bm.verts[r_elem[0]],
+ r_bm.verts[r_elem[1]],
+ r_bm.verts[r_elem[2]],
+ ]
+
+ faces = set(tri[0].link_faces).intersection(tri[1].link_faces, tri[2].link_faces)
+ if len(faces) == 1:
+ r_bm_geom = faces.pop()
+ else:
+ i = -2
+ edge = None
+ while not edge and i != 1:
+ edge = r_bm.edges.get([tri[i], tri[i + 1]])
+ i += 1
+ if edge:
+ for l in edge.link_loops:
+ if l.link_loop_next.vert == tri[i] or l.link_loop_prev.vert == tri[i - 2]:
+ r_bm_geom = l.face
+ break
+
+ return r_snp_obj, r_loc, r_elem, r_elem_co, r_view_vector, r_orig, r_bm, r_bm_geom
+
+
+class SnapCache:
+ snp_obj = None
+ elem = None
+
+ v0 = None
+ v1 = None
+ vmid = None
+ vperp = None
+
+ v2d0 = None
+ v2d1 = None
+ v2dmid = None
+ v2dperp = None
+
+ is_increment = False
+
+
+def snap_utilities(
+ sctx, main_snap_obj,
+ mcursor,
+ constrain = None,
+ previous_vert = None,
+ increment = 0.0):
+
+ snp_obj, loc, elem, elem_co, view_vector, orig, bm, bm_geom = get_snap_bm_geom(sctx, main_snap_obj, mcursor)
+
+ is_increment = False
+ r_loc = None
+ r_type = None
+ r_len = 0.0
+
+ if not snp_obj:
+ is_increment = True
+ if constrain:
+ end = orig + view_vector
+ t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
+ if t_loc is None:
+ t_loc = constrain
+ r_loc = t_loc[0]
+ else:
+ r_type = 'OUT'
+ r_loc = out_Location(sctx.rv3d, orig, view_vector)
+
+ elif len(elem) == 1:
+ r_type = 'VERT'
+ if constrain:
+ r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
+ else:
+ r_loc = loc
+
+ elif len(elem) == 2:
+ if SnapCache.snp_obj is not snp_obj or not (elem == SnapCache.elem).all():
+ SnapCache.snp_obj = snp_obj
+ SnapCache.elem = elem
+
+ SnapCache.v0 = elem_co[0]
+ SnapCache.v1 = elem_co[1]
+ SnapCache.vmid = 0.5 * (SnapCache.v0 + SnapCache.v1)
+ SnapCache.v2d0 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v0)
+ SnapCache.v2d1 = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.v1)
+ SnapCache.v2dmid = location_3d_to_region_2d(sctx.region, sctx.rv3d, SnapCache.vmid)
+
+ if previous_vert and (not bm_geom or previous_vert not in bm_geom.verts):
+ pvert_co = main_snap_obj.mat @ previous_vert.co
+ perp_point = intersect_point_line(pvert_co, SnapCache.v0, SnapCache.v1)
+ SnapCache.vperp = perp_point[0]
+ #factor = point_perpendicular[1]
+ SnapCache.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0])
+ SnapCache.is_increment = False
+ else:
+ SnapCache.is_increment = True
+
+ #else: SnapCache.v2dperp = None
+
+ if constrain:
+ t_loc = intersect_line_line(constrain[0], constrain[1], SnapCache.v0, SnapCache.v1)
+
+ if t_loc is None:
+ is_increment = True
+ end = orig + view_vector
+ t_loc = intersect_line_line(constrain[0], constrain[1], orig, end)
+ r_loc = t_loc[0]
+
+ elif SnapCache.v2dperp and\
+ abs(SnapCache.v2dperp[0] - mcursor[0]) < 10 and abs(SnapCache.v2dperp[1] - mcursor[1]) < 10:
+ r_type = 'PERPENDICULAR'
+ r_loc = SnapCache.vperp
+
+ elif abs(SnapCache.v2dmid[0] - mcursor[0]) < 10 and abs(SnapCache.v2dmid[1] - mcursor[1]) < 10:
+ r_type = 'CENTER'
+ r_loc = SnapCache.vmid
+
+ else:
+ is_increment = SnapCache.is_increment
+
+ r_type = 'EDGE'
+ r_loc = loc
+
+ elif len(elem) == 3:
+ r_type = 'FACE'
+
+ if constrain:
+ is_increment = False
+ r_loc = intersect_point_line(loc, constrain[0], constrain[1])[0]
+ else:
+ is_increment = True
+ r_loc = loc
+
+ if previous_vert:
+ pv_co = main_snap_obj.mat @ previous_vert.co
+ vec = r_loc - pv_co
+ if is_increment and increment:
+ r_len = round((1 / increment) * vec.length) * increment
+ r_loc = r_len * vec.normalized() + pv_co
+ else:
+ r_len = vec.length
+
+ return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len
+
+snap_utilities.cache = SnapCache
diff --git a/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat
new file mode 100644
index 00000000..fa738db9
--- /dev/null
+++ b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat
Binary files differ
diff --git a/mesh_snap_utilities_line/ops_line.py b/mesh_snap_utilities_line/ops_line.py
new file mode 100644
index 00000000..427b0b30
--- /dev/null
+++ b/mesh_snap_utilities_line/ops_line.py
@@ -0,0 +1,551 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy, bmesh
+
+from bpy.props import FloatProperty
+
+from mathutils import Vector
+
+from mathutils.geometry import intersect_point_line
+
+from .common_classes import (
+ SnapDrawn,
+ CharMap,
+ SnapNavigation,
+ )
+
+from .common_utilities import (
+ get_units_info,
+ convert_distance,
+ snap_utilities,
+ )
+
+if not __package__:
+ __package__ = "mesh_snap_utilities"
+
+
+def Bpy_Area_header_text_clear(area):
+ #HACK
+ import ctypes
+ func = area.header_text_set
+ c_func = ctypes.c_void_p.from_address(id(func) + 56).value
+ c_param = ctypes.c_void_p.from_address(c_func + 24).value
+ flag_parameter = ctypes.c_void_p.from_address(c_param + 40)
+ previous_value = flag_parameter.value
+ flag_parameter.value = 0
+ func()
+ flag_parameter.value = previous_value
+
+
+def get_closest_edge(bm, point, dist):
+ r_edge = None
+ for edge in bm.edges:
+ v1 = edge.verts[0].co
+ v2 = edge.verts[1].co
+ # Test the BVH (AABB) first
+ for i in range(3):
+ if v1[i] <= v2[i]:
+ isect = v1[i] - dist <= point[i] <= v2[i] + dist
+ else:
+ isect = v2[i] - dist <= point[i] <= v1[i] + dist
+
+ if not isect:
+ break
+ else:
+ ret = intersect_point_line(point, v1, v2)
+
+ if ret[1] < 0.0:
+ tmp = v1
+ elif ret[1] > 1.0:
+ tmp = v2
+ else:
+ tmp = ret[0]
+
+ new_dist = (point - tmp).length
+ if new_dist <= dist:
+ dist = new_dist
+ r_edge = edge
+
+ return r_edge
+
+
+def get_loose_linked_edges(bmvert):
+ linked = [e for e in bmvert.link_edges if not e.link_faces]
+ for e in linked:
+ linked += [le for v in e.verts if not v.link_faces for le in v.link_edges if le not in linked]
+ return linked
+
+
+def draw_line(self, bm_geom, location):
+ obj = self.main_snap_obj.data[0]
+ bm = self.main_bm
+ split_faces = set()
+
+ drawing_is_dirt = False
+ update_edit_mesh = False
+
+ if bm_geom is None:
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+
+ elif isinstance(bm_geom, bmesh.types.BMVert):
+ if (bm_geom.co - location).length_squared < .001:
+ if self.list_verts == [] or self.list_verts[-1] != bm_geom:
+ self.list_verts.append(bm_geom)
+ else:
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ drawing_is_dirt = True
+
+ elif isinstance(bm_geom, bmesh.types.BMEdge):
+ self.list_edges.append(bm_geom)
+ ret = intersect_point_line(location, bm_geom.verts[0].co, bm_geom.verts[1].co)
+
+ if (ret[0] - location).length_squared < .001:
+ if ret[1] == 0.0:
+ vert = bm_geom.verts[0]
+ elif ret[1] == 1.0:
+ vert = bm_geom.verts[1]
+ else:
+ edge, vert = bmesh.utils.edge_split(bm_geom, bm_geom.verts[0], ret[1])
+ drawing_is_dirt = True
+ self.list_verts.append(vert)
+ self.geom = vert # hack to highlight in the drawing
+ # self.list_edges.append(edge)
+
+ else: # constrain point is near
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ drawing_is_dirt = True
+
+ elif isinstance(bm_geom, bmesh.types.BMFace):
+ split_faces.add(bm_geom)
+ vert = bm.verts.new(location)
+ self.list_verts.append(vert)
+ drawing_is_dirt = True
+
+ # draw, split and create face
+ if len(self.list_verts) >= 2:
+ v1, v2 = self.list_verts[-2:]
+ # v2_link_verts = [x for y in [a.verts for a in v2.link_edges] for x in y if x != v2]
+ edge = bm.edges.get([v1, v2])
+ if edge:
+ self.list_edges.append(edge)
+
+ else: # if v1 not in v2_link_verts:
+ if not v2.link_edges:
+ edge = bm.edges.new([v1, v2])
+ self.list_edges.append(edge)
+ drawing_is_dirt = True
+ else: # split face
+ v1_link_faces = v1.link_faces
+ v2_link_faces = v2.link_faces
+ if v1_link_faces and v2_link_faces:
+ split_faces.update(set(v1_link_faces).intersection(v2_link_faces))
+
+ else:
+ if v1_link_faces:
+ faces = v1_link_faces
+ co2 = v2.co.copy()
+ else:
+ faces = v2_link_faces
+ co2 = v1.co.copy()
+
+ for face in faces:
+ if bmesh.geometry.intersect_face_point(face, co2):
+ co = co2 - face.calc_center_median()
+ if co.dot(face.normal) < 0.001:
+ split_faces.add(face)
+
+ if split_faces:
+ edge = bm.edges.new([v1, v2])
+ self.list_edges.append(edge)
+ ed_list = get_loose_linked_edges(v2)
+ for face in split_faces:
+ facesp = bmesh.utils.face_split_edgenet(face, ed_list)
+ del split_faces
+ update_edit_mesh = True
+ else:
+ if self.intersect:
+ facesp = bmesh.ops.connect_vert_pair(bm, verts=[v1, v2], verts_exclude=bm.verts)
+ # print(facesp)
+ if not self.intersect or not facesp['edges']:
+ edge = bm.edges.new([v1, v2])
+ self.list_edges.append(edge)
+ drawing_is_dirt = True
+ else:
+ for edge in facesp['edges']:
+ self.list_edges.append(edge)
+ update_edit_mesh = True
+
+ # create face
+ if self.create_face:
+ ed_list = set(self.list_edges)
+ for edge in v2.link_edges:
+ for vert in edge.verts:
+ if vert != v2 and vert in self.list_verts:
+ ed_list.add(edge)
+ break
+ else:
+ continue
+ # Inner loop had a break, break the outer
+ break
+
+ ed_list.update(get_loose_linked_edges(v2))
+
+ bmesh.ops.edgenet_fill(bm, edges=list(ed_list))
+ update_edit_mesh = True
+ # print('face created')
+ if update_edit_mesh or drawing_is_dirt:
+ obj.data.update_gpu_tag()
+ obj.data.update_tag()
+ obj.update_from_editmode()
+ obj.update_tag()
+ bmesh.update_edit_mesh(obj.data)
+ self.sctx.tag_update_drawn_snap_object(self.main_snap_obj)
+ #bm.verts.index_update()
+
+ return [obj.matrix_world @ v.co for v in self.list_verts]
+
+
+class SnapUtilitiesLine(bpy.types.Operator):
+ """Make Lines. Connect them to split faces"""
+ bl_idname = "mesh.make_line"
+ bl_label = "Line Tool"
+ bl_options = {'REGISTER'}
+
+ constrain_keys = {
+ 'X': Vector((1,0,0)),
+ 'Y': Vector((0,1,0)),
+ 'Z': Vector((0,0,1)),
+ 'RIGHT_SHIFT': 'shift',
+ 'LEFT_SHIFT': 'shift',
+ }
+
+ def modal(self, context, event):
+ if self.navigation_ops.run(context, event, self.prevloc if self.vector_constrain else self.location):
+ return {'RUNNING_MODAL'}
+
+ context.area.tag_redraw()
+
+ if event.ctrl and event.type == 'Z' and event.value == 'PRESS':
+ del self.bm
+ del self.main_bm
+ bpy.ops.ed.undo()
+ self.vector_constrain = None
+ self.list_verts_co = []
+ self.list_verts = []
+ self.list_edges = []
+ bpy.ops.object.mode_set(mode='EDIT') # just to be sure
+ self.main_bm = bmesh.from_edit_mesh(self.main_snap_obj.data[0])
+ self.sctx.tag_update_drawn_snap_object(self.main_snap_obj)
+ return {'RUNNING_MODAL'}
+
+ is_making_lines = bool(self.list_verts_co)
+
+ if event.type == 'MOUSEMOVE' or self.bool_update:
+ if self.rv3d.view_matrix != self.rotMat:
+ self.rotMat = self.rv3d.view_matrix.copy()
+ self.bool_update = True
+ snap_utilities.cache.snp_obj = None # hack for snap edge elemens update
+ else:
+ self.bool_update = False
+
+ mval = Vector((event.mouse_region_x, event.mouse_region_y))
+
+ self.snap_obj, self.prevloc, self.location, self.type, self.bm, self.geom, self.len = snap_utilities(
+ self.sctx,
+ self.main_snap_obj,
+ mval,
+ constrain=self.vector_constrain,
+ previous_vert=(self.list_verts[-1] if self.list_verts else None),
+ increment=self.incremental
+ )
+
+ if self.snap_to_grid and self.type == 'OUT':
+ loc = self.location / self.rd
+ self.location = Vector((round(loc.x),
+ round(loc.y),
+ round(loc.z))) * self.rd
+
+ if self.keyf8 and is_making_lines:
+ lloc = self.list_verts_co[-1]
+ view_vec, orig = self.sctx.last_ray
+ location = intersect_point_line(lloc, orig, (orig + view_vec))
+ vec = (location[0] - lloc)
+ ax, ay, az = abs(vec.x), abs(vec.y), abs(vec.z)
+ vec.x = ax > ay > az or ax > az > ay
+ vec.y = ay > ax > az or ay > az > ax
+ vec.z = az > ay > ax or az > ax > ay
+ if vec == Vector():
+ self.vector_constrain = None
+ else:
+ vc = lloc + vec
+ try:
+ if vc != self.vector_constrain[1]:
+ type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
+ self.vector_constrain = [lloc, vc, type]
+ except:
+ type = 'X' if vec.x else 'Y' if vec.y else 'Z' if vec.z else 'shift'
+ self.vector_constrain = [lloc, vc, type]
+
+ if event.value == 'PRESS':
+ if is_making_lines and (event.ascii in CharMap.ascii or event.type in CharMap.type):
+ CharMap.modal(self, context, event)
+
+ elif event.type in self.constrain_keys:
+ self.bool_update = True
+ self.keyf8 = False
+
+ if self.vector_constrain and self.vector_constrain[2] == event.type:
+ self.vector_constrain = ()
+
+ else:
+ if event.shift:
+ if isinstance(self.geom, bmesh.types.BMEdge):
+ if is_making_lines:
+ loc = self.list_verts_co[-1]
+ self.vector_constrain = (loc, loc + self.geom.verts[1].co -
+ self.geom.verts[0].co, event.type)
+ else:
+ self.vector_constrain = [self.main_snap_obj.mat @ v.co for
+ v in self.geom.verts] + [event.type]
+ else:
+ if is_making_lines:
+ loc = self.list_verts_co[-1]
+ else:
+ loc = self.location
+ self.vector_constrain = [loc, loc + self.constrain_keys[event.type], event.type]
+
+ elif event.type == 'LEFTMOUSE':
+ if not is_making_lines and self.bm:
+ self.main_snap_obj = self.snap_obj
+ self.main_bm = self.bm
+
+ mat_inv = self.main_snap_obj.mat.inverted_safe()
+ point = mat_inv @ self.location
+ # with constraint the intersection can be in a different element of the selected one
+ geom2 = self.geom
+ if geom2:
+ geom2.select = False
+
+ if self.vector_constrain:
+ geom2 = get_closest_edge(self.main_bm, point, .001)
+
+ self.vector_constrain = None
+ self.list_verts_co = draw_line(self, geom2, point)
+ bpy.ops.ed.undo_push(message="Undo draw line*")
+
+ elif event.type == 'TAB':
+ self.keytab = self.keytab is False
+ if self.keytab:
+ self.sctx.set_snap_mode(False, False, True)
+ context.tool_settings.mesh_select_mode = (False, False, True)
+ else:
+ self.sctx.set_snap_mode(True, True, True)
+ context.tool_settings.mesh_select_mode = (True, True, self.snap_face)
+
+ elif event.type == 'F8':
+ self.vector_constrain = None
+ self.keyf8 = self.keyf8 is False
+
+ elif event.value == 'RELEASE':
+ if event.type in {'RET', 'NUMPAD_ENTER'}:
+ if self.length_entered != "" and self.list_verts_co:
+ try:
+ text_value = bpy.utils.units.to_value(self.unit_system, 'LENGTH', self.length_entered)
+ vector = (self.location - self.list_verts_co[-1]).normalized()
+ location = (self.list_verts_co[-1] + (vector * text_value))
+
+ mat_inv = self.main_snap_obj.mat.inverted_safe()
+ self.list_verts_co = draw_line(self, self.geom, mat_inv @ location)
+ self.length_entered = ""
+ self.vector_constrain = None
+
+ except: # ValueError:
+ self.report({'INFO'}, "Operation not supported yet")
+
+ elif event.type in {'RIGHTMOUSE', 'ESC'}:
+ if not is_making_lines or event.type == 'ESC':
+ del self.main_bm #avoids unpredictable crashs
+ del self.bm
+ del self.list_edges
+ del self.list_verts
+ del self.list_verts_co
+
+ bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
+ Bpy_Area_header_text_clear(context.area)
+ #context.area.header_text_set(text = "")
+ self.sctx.free()
+ del self.draw_cache
+
+ #Restore initial state
+ context.tool_settings.mesh_select_mode = self.select_mode
+ context.user_preferences.view.use_rotate_around_active = self.use_rotate_around_active
+ context.space_data.overlay.show_face_center = self.show_face_center
+ if not self.is_editmode:
+ bpy.ops.object.editmode_toggle()
+
+ return {'FINISHED'}
+ else:
+ snap_utilities.cache.snp_obj = None # hack for snap edge elemens update
+ self.vector_constrain = None
+ self.list_edges = []
+ self.list_verts = []
+ self.list_verts_co = []
+
+ a = ""
+ if is_making_lines:
+ if self.length_entered:
+ pos = self.line_pos
+ a = 'length: ' + self.length_entered[:pos] + '|' + self.length_entered[pos:]
+ else:
+ length = self.len
+ length = convert_distance(length, self.uinfo)
+ a = 'length: ' + length
+
+ context.area.header_text_set(text = "hit: %.3f %.3f %.3f %s" % (*self.location, a))
+
+ if True or is_making_lines:
+ return {'RUNNING_MODAL'}
+
+ return {'PASS_THROUGH'}
+
+ def draw_callback_px(self):
+ if self.bm:
+ self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom)
+ self.draw_cache.draw(self.type, self.location, self.list_verts_co, self.vector_constrain, self.prevloc)
+
+ def invoke(self, context, event):
+ if context.space_data.type == 'VIEW_3D':
+ # print('name', __name__, __package__)
+ preferences = context.user_preferences.addons[__package__].preferences
+
+ #Store the preferences that will be used in modal
+ self.intersect = preferences.intersect
+ self.create_face = preferences.create_face
+ self.outer_verts = preferences.outer_verts
+ self.snap_to_grid = preferences.increments_grid
+
+ self.draw_cache = SnapDrawn(
+ preferences.out_color,
+ preferences.face_color,
+ preferences.edge_color,
+ preferences.vert_color,
+ preferences.center_color,
+ preferences.perpendicular_color,
+ preferences.constrain_shift_color,
+ tuple(context.user_preferences.themes[0].user_interface.axis_x) + (1.0,),
+ tuple(context.user_preferences.themes[0].user_interface.axis_y) + (1.0,),
+ tuple(context.user_preferences.themes[0].user_interface.axis_z) + (1.0,)
+ )
+
+ obj = context.active_object
+
+ #Create a new object
+ if obj is None or obj.type != 'MESH':
+ mesh = bpy.data.meshes.new("")
+ obj = bpy.data.objects.new("", mesh)
+ context.scene.objects.link(obj)
+ context.scene.objects.active = obj
+ else:
+ mesh = obj.data
+
+ #Store current state
+ self.is_editmode = mesh.is_editmode
+ self.use_rotate_around_active = context.user_preferences.view.use_rotate_around_active
+ self.select_mode = context.tool_settings.mesh_select_mode[:]
+ self.show_face_center = context.space_data.overlay.show_face_center
+
+ #Modify the current state
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='DESELECT')
+ context.user_preferences.view.use_rotate_around_active = True
+ context.tool_settings.mesh_select_mode = (True, True, True)
+ context.space_data.overlay.show_face_center = True
+ context.scene.update_tag()
+
+ #Configure the unit of measure
+ scale = context.scene.unit_settings.scale_length
+ self.unit_system = context.scene.unit_settings.system
+ separate_units = context.scene.unit_settings.use_separate
+ self.uinfo = get_units_info(scale, self.unit_system, separate_units)
+
+ scale /= context.space_data.overlay.grid_scale * preferences.relative_scale
+ self.rd = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(1 / scale))
+
+ self.incremental = bpy.utils.units.to_value(self.unit_system, 'LENGTH', str(preferences.incremental))
+
+ #Store values from 3d view context
+ self.rv3d = context.region_data
+ self.rotMat = self.rv3d.view_matrix.copy()
+ # self.obj_glmatrix = bgl.Buffer(bgl.GL_FLOAT, [4, 4], self.obj_matrix.transposed())
+ self.main_bm = self.bm = bmesh.from_edit_mesh(mesh) #remove at end
+
+ #init these variables to avoid errors
+ self.prevloc = Vector()
+ self.location = Vector()
+ self.list_verts = []
+ self.list_edges = []
+ self.list_verts_co = []
+ self.bool_update = False
+ self.vector_constrain = ()
+ self.navigation_ops = SnapNavigation(context, True)
+ self.type = 'OUT'
+ self.len = 0
+ self.length_entered = ""
+ self.line_pos = 0
+ self.geom = None
+
+ #Init event variables
+ self.keytab = False
+ self.keyf8 = False
+
+ #Init Snap Context
+ from .snap_context_l import SnapContext
+
+ self.sctx = SnapContext(context.region, context.space_data)
+ self.sctx.set_pixel_dist(12)
+ self.sctx.use_clip_planes(True)
+
+ act_base = context.active_base
+
+ if self.outer_verts:
+ for base in context.visible_bases:
+ if base != act_base:
+ self.sctx.add_obj(base.object, base.object.matrix_world)
+
+ self.main_snap_obj = self.snap_obj = self.sctx.add_obj(act_base.object, act_base.object.matrix_world)
+
+ self.snap_face = True
+ self.sctx.set_snap_mode(True, True, self.snap_face)
+
+ #modals
+ self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (), 'WINDOW', 'POST_VIEW')
+ context.window_manager.modal_handler_add(self)
+
+ return {'RUNNING_MODAL'}
+ else:
+ self.report({'WARNING'}, "Active space must be a View3d")
+ return {'CANCELLED'}
+
+def register():
+ bpy.utils.register_class(SnapUtilitiesLine)
+
+if __name__ == "__main__":
+ register()
diff --git a/mesh_snap_utilities_line/preferences.py b/mesh_snap_utilities_line/preferences.py
new file mode 100644
index 00000000..37b6baf6
--- /dev/null
+++ b/mesh_snap_utilities_line/preferences.py
@@ -0,0 +1,125 @@
+### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from bpy.props import (
+ EnumProperty,
+ StringProperty,
+ BoolProperty,
+ IntProperty,
+ FloatVectorProperty,
+ FloatProperty,
+ )
+
+
+def update_panel(self, context):
+ try:
+ panel = bpy.types.VIEW3D_PT_snap_utilities
+ panel.bl_category = context.user_preferences.addons[__package__].preferences.category
+ bpy.utils.unregister_class(panel)
+ bpy.utils.register_class(panel)
+ except:
+ print('not update')
+ pass
+
+
+class SnapUtilitiesLinePreferences(bpy.types.AddonPreferences):
+ # this must match the addon name, use '__package__'
+ # when defining this in a submodule of a python package.
+ bl_idname = __package__
+
+ intersect: BoolProperty(
+ name="Intersect",
+ description="intersects created line with the existing edges, even if the lines do not intersect",
+ default=True)
+
+ create_face: BoolProperty(
+ name="Create faces",
+ description="Create faces defined by enclosed edges",
+ default=False)
+
+ outer_verts: BoolProperty(
+ name="Snap to outer vertices",
+ description="The vertices of the objects are not activated also snapped",
+ default=True)
+
+ increments_grid: BoolProperty(
+ name="Increments of Grid",
+ description="Snap to increments of grid",
+ default=False)
+
+ incremental: FloatProperty(
+ name="Incremental",
+ description="Snap in defined increments",
+ default=0,
+ min=0,
+ step=1,
+ precision=3)
+
+ relative_scale: FloatProperty(
+ name="Relative Scale",
+ description="Value that divides the global scale",
+ default=1,
+ min=0,
+ step=1,
+ precision=3)
+
+ out_color: FloatVectorProperty(name="OUT", default=(0.0, 0.0, 0.0, 0.5), size=4, subtype="COLOR", min=0, max=1)
+ face_color: FloatVectorProperty(name="FACE", default=(1.0, 0.8, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ edge_color: FloatVectorProperty(name="EDGE", default=(0.0, 0.8, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ vert_color: FloatVectorProperty(name="VERT", default=(1.0, 0.5, 0.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ center_color: FloatVectorProperty(name="CENTER", default=(1.0, 0.0, 1.0, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ perpendicular_color: FloatVectorProperty(name="PERPENDICULAR", default=(0.1, 0.5, 0.5, 1.0), size=4, subtype="COLOR", min=0, max=1)
+ constrain_shift_color: FloatVectorProperty(name="SHIFT CONSTRAIN", default=(0.8, 0.5, 0.4, 1.0), size=4, subtype="COLOR", min=0, max=1)
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.label(text="Snap Colors:")
+ split = layout.split()
+
+ col = split.column()
+ col.prop(self, "out_color")
+ col.prop(self, "constrain_shift_color")
+ col = split.column()
+ col.prop(self, "face_color")
+ col = split.column()
+ col.prop(self, "edge_color")
+ col = split.column()
+ col.prop(self, "vert_color")
+ col = split.column()
+ col.prop(self, "center_color")
+ col = split.column()
+ col.prop(self, "perpendicular_color")
+
+ row = layout.row()
+
+ col = row.column()
+ #col.label(text="Snap Items:")
+ col.prop(self, "incremental")
+ col.prop(self, "increments_grid")
+ if self.increments_grid:
+ col.prop(self, "relative_scale")
+
+ col.prop(self, "outer_verts")
+ row.separator()
+
+ col = row.column()
+ col.label(text="Line Tool:")
+ col.prop(self, "intersect")
+ col.prop(self, "create_face")
+ col.prop(self, "create_new_obj")
diff --git a/mesh_snap_utilities_line/snap_context_l/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py
new file mode 100644
index 00000000..584f7ecb
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/__init__.py
@@ -0,0 +1,439 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__all__ = (
+ "SnapContext",
+ )
+
+import bgl
+from mathutils import Vector
+
+VERT = 1
+EDGE = 2
+FACE = 4
+
+
+class _Internal:
+ from .mesh_drawing import (
+ gpu_Indices_enable_state,
+ gpu_Indices_restore_state,
+ gpu_Indices_use_clip_planes,
+ gpu_Indices_set_ProjectionMatrix,
+ )
+
+ from .utils_projection import (
+ region_2d_to_orig_and_view_vector,
+ intersect_boundbox_threshold,
+ intersect_ray_segment_fac,
+ project_co_v3,
+ )
+
+ from mathutils.geometry import intersect_line_plane
+
+
+class _SnapObjectData():
+ __slots__ = ('data', 'mat')
+ def __init__(self, data, omat):
+ self.data = data
+ self.mat = omat
+
+
+class _SnapOffscreen():
+ bound = None
+ def __init__(self, width, height):
+ import ctypes
+
+ self.freed = False
+ self.is_bound = False
+
+ self.width = width
+ self.height = height
+
+ self.fbo = bgl.Buffer(bgl.GL_INT, 1)
+ self.buf_color = bgl.Buffer(bgl.GL_INT, 1)
+ self.buf_depth = bgl.Buffer(bgl.GL_INT, 1)
+
+ self.cur_fbo = bgl.Buffer(bgl.GL_INT, 1)
+ self.cur_viewport = bgl.Buffer(bgl.GL_INT, 4)
+
+ bgl.glGenRenderbuffers(1, self.buf_depth)
+ bgl.glBindRenderbuffer(bgl.GL_RENDERBUFFER, self.buf_depth[0])
+ bgl.glRenderbufferStorage(bgl.GL_RENDERBUFFER, bgl.GL_DEPTH_COMPONENT, width, height)
+
+ bgl.glGenTextures(1, self.buf_color)
+ bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.buf_color[0])
+ NULL = bgl.Buffer(bgl.GL_INT, 1, (ctypes.c_int32 * 1).from_address(0))
+ bgl.glTexImage2D(bgl.GL_TEXTURE_2D, 0, bgl.GL_R32UI, width, height, 0, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, NULL)
+ del NULL
+ bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_NEAREST)
+ bgl.glTexParameteri(bgl.GL_TEXTURE_2D, bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_NEAREST)
+
+ bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo)
+
+ bgl.glGenFramebuffers(1, self.fbo)
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0])
+ bgl.glFramebufferRenderbuffer(bgl.GL_FRAMEBUFFER, bgl.GL_DEPTH_ATTACHMENT, bgl.GL_RENDERBUFFER, self.buf_depth[0])
+ bgl.glFramebufferTexture(bgl.GL_FRAMEBUFFER, bgl.GL_COLOR_ATTACHMENT0, self.buf_color[0], 0)
+
+ bgl.glDrawBuffers(1, bgl.Buffer(bgl.GL_INT, 1, [bgl.GL_COLOR_ATTACHMENT0]))
+
+ status = bgl.glCheckFramebufferStatus(bgl.GL_FRAMEBUFFER)
+ if status != bgl.GL_FRAMEBUFFER_COMPLETE:
+ print("Framebuffer Invalid", status)
+
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0])
+
+ def bind(self):
+ if self is not _SnapOffscreen.bound:
+ if _SnapOffscreen.bound is None:
+ bgl.glGetIntegerv(bgl.GL_FRAMEBUFFER_BINDING, self.cur_fbo)
+ bgl.glGetIntegerv(bgl.GL_VIEWPORT, self.cur_viewport)
+
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.fbo[0])
+ bgl.glViewport(0, 0, self.width, self.height)
+ _SnapOffscreen.bound = self
+
+ def unbind(self):
+ if self is _SnapOffscreen.bound:
+ bgl.glBindFramebuffer(bgl.GL_FRAMEBUFFER, self.cur_fbo[0])
+ bgl.glViewport(*self.cur_viewport)
+ _SnapOffscreen.bound = None
+
+ def clear(self):
+ is_bound = self is _SnapOffscreen.bound
+ if not is_bound:
+ self.bind()
+
+ bgl.glColorMask(bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE, bgl.GL_TRUE)
+ bgl.glClearColor(0.0, 0.0, 0.0, 0.0)
+
+ bgl.glDepthMask(bgl.GL_TRUE)
+ bgl.glClearDepth(1.0);
+
+ bgl.glClear(bgl.GL_COLOR_BUFFER_BIT | bgl.GL_DEPTH_BUFFER_BIT)
+
+ if not is_bound:
+ self.unbind()
+
+ def __del__(self):
+ if not self.freed:
+ bgl.glDeleteFramebuffers(1, self.fbo)
+ bgl.glDeleteRenderbuffers(1, self.buf_depth)
+ bgl.glDeleteTextures(1, self.buf_color)
+ del self.fbo
+ del self.buf_color
+ del self.buf_depth
+
+ del self.cur_fbo
+ del self.cur_viewport
+
+ def free(self):
+ self.__del__()
+ self.freed = True
+
+
+class SnapContext():
+ """
+ Initializes the snap context with the region and space where the snap objects will be added.
+
+ .. note::
+ After the context has been created, add the objects with the `add_obj` method.
+
+ :arg region: region of the 3D viewport, typically bpy.context.region.
+ :type region: :class:`bpy.types.Region`
+ :arg space: 3D region data, typically bpy.context.space_data.
+ :type space: :class:`bpy.types.SpaceView3D`
+ """
+
+ def __init__(self, region, space):
+ #print('Render:', bgl.glGetString(bgl.GL_RENDERER))
+ #print('OpenGL Version:', bgl.glGetString(bgl.GL_VERSION))
+
+ self.freed = False
+ self.snap_objects = []
+ self.drawn_count = 0
+ self._offset_cur = 1 # Starts with index 1
+ self.region = region
+ self.rv3d = space.region_3d
+
+ if self.rv3d.is_perspective:
+ self.depth_range = Vector((space.clip_start, space.clip_end))
+ else:
+ self.depth_range = Vector((-space.clip_end, space.clip_end))
+
+ self.proj_mat = None
+ self.mval = Vector((0, 0))
+ self._snap_mode = VERT | EDGE | FACE
+
+ self.set_pixel_dist(12)
+
+ self._offscreen = _SnapOffscreen(self.region.width, self.region.height)
+
+ self.winsize = Vector((self._offscreen.width, self._offscreen.height))
+
+ self._offscreen.clear()
+
+ ## PRIVATE ##
+
+ def _get_snap_obj_by_index(self, index):
+ if index:
+ for snap_obj in self.snap_objects[:self.drawn_count]:
+ data = snap_obj.data[1]
+ if index < data.first_index + data.get_tot_elems():
+ return snap_obj
+ return None
+
+ def _get_nearest_index(self):
+ r_snap_obj = None
+ r_value = 0
+
+ loc = [self._dist_px, self._dist_px]
+ d = 1
+ m = self.threshold
+ max_val = 2 * m - 1
+ last_value = -1
+ find_next_index = self._snap_mode & FACE and self._snap_mode & (VERT | EDGE)
+ while m < max_val:
+ for i in range(2):
+ while 2 * loc[i] * d < m:
+ value = int(self._snap_buffer[loc[0]][loc[1]])
+ loc[i] += d
+ if value != last_value:
+ r_value = value
+ if find_next_index:
+ last_value = value
+ find_next_index = False
+ r_snap_obj = self._get_snap_obj_by_index(value)
+ if (r_snap_obj is None) or value < (r_snap_obj.data[1].first_index + len(r_snap_obj.data[1].tri_verts)):
+ continue
+ elif (r_snap_obj is None) or\
+ (value < r_snap_obj.data[1].first_index) or\
+ (value >= (r_snap_obj.data[1].first_index + r_snap_obj.data[1].get_tot_elems())):
+ r_snap_obj = self._get_snap_obj_by_index(value)
+ return r_snap_obj, r_value
+ d = -d
+ m += 4 * self._dist_px * d + 1
+
+ return r_snap_obj, r_value
+
+ def _get_loc(self, snap_obj, index):
+ index -= snap_obj.data[1].first_index
+ gpu_data = snap_obj.data[1]
+
+ if gpu_data.draw_tris:
+ num_tris = len(snap_obj.data[1].tri_verts)
+ if index < num_tris:
+ tri_verts = gpu_data.get_tri_verts(index)
+ tri_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_tri_co(index)]
+ nor = (tri_co[1] - tri_co[0]).cross(tri_co[2] - tri_co[0])
+ return _Internal.intersect_line_plane(self.last_ray[1], self.last_ray[1] + self.last_ray[0], tri_co[0], nor), tri_verts, tri_co
+
+ index -= num_tris
+
+ if gpu_data.draw_edges:
+ num_edges = len(snap_obj.data[1].edge_verts)
+ if index < num_edges:
+ edge_verts = gpu_data.get_edge_verts(index)
+ edge_co = [snap_obj.mat @ Vector(v) for v in gpu_data.get_edge_co(index)]
+ fac = _Internal.intersect_ray_segment_fac(*edge_co, *self.last_ray)
+
+ if (self._snap_mode) & VERT and (fac < 0.25 or fac > 0.75):
+ co = edge_co[0] if fac < 0.5 else edge_co[1]
+ proj_co = _Internal.project_co_v3(self, co)
+ dist = self.mval - proj_co
+ if abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px:
+ return co, (edge_verts[0] if fac < 0.5 else edge_verts[1],), co
+
+ if fac <= 0.0:
+ co = edge_co[0]
+ elif fac >= 1.0:
+ co = edge_co[1]
+ else:
+ co = edge_co[0] + fac * (edge_co[1] - edge_co[0])
+
+ return co, edge_verts, edge_co
+
+ index -= num_edges
+
+ if gpu_data.draw_verts:
+ if index < len(snap_obj.data[1].looseverts):
+ co = snap_obj.mat @ Vector(gpu_data.get_loosevert_co(index))
+ return co, (gpu_data.get_loosevert_index(index),), co
+
+ return None, None, None
+
+
+ def _get_snap_obj_by_obj(self, obj):
+ for snap_obj in self.snap_objects:
+ if obj == snap_obj.data[0]:
+ return snap_obj
+
+ def __del__(self):
+ if not self.freed:
+ self._offscreen.free()
+ # Some objects may still be being referenced
+ for snap_obj in self.snap_objects:
+ del snap_obj.data
+ del snap_obj.mat
+ del snap_obj
+ del self.snap_objects
+
+ ## PUBLIC ##
+
+ def update_all(self):
+ self.drawn_count = 0
+ self._offset_cur = 1
+ self._offscreen.clear()
+
+ def tag_update_drawn_snap_object(self, snap_obj):
+ if len(snap_obj.data) > 1:
+ del snap_obj.data[1:]
+ #self.update_all()
+ # Update on next snap_get call #
+ self.proj_mat = None
+
+ def update_drawn_snap_object(self, snap_obj):
+ if len(snap_obj.data) > 1:
+ _Internal.gpu_Indices_enable_state()
+
+ from .mesh_drawing import GPU_Indices_Mesh
+ snap_vert = self._snap_mode & VERT != 0
+ snap_edge = self._snap_mode & EDGE != 0
+ snap_face = self._snap_mode & FACE != 0
+ snap_obj.data[1] = GPU_Indices_Mesh(snap_obj.data[0], snap_face, snap_edge, snap_vert)
+
+ _Internal.gpu_Indices_restore_state()
+
+ def use_clip_planes(self, value):
+ _Internal.gpu_Indices_use_clip_planes(self.rv3d, value)
+
+ def set_pixel_dist(self, dist_px):
+ self._dist_px = int(dist_px)
+ self._dist_px_sq = self._dist_px ** 2
+ self.threshold = 2 * self._dist_px + 1
+ self._snap_buffer = bgl.Buffer(bgl.GL_INT, (self.threshold, self.threshold))
+
+ def set_snap_mode(self, snap_to_vert, snap_to_edge, snap_to_face):
+ snap_mode = 0
+ if snap_to_vert:
+ snap_mode |= VERT
+ if snap_to_edge:
+ snap_mode |= EDGE
+ if snap_to_face:
+ snap_mode |= FACE
+
+ if snap_mode != self._snap_mode:
+ self._snap_mode = snap_mode
+ self.update_all()
+
+ def add_obj(self, obj, matrix):
+ matrix = matrix.copy()
+ snap_obj = self._get_snap_obj_by_obj(obj)
+ if not snap_obj:
+ self.snap_objects.append(_SnapObjectData([obj], matrix))
+ else:
+ self.snap_objects.append(_SnapObjectData(snap_obj.data, matrix))
+
+ return self.snap_objects[-1]
+
+ def get_ray(self, mval):
+ self.last_ray = _Internal.region_2d_to_orig_and_view_vector(self.region, self.rv3d, mval)
+ return self.last_ray
+
+ def snap_get(self, mval, main_snap_obj = None):
+ ret = None, None, None
+ self.mval[:] = mval
+ snap_vert = self._snap_mode & VERT != 0
+ snap_edge = self._snap_mode & EDGE != 0
+ snap_face = self._snap_mode & FACE != 0
+
+ _Internal.gpu_Indices_enable_state()
+ self._offscreen.bind()
+
+ #bgl.glDisable(bgl.GL_DITHER) # dithering and AA break color coding, so disable #
+ #multisample_enabled = bgl.glIsEnabled(bgl.GL_MULTISAMPLE)
+ #bgl.glDisable(bgl.GL_MULTISAMPLE)
+ bgl.glEnable(bgl.GL_DEPTH_TEST)
+
+ proj_mat = self.rv3d.perspective_matrix.copy()
+ if self.proj_mat != proj_mat:
+ self.proj_mat = proj_mat
+ _Internal.gpu_Indices_set_ProjectionMatrix(self.proj_mat)
+ self.update_all()
+
+ ray_dir, ray_orig = self.get_ray(mval)
+ for i, snap_obj in enumerate(self.snap_objects[self.drawn_count:], self.drawn_count):
+ obj = snap_obj.data[0]
+ try:
+ bbmin = Vector(obj.bound_box[0])
+ bbmax = Vector(obj.bound_box[6])
+ except ReferenceError:
+ self.snap_objects.remove(snap_obj)
+ continue
+
+ if bbmin != bbmax:
+ MVP = proj_mat @ snap_obj.mat
+ mat_inv = snap_obj.mat.inverted()
+ ray_orig_local = mat_inv @ ray_orig
+ ray_dir_local = mat_inv.to_3x3() @ ray_dir
+ in_threshold = _Internal.intersect_boundbox_threshold(
+ self, MVP, ray_orig_local, ray_dir_local, bbmin, bbmax)
+ else:
+ proj_co = _Internal.project_co_v3(self, snap_obj.mat.translation)
+ dist = self.mval - proj_co
+ in_threshold = abs(dist.x) < self._dist_px and abs(dist.y) < self._dist_px
+ #snap_obj.data[1] = primitive_point
+
+ if in_threshold:
+ if len(snap_obj.data) == 1:
+ from .mesh_drawing import GPU_Indices_Mesh
+ snap_obj.data.append(GPU_Indices_Mesh(obj, snap_face, snap_edge, snap_vert))
+ snap_obj.data[1].set_draw_mode(snap_face, snap_edge, snap_vert)
+ snap_obj.data[1].set_ModelViewMatrix(snap_obj.mat)
+
+ if snap_obj == main_snap_obj:
+ snap_obj.data[1].Draw(self._offset_cur, -0.0001)
+ else:
+ snap_obj.data[1].Draw(self._offset_cur)
+ self._offset_cur += snap_obj.data[1].get_tot_elems()
+
+ tmp = self.snap_objects[self.drawn_count]
+ self.snap_objects[self.drawn_count] = self.snap_objects[i]
+ self.snap_objects[i] = tmp
+
+ self.drawn_count += 1
+
+ bgl.glReadBuffer(bgl.GL_COLOR_ATTACHMENT0)
+ bgl.glReadPixels(
+ int(self.mval[0]) - self._dist_px, int(self.mval[1]) - self._dist_px,
+ self.threshold, self.threshold, bgl.GL_RED_INTEGER, bgl.GL_UNSIGNED_INT, self._snap_buffer)
+ #bgl.glReadBuffer(bgl.GL_BACK)
+
+ snap_obj, index = self._get_nearest_index()
+ #print("index:", index)
+ if snap_obj:
+ ret = self._get_loc(snap_obj, index)
+
+ bgl.glDisable(bgl.GL_DEPTH_TEST)
+ self._offscreen.unbind()
+ _Internal.gpu_Indices_restore_state()
+
+ return (snap_obj, *ret)
+
+ def free(self):
+ self.__del__()
+ self.freed = True
diff --git a/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py
new file mode 100644
index 00000000..6ef0b0a0
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/mesh_drawing.py
@@ -0,0 +1,412 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+import bgl
+import bmesh
+import numpy as np
+from mathutils import Matrix
+import gpu
+
+_Hash = {}
+
+def load_shader(shadername):
+ from os import path
+ with open(path.join(path.dirname(__file__), 'shaders', shadername), 'r') as f:
+ return f.read()
+
+def get_mesh_vert_co_array(me):
+ tot_vco = len(me.vertices)
+ if tot_vco:
+ verts_co = np.empty(len(me.vertices) * 3, 'f4')
+ me.vertices.foreach_get("co", verts_co)
+ verts_co.shape = (-1, 3)
+ return verts_co
+ return None
+
+
+def get_bmesh_vert_co_array(bm):
+ tot_vco = len(bm.verts)
+ if tot_vco:
+ return np.array([v.co for v in bm.verts], 'f4')
+ return None
+
+
+def get_mesh_tri_verts_array(me):
+ me.calc_loop_triangles()
+ len_triangles = len(me.loop_triangles)
+ if len_triangles:
+ tris = np.empty(len_triangles * 3, 'i4')
+ me.loop_triangles.foreach_get("vertices", tris)
+ tris.shape = (-1, 3)
+ return tris
+ return None
+
+
+def get_bmesh_tri_verts_array(bm):
+ l_tri_layer = bm.faces.layers.int.get("l_tri")
+ if l_tri_layer is None:
+ l_tri_layer = bm.faces.layers.int.new("l_tri")
+
+ ltris = bm.calc_loop_triangles()
+ tris = np.empty((len(ltris), 3), 'i4')
+ i = 0
+ last_face = bm.faces[-1]
+ for ltri in ltris:
+ face = ltri[0].face
+ if not face.hide:
+ tris[i] = ltri[0].vert.index, ltri[1].vert.index, ltri[2].vert.index
+ if last_face != face:
+ last_face = face
+ face[l_tri_layer] = i
+ i += 1
+ if i:
+ tris.resize((i, 3), refcheck=False)
+ return tris
+ return None
+
+
+def get_mesh_edge_verts_array(me):
+ tot_edges = len(me.edges)
+ if tot_edges:
+ edge_verts = np.empty(tot_edges * 2, 'i4')
+ me.edges.foreach_get("vertices", edge_verts)
+ edge_verts.shape = tot_edges, 2
+ return edge_verts
+ return None
+
+
+def get_bmesh_edge_verts_array(bm):
+ bm.edges.ensure_lookup_table()
+ edges = [[e.verts[0].index, e.verts[1].index] for e in bm.edges if not e.hide]
+ if edges:
+ return np.array(edges, 'i4')
+ return None
+
+
+def get_mesh_loosevert_array(me, edges):
+ verts = np.arange(len(me.vertices))
+
+ mask = np.in1d(verts, edges, invert=True)
+
+ verts = verts[mask]
+ if len(verts):
+ return verts
+ return None
+
+
+def get_bmesh_loosevert_array(bm):
+ looseverts = [v.index for v in bm.verts if not (v.link_edges or v.hide)]
+ if looseverts:
+ return np.array(looseverts, 'i4')
+ return None
+
+
+class _Mesh_Arrays():
+ def __init__(self, obj, create_tris, create_edges, create_looseverts):
+ self.tri_verts = self.edge_verts = self.looseverts = ()
+ if obj.type == 'MESH':
+ me = obj.data
+ if me.is_editmode:
+ bm = bmesh.from_edit_mesh(me)
+ bm.verts.ensure_lookup_table()
+
+ self.verts_co = get_bmesh_vert_co_array(bm)
+
+ if create_tris:
+ self.tri_verts = get_bmesh_tri_verts_array(bm)
+ if create_edges:
+ self.edge_verts = get_bmesh_edge_verts_array(bm)
+ if create_looseverts:
+ self.looseverts = get_bmesh_loosevert_array(bm)
+
+ del bm
+ else:
+ import bpy
+ self.verts_co = get_mesh_vert_co_array(me)
+
+ if create_tris:
+ self.tri_verts = get_mesh_tri_verts_array(me)
+ if create_edges:
+ self.edge_verts = get_mesh_edge_verts_array(me)
+ if create_looseverts:
+ edge_verts = self.edge_verts
+ if edge_verts is None:
+ edge_verts = get_mesh_edge_verts_array(me)
+ self.looseverts = get_mesh_loosevert_array(me, edge_verts)
+ del edge_verts
+
+
+ else: #TODO
+ self.verts_co = np.zeros((1,3), 'f4')
+ self.looseverts = np.zeros(1, 'i4')
+
+ def __del__(self):
+ del self.tri_verts, self.edge_verts, self.looseverts
+ del self.verts_co
+
+
+class GPU_Indices_Mesh():
+ __slots__ = (
+ "obj",
+ "draw_tris",
+ "draw_edges",
+ "draw_verts",
+ "batch_tris",
+ "batch_edges",
+ "batch_lverts",
+ "verts_co",
+ "tri_verts",
+ "edge_verts",
+ "looseverts",
+ "first_index",
+ "users"
+ )
+
+ shader = None
+
+ @classmethod
+ def end_opengl(cls):
+ del cls.shader
+ del cls.P
+
+ del cls
+
+ @staticmethod
+ def init_opengl():
+ cls = GPU_Indices_Mesh
+ # OpenGL was already initialized, nothing to do here.
+ if cls.shader is not None:
+ return
+
+ import atexit
+
+ # Make sure we only registered the callback once.
+ atexit.unregister(cls.end_opengl)
+ atexit.register(cls.end_opengl)
+
+ cls.shader = gpu.types.GPUShader(
+ load_shader("ID_color_vert.glsl"),
+ load_shader("ID_color_frag.glsl"),
+ )
+ #cls.unif_use_clip_planes = cls.shader.uniform_from_name('use_clip_planes')
+ #cls.unif_clip_plane = cls.shader.uniform_from_name('clip_plane')
+ cls.unif_offset = cls.shader.uniform_from_name('offset')
+
+ cls.P = Matrix()
+
+
+ @staticmethod
+ def set_ModelViewMatrix(MV):
+ gpu.matrix.load_matrix(MV)
+
+
+ def __init__(self, obj, draw_tris, draw_edges, draw_verts):
+ self.obj = obj
+
+ if obj.data in _Hash:
+ src = _Hash[obj.data]
+ dst = self
+
+ dst.draw_tris = src.draw_tris
+ dst.draw_edges = src.draw_edges
+ dst.draw_verts = src.draw_verts
+ dst.batch_tris = src.batch_tris
+ dst.batch_edges = src.batch_edges
+ dst.batch_lverts = src.batch_lverts
+ dst.verts_co = src.verts_co
+ dst.tri_verts = src.tri_verts
+ dst.edge_verts = src.edge_verts
+ dst.looseverts = src.looseverts
+ dst.users = src.users
+ dst.users.append(self)
+
+ update = obj.type == 'MESH' and obj.data.is_editmode
+
+ else:
+ _Hash[obj.data] = self
+ self.users = [self]
+ update = True;
+
+ if update:
+ self.draw_tris = draw_tris
+ self.draw_edges = draw_edges
+ self.draw_verts = draw_verts
+
+ GPU_Indices_Mesh.init_opengl()
+
+ ## Init Array ##
+ mesh_arrays = _Mesh_Arrays(obj, draw_tris, draw_edges, draw_verts)
+
+ if mesh_arrays.verts_co is None:
+ self.draw_tris = False
+ self.draw_edges = False
+ self.draw_verts = False
+ self.tri_verts = None
+ self.edge_verts = None
+ self.looseverts = None
+ return
+
+ ## Create VBO for vertices ##
+ self.verts_co = mesh_arrays.verts_co
+ self.tri_verts = mesh_arrays.tri_verts
+ self.edge_verts = mesh_arrays.edge_verts
+ self.looseverts = mesh_arrays.looseverts
+ del mesh_arrays
+
+ format = gpu.types.GPUVertFormat()
+ format.attr_add(id="pos", comp_type='F32', len=3, fetch_mode='FLOAT')
+
+ vbo = gpu.types.GPUVertBuf(format, len = len(self.verts_co))
+
+ vbo.attr_fill(0, data = self.verts_co)
+
+ ## Create Batch for Tris ##
+ if self.tri_verts is not None:
+ ebo = gpu.types.GPUIndexBuf(type = "TRIS", seq = self.tri_verts)
+ self.batch_tris = gpu.types.GPUBatch(type = "TRIS", buf = vbo, elem = ebo)
+ self.batch_tris.program_set(self.shader)
+ else:
+ self.draw_tris = False
+ self.batch_tris = None
+
+ ## Create Batch for Edges ##
+ if self.edge_verts is not None:
+ ebo = gpu.types.GPUIndexBuf(type = "LINES", seq = self.edge_verts)
+ self.batch_edges = gpu.types.GPUBatch(type = "LINES", buf = vbo, elem = ebo)
+ self.batch_edges.program_set(self.shader)
+ else:
+ self.draw_edges = False
+ self.batch_edges = None
+
+ ## Create Batch for Loose Verts ##
+ if self.looseverts is not None:
+ ebo = gpu.types.GPUIndexBuf(type = "POINTS", seq = self.looseverts)
+ self.batch_lverts = gpu.types.GPUBatch(type = "POINTS", buf = vbo, elem = ebo)
+ self.batch_lverts.program_set(self.shader)
+ else:
+ self.draw_verts = False
+ self.batch_lverts = None
+
+
+ def get_tot_elems(self):
+ tot = 0
+ if self.draw_tris:
+ tot += len(self.tri_verts)
+
+ if self.draw_edges:
+ tot += len(self.edge_verts)
+
+ if self.draw_verts:
+ tot += len(self.looseverts)
+
+ return tot
+
+
+ def set_draw_mode(self, draw_tris, draw_edges, draw_verts):
+ self.draw_tris = draw_tris and self.tri_verts is not None
+ self.draw_edges = draw_edges and self.edge_verts is not None
+ self.draw_verts = draw_verts and self.looseverts is not None
+
+
+ def Draw(self, index_offset, depth_offset = -0.00005):
+ self.first_index = index_offset
+ if self.draw_tris:
+ self.shader.uniform_int("offset", (index_offset,))
+ self.batch_tris.draw(self.shader)
+ index_offset += len(self.tri_verts)
+ bgl.glDepthRange(depth_offset, 1 + depth_offset)
+
+ if self.draw_edges:
+ self.shader.uniform_int("offset", (index_offset,))
+ #bgl.glLineWidth(3.0)
+ self.batch_edges.draw(self.shader)
+ #bgl.glLineWidth(1.0)
+ index_offset += len(self.edge_verts)
+
+ if self.draw_verts:
+ self.shader.uniform_int("offset", (index_offset,))
+ self.batch_lverts.draw(self.shader)
+
+ bgl.glDepthRange(0.0, 1.0)
+
+
+ def get_tri_co(self, index):
+ return self.verts_co[self.tri_verts[index]]
+
+ def get_edge_co(self, index):
+ return self.verts_co[self.edge_verts[index]]
+
+ def get_loosevert_co(self, index):
+ return self.verts_co[self.looseverts[index]]
+
+ def get_loop_tri_co_by_bmface(self, bm, bmface):
+ l_tri_layer = bm.faces.layers.int["l_tri"]
+ tri = bmface[l_tri_layer]
+ return self.verts_co[self.tri_verts[tri : tri + len(bmface.verts) - 2]]
+
+
+ def get_tri_verts(self, index):
+ return self.tri_verts[index]
+
+ def get_edge_verts(self, index):
+ return self.edge_verts[index]
+
+ def get_loosevert_index(self, index):
+ return self.looseverts[index]
+
+
+ def __del__(self):
+ if len(self.users) == 1:
+ self.free_gl()
+ _Hash.pop(obj.data)
+
+ self.user.remove(self)
+ #print('mesh_del', self.obj.name)
+
+
+def gpu_Indices_enable_state():
+ GPU_Indices_Mesh.init_opengl()
+ gpu.matrix.push()
+ gpu.matrix.push_projection()
+ gpu.matrix.load_projection_matrix(GPU_Indices_Mesh.P)
+ GPU_Indices_Mesh.shader.bind()
+
+
+def gpu_Indices_restore_state():
+ gpu.matrix.pop()
+ gpu.matrix.pop_projection()
+
+
+def gpu_Indices_use_clip_planes(rv3d, value):
+ pass #TODO
+ #if rv3d.use_clip_planes:
+ #planes = bgl.Buffer(bgl.GL_FLOAT, (6, 4), rv3d.clip_planes)
+
+ #_store_current_shader_state(PreviousGLState)
+ #GPU_Indices_Mesh.init_opengl()
+ #bgl.glUseProgram(GPU_Indices_Mesh.shader.program)
+ #bgl.glUniform1i(GPU_Indices_Mesh.unif_use_clip_planes, value)
+
+ #bgl.glUniform4fv(GPU_Indices_Mesh.unif_clip_plane, 4, planes)
+
+ #_restore_shader_state(PreviousGLState)
+
+
+def gpu_Indices_set_ProjectionMatrix(P):
+ gpu.matrix.load_projection_matrix(P)
+ GPU_Indices_Mesh.P[:] = P
diff --git a/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl
new file mode 100644
index 00000000..3e01f7b0
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl
@@ -0,0 +1,24 @@
+uniform int offset;
+
+#ifdef USE_CLIP_PLANES
+uniform bool use_clip_planes;
+in vec4 clip_distance;
+#endif
+
+out uint FragColor;
+
+void main()
+{
+#ifdef USE_CLIP_PLANES
+ if (use_clip_planes &&
+ ((clip_distance[0] < 0) ||
+ (clip_distance[1] < 0) ||
+ (clip_distance[2] < 0) ||
+ (clip_distance[3] < 0)))
+ {
+ discard;
+ }
+#endif
+
+ FragColor = uint(gl_PrimitiveID + offset);
+}
diff --git a/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl
new file mode 100644
index 00000000..fa0afec6
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl
@@ -0,0 +1,25 @@
+uniform mat4 ModelViewProjectionMatrix;
+
+#ifdef USE_CLIP_PLANES
+uniform mat4 ModelViewMatrix;
+uniform bool use_clip_planes;
+uniform vec4 clip_plane[4];
+out vec4 clip_distance;
+#endif
+
+in vec3 pos;
+
+void main()
+{
+#ifdef USE_CLIP_PLANES
+ if (use_clip_planes) {
+ vec4 g_pos = ModelViewMatrix * vec4(pos, 1.0);
+
+ for (int i = 0; i != 4; i++) {
+ clip_distance[i] = dot(clip_plane[i], g_pos);
+ }
+ }
+#endif
+
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+}
diff --git a/mesh_snap_utilities_line/snap_context_l/utils_projection.py b/mesh_snap_utilities_line/snap_context_l/utils_projection.py
new file mode 100644
index 00000000..cc17aa23
--- /dev/null
+++ b/mesh_snap_utilities_line/snap_context_l/utils_projection.py
@@ -0,0 +1,216 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+from mathutils import Vector
+from mathutils.geometry import intersect_point_line
+
+
+def depth_get(co, ray_start, ray_dir):
+ dvec = co - ray_start
+ return dvec.dot(ray_dir)
+
+
+def region_2d_to_orig_and_view_vector(region, rv3d, coord):
+ viewinv = rv3d.view_matrix.inverted_safe()
+ persinv = rv3d.perspective_matrix.inverted_safe()
+
+ dx = (2.0 * coord[0] / region.width) - 1.0
+ dy = (2.0 * coord[1] / region.height) - 1.0
+
+ if rv3d.is_perspective:
+ origin_start = viewinv.translation.copy()
+
+ out = Vector((dx, dy, -0.5))
+
+ w = out.dot(persinv[3].xyz) + persinv[3][3]
+
+ view_vector = ((persinv @ out) / w) - origin_start
+ else:
+ view_vector = -viewinv.col[2].xyz
+
+ origin_start = ((persinv.col[0].xyz * dx) +
+ (persinv.col[1].xyz * dy) +
+ viewinv.translation)
+
+ view_vector.normalize()
+ return view_vector, origin_start
+
+
+def project_co_v3(sctx, co):
+ proj_co = sctx.proj_mat @ co.to_4d()
+ try:
+ proj_co.xy /= proj_co.w
+ except Exception as e:
+ print(e)
+
+ win_half = sctx.winsize * 0.5
+ proj_co[0] = (proj_co[0] + 1.0) * win_half[0]
+ proj_co[1] = (proj_co[1] + 1.0) * win_half[1]
+
+ return proj_co.xy
+
+
+
+def intersect_boundbox_threshold(sctx, MVP, ray_origin_local, ray_direction_local, bbmin, bbmax):
+ local_bvmin = Vector()
+ local_bvmax = Vector()
+ tmin = Vector()
+ tmax = Vector()
+
+ if (ray_direction_local[0] < 0.0):
+ local_bvmin[0] = bbmax[0]
+ local_bvmax[0] = bbmin[0]
+ else:
+ local_bvmin[0] = bbmin[0]
+ local_bvmax[0] = bbmax[0]
+
+ if (ray_direction_local[1] < 0.0):
+ local_bvmin[1] = bbmax[1]
+ local_bvmax[1] = bbmin[1]
+ else:
+ local_bvmin[1] = bbmin[1]
+ local_bvmax[1] = bbmax[1]
+
+ if (ray_direction_local[2] < 0.0):
+ local_bvmin[2] = bbmax[2]
+ local_bvmax[2] = bbmin[2]
+ else:
+ local_bvmin[2] = bbmin[2]
+ local_bvmax[2] = bbmax[2]
+
+ if (ray_direction_local[0]):
+ tmin[0] = (local_bvmin[0] - ray_origin_local[0]) / ray_direction_local[0]
+ tmax[0] = (local_bvmax[0] - ray_origin_local[0]) / ray_direction_local[0]
+ else:
+ tmin[0] = tmax[0] = sctx.depth_range[1]
+
+ if (ray_direction_local[1]):
+ tmin[1] = (local_bvmin[1] - ray_origin_local[1]) / ray_direction_local[1]
+ tmax[1] = (local_bvmax[1] - ray_origin_local[1]) / ray_direction_local[1]
+ else:
+ tmin[1] = tmax[1] = sctx.depth_range[1]
+
+ if (ray_direction_local[2]):
+ tmin[2] = (local_bvmin[2] - ray_origin_local[2]) / ray_direction_local[2]
+ tmax[2] = (local_bvmax[2] - ray_origin_local[2]) / ray_direction_local[2]
+ else:
+ tmin[2] = tmax[2] = sctx.depth_range[1]
+
+ # `va` and `vb` are the coordinates of the AABB edge closest to the ray #
+ va = Vector()
+ vb = Vector()
+ # `rtmin` and `rtmax` are the minimum and maximum distances of the ray hits on the AABB #
+
+ if ((tmax[0] <= tmax[1]) and (tmax[0] <= tmax[2])):
+ rtmax = tmax[0]
+ va[0] = vb[0] = local_bvmax[0]
+ main_axis = 3
+ elif ((tmax[1] <= tmax[0]) and (tmax[1] <= tmax[2])):
+ rtmax = tmax[1]
+ va[1] = vb[1] = local_bvmax[1]
+ main_axis = 2
+ else:
+ rtmax = tmax[2]
+ va[2] = vb[2] = local_bvmax[2]
+ main_axis = 1
+
+ if ((tmin[0] >= tmin[1]) and (tmin[0] >= tmin[2])):
+ rtmin = tmin[0]
+ va[0] = vb[0] = local_bvmin[0]
+ main_axis -= 3
+
+ elif ((tmin[1] >= tmin[0]) and (tmin[1] >= tmin[2])):
+ rtmin = tmin[1]
+ va[1] = vb[1] = local_bvmin[1]
+ main_axis -= 1
+
+ else:
+ rtmin = tmin[2]
+ va[2] = vb[2] = local_bvmin[2]
+ main_axis -= 2
+
+ if (main_axis < 0):
+ main_axis += 3
+
+#ifdef IGNORE_BEHIND_RAY
+ depth_max = depth_get(local_bvmax, ray_origin_local, ray_direction_local)
+ if (depth_max < sctx.depth_range[0]):
+ return False
+#endif
+
+ if (rtmin <= rtmax):
+ # if rtmin < rtmax, ray intersect `AABB` #
+ return True
+
+ if (ray_direction_local[main_axis] < 0.0):
+ va[main_axis] = local_bvmax[main_axis]
+ vb[main_axis] = local_bvmin[main_axis]
+
+ else:
+ va[main_axis] = local_bvmin[main_axis]
+ vb[main_axis] = local_bvmax[main_axis]
+
+ win_half = sctx.winsize * 0.5
+
+ scale = abs(local_bvmax[main_axis] - local_bvmin[main_axis])
+
+ va2d = Vector((
+ (MVP[0].xyz.dot(va) + MVP[0][3]),
+ (MVP[1].xyz.dot(va) + MVP[1][3]),
+ ))
+
+ vb2d = Vector((
+ (va2d[0] + MVP[0][main_axis] * scale),
+ (va2d[1] + MVP[1][main_axis] * scale),
+ ))
+
+ depth_a = MVP[3].xyz.dot(va) + MVP[3][3]
+ depth_b = depth_a + MVP[3][main_axis] * scale
+
+ va2d /= depth_a
+ vb2d /= depth_b
+
+ va2d[0] = (va2d[0] + 1.0) * win_half[0]
+ va2d[1] = (va2d[1] + 1.0) * win_half[1]
+ vb2d[0] = (vb2d[0] + 1.0) * win_half[0]
+ vb2d[1] = (vb2d[1] + 1.0) * win_half[1]
+
+ p, fac = intersect_point_line(sctx.mval, va2d, vb2d)
+ if fac < 0.0:
+ return (sctx.mval - va2d).length_squared < sctx._dist_px_sq
+ elif fac > 1.0:
+ return (sctx.mval - vb2d).length_squared < sctx._dist_px_sq
+ else:
+ return (sctx.mval - p).length_squared < sctx._dist_px_sq
+
+
+def intersect_ray_segment_fac(v0, v1, ray_direction, ray_origin):
+ a = v1 - v0
+ t = v0 - ray_origin
+ n = a.cross(ray_direction)
+ nlen = n.length_squared
+
+ # if (nlen == 0.0f) the lines are parallel, has no nearest point, only distance squared.*/
+ if nlen == 0.0:
+ # Calculate the distance to the nearest point to origin then #
+ return a.dot(ray_direction) < 0
+ else:
+ c = n - t
+ cray = c.cross(ray_direction)
+ return cray.dot(n) / nlen
+