diff options
author | mano-wii <germano.costa@ig.com.br> | 2018-10-30 06:11:22 +0300 |
---|---|---|
committer | mano-wii <germano.costa@ig.com.br> | 2018-10-30 06:11:22 +0300 |
commit | 7fb1a1ca26c519d12ffb3973f68fa50f306d7456 (patch) | |
tree | 1361e10460c179f353f2b85059b8446aaa6940c9 /mesh_snap_utilities_line | |
parent | 747c0a3731f10972eb373a119171d75a35b95b7c (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__.py | 103 | ||||
-rw-r--r-- | mesh_snap_utilities_line/common_classes.py | 334 | ||||
-rw-r--r-- | mesh_snap_utilities_line/common_utilities.py | 269 | ||||
-rw-r--r-- | mesh_snap_utilities_line/icons/ops.mesh.make_line.dat | bin | 0 -> 1340 bytes | |||
-rw-r--r-- | mesh_snap_utilities_line/ops_line.py | 551 | ||||
-rw-r--r-- | mesh_snap_utilities_line/preferences.py | 125 | ||||
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/__init__.py | 439 | ||||
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/mesh_drawing.py | 412 | ||||
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/shaders/ID_color_frag.glsl | 24 | ||||
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/shaders/ID_color_vert.glsl | 25 | ||||
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/utils_projection.py | 216 |
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 Binary files differnew file mode 100644 index 00000000..fa738db9 --- /dev/null +++ b/mesh_snap_utilities_line/icons/ops.mesh.make_line.dat 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 + |