diff options
-rw-r--r-- | mesh_snap_utilities_line/__init__.py | 26 | ||||
-rw-r--r-- | mesh_snap_utilities_line/common_classes.py | 498 | ||||
-rw-r--r-- | mesh_snap_utilities_line/common_utilities.py | 30 | ||||
-rw-r--r-- | mesh_snap_utilities_line/drawing_utilities.py | 224 | ||||
-rw-r--r-- | mesh_snap_utilities_line/navigation_ops.py | 102 | ||||
-rw-r--r-- | mesh_snap_utilities_line/op_line.py (renamed from mesh_snap_utilities_line/ops_line.py) | 16 | ||||
-rw-r--r-- | mesh_snap_utilities_line/preferences.py | 6 | ||||
-rw-r--r-- | mesh_snap_utilities_line/snap_context_l/__init__.py | 8 | ||||
-rw-r--r-- | mesh_snap_utilities_line/widgets.py | 244 |
9 files changed, 670 insertions, 484 deletions
diff --git a/mesh_snap_utilities_line/__init__.py b/mesh_snap_utilities_line/__init__.py index dc086540..be37d8a0 100644 --- a/mesh_snap_utilities_line/__init__.py +++ b/mesh_snap_utilities_line/__init__.py @@ -22,22 +22,24 @@ bl_info = { "name": "Snap_Utilities_Line", "author": "Germano Cavalcante", - "version": (5, 9, 00), + "version": (5, 9, 1), "blender": (2, 80, 0), "location": "View3D > TOOLS > Line Tool", "description": "Extends Blender Snap controls", - #"wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities", + "wiki_url" : "http://blenderartists.org/forum/showthread.php?363859-Addon-CAD-Snap-Utilities", "category": "Mesh"} if "bpy" in locals(): import importlib - importlib.reload(common_classes) + importlib.reload(navigation_ops) + importlib.reload(widgets) importlib.reload(preferences) - importlib.reload(ops_line) + importlib.reload(op_line) else: - from . import common_classes + from . import navigation_ops + from . import widgets from . import preferences - from . import ops_line + from . import op_line import bpy from bpy.utils.toolsystem import ToolDef @@ -105,7 +107,7 @@ def register_snap_tools(): tools[:index] += None, tool_line - del tool, tools, index + del tools, index keyconfigs = bpy.context.window_manager.keyconfigs kc_defaultconf = keyconfigs.get("blender") @@ -144,11 +146,11 @@ def unregister_snap_tools(): classes = ( preferences.SnapUtilitiesPreferences, - ops_line.SnapUtilitiesLine, - common_classes.VIEW3D_OT_rotate_custom_pivot, - common_classes.VIEW3D_OT_zoom_custom_target, - common_classes.SnapPointWidget, - common_classes.SnapPointWidgetGroup, + op_line.SnapUtilitiesLine, + navigation_ops.VIEW3D_OT_rotate_custom_pivot, + navigation_ops.VIEW3D_OT_zoom_custom_target, + widgets.SnapPointWidget, + widgets.SnapPointWidgetGroup, ) def register(): diff --git a/mesh_snap_utilities_line/common_classes.py b/mesh_snap_utilities_line/common_classes.py index a096415d..6ad150c4 100644 --- a/mesh_snap_utilities_line/common_classes.py +++ b/mesh_snap_utilities_line/common_classes.py @@ -16,222 +16,15 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -import bgl from mathutils import Vector - +from .drawing_utilities import SnapDrawn from .common_utilities import ( convert_distance, get_units_info, - snap_utilities, ) -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): - - import gpu - - 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 - - - def batch_line_strip_create(self, coords): - from gpu.types import ( - GPUVertBuf, - GPUBatch, - ) - - vbo = GPUVertBuf(self._format_pos, len = len(coords)) - vbo.attr_fill(0, data = coords) - batch_lines = GPUBatch(type = "LINE_STRIP", buf = vbo) - return batch_lines - - def batch_lines_smooth_color_create(self, coords, colors): - from gpu.types import ( - GPUVertBuf, - GPUBatch, - ) - - vbo = GPUVertBuf(self._format_pos_and_color, len = len(coords)) - vbo.attr_fill(0, data = coords) - vbo.attr_fill(1, data = colors) - batch_lines = GPUBatch(type = "LINES", buf = vbo) - return batch_lines - - def batch_triangles_create(self, coords): - from gpu.types import ( - GPUVertBuf, - GPUBatch, - ) - - vbo = GPUVertBuf(self._format_pos, len = len(coords)) - vbo.attr_fill(0, data = coords) - batch_tris = GPUBatch(type = "TRIS", buf = vbo) - return batch_tris - - def batch_point_get(self): - if self._batch_point is None: - from gpu.types import ( - GPUVertBuf, - GPUBatch, - ) - vbo = GPUVertBuf(self._format_pos, len = 1) - vbo.attr_fill(0, ((0.0, 0.0, 0.0),)) - self._batch_point = GPUBatch(type = "POINTS", buf = vbo) - return self._batch_point - - def draw(self, type, location, list_verts_co, vector_constrain, prevloc): - import gpu - - # 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): - #TODO: Cache coords (because antialiasing) - import gpu - 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) - - with gpu.matrix.push_pop(): - gpu.matrix.multiply_matrix(snap_obj.mat) - - if isinstance(elem, BMVert): - if elem.link_edges: - import numpy as np - - 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): - if len(snap_obj.data) == 2: - 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) - - class SnapNavigation(): @staticmethod def debug_key(key): @@ -400,90 +193,6 @@ class CharMap: self.line_pos = 0 -class VIEW3D_OT_rotate_custom_pivot(bpy.types.Operator): - bl_idname = "view3d.rotate_custom_pivot" - bl_label = "Rotate the view" - bl_options = {'BLOCKING', 'GRAB_CURSOR'} - - pivot: bpy.props.FloatVectorProperty("Pivot", subtype='XYZ') - g_up_axis: bpy.props.FloatVectorProperty("up_axis", default=(0.0, 0.0, 1.0), subtype='XYZ') - sensitivity: bpy.props.FloatProperty("sensitivity", default=0.007) - - def modal(self, context, event): - from mathutils import Matrix - if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}: - dx = self.init_coord[0] - event.mouse_region_x - dy = self.init_coord[1] - event.mouse_region_y - rot_ver = Matrix.Rotation(-dx * self.sensitivity, 3, self.g_up_axis) - rot_hor = Matrix.Rotation(dy * self.sensitivity, 3, self.view_rot[0]) - rot_mat = rot_hor @ rot_ver - view_matrix = self.view_rot @ rot_mat - - pos = self.pos1 @ rot_mat + self.pivot - qua = view_matrix.to_quaternion() - qua.invert() - - self.rv3d.view_location = pos - self.rv3d.view_rotation = qua - - context.area.tag_redraw() - return {'RUNNING_MODAL'} - - return {'FINISHED'} - - def invoke(self, context, event): - self.rv3d = context.region_data - self.init_coord = event.mouse_region_x, event.mouse_region_y - self.pos1 = self.rv3d.view_location - self.pivot - self.view_rot = self.rv3d.view_matrix.to_3x3() - - context.window_manager.modal_handler_add(self) - return {'RUNNING_MODAL'} - - -class VIEW3D_OT_zoom_custom_target(bpy.types.Operator): - bl_idname = "view3d.zoom_custom_target" - bl_label = "Zoom the view" - bl_options = {'BLOCKING', 'GRAB_CURSOR'} - - target: bpy.props.FloatVectorProperty("target", subtype='XYZ') - delta: bpy.props.IntProperty("delta", default=0) - step_factor = 0.333 - - def modal(self, context, event): - if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}: - if not hasattr(self, "init_mouse_region_y"): - self.init_mouse_region_y = event.mouse_region_y - self.heigt_up = context.area.height - self.init_mouse_region_y - self.rv3d.view_location = self.target - - fac = (event.mouse_region_y - self.init_mouse_region_y) / self.heigt_up - ret = 'RUNNING_MODAL' - else: - fac = self.step_factor * self.delta - ret = 'FINISHED' - - self.rv3d.view_location = self.init_loc + (self.target - self.init_loc) * fac - self.rv3d.view_distance = self.init_dist - self.init_dist * fac - - context.area.tag_redraw() - return {ret} - - def invoke(self, context, event): - v3d = context.space_data - dist_range = (v3d.clip_start, v3d.clip_end) - self.rv3d = context.region_data - self.init_dist = self.rv3d.view_distance - if ((self.delta <= 0 and self.init_dist < dist_range[1]) or - (self.delta > 0 and self.init_dist > dist_range[0])): - self.init_loc = self.rv3d.view_location.copy() - - context.window_manager.modal_handler_add(self) - return {'RUNNING_MODAL'} - - return {'FINISHED'} - - class SnapUtilities: # __slots__ = ( # "sctx", @@ -512,11 +221,66 @@ class SnapUtilities: widget = SnapUtilities.snapwidgets[-1] if SnapUtilities.snapwidgets else None if SnapUtilities.constrain == key: SnapUtilities.constrain = None + if hasattr(widget, "get_normal"): + widget.get_normal(context) return + if hasattr(widget, "normal"): + if key == 'shift': + import bmesh + if isinstance(widget.geom, bmesh.types.BMEdge): + verts = widget.geom.verts + widget.normal = verts[1].co - verts[0].co + widget.normal.normalise() + else: + return + else: + widget.normal = SnapUtilities.constrain_keys[key] + SnapUtilities.constrain = key + def snap_context_update_and_return_moving_objects(self, context): + moving_objects = set() + moving_snp_objects = set() + children = set() + for obj in context.view_layer.objects.selected: + moving_objects.add(obj) + + temp_children = set() + for obj in context.visible_objects: + temp_children.clear() + while obj.parent is not None: + temp_children.add(obj) + parent = obj.parent + if parent in moving_objects: + children.update(temp_children) + temp_children.clear() + obj = parent + + del temp_children + + moving_objects.difference_update(children) + + self.sctx.clear_snap_objects() + + for obj in context.visible_objects: + is_moving = obj in moving_objects or obj in children + snap_obj = self.sctx.add_obj(obj, obj.matrix_world) + if is_moving: + moving_snp_objects.add(snap_obj) + + if obj.instance_type == 'COLLECTION': + mat = obj.matrix_world.copy() + for ob in obj.instance_collection.objects: + snap_obj = self.sctx.add_obj(obj, mat @ ob.matrix_world) + if is_moving: + moving_snp_objects.add(snap_obj) + + del children + return moving_objects, moving_snp_objects + + def snap_context_update(self, context): def visible_objects_and_duplis(): if self.preferences.outer_verts: @@ -554,6 +318,11 @@ class SnapUtilities: self.location = widget.location self.preferences = widget.preferences self.draw_cache = widget.draw_cache + if hasattr(widget, "normal"): + self.normal = widget.normal + #loc = widget.location + #self.vector_constrain = [loc, loc + widget.normal, self.constrain] + else: #init these variables to avoid errors self.snap_obj = None @@ -619,148 +388,3 @@ class SnapUtilities: del self.preferences SnapUtilities.constrain = None - - -#def mesh_runtime_batchcache_isdirty(me): -# import ctypes -# batch_cache = ctypes.c_void_p.from_address(me.as_pointer() + 1440) -# if batch_cache: -# return ctypes.c_bool.from_address(batch_cache.value + 549).value -# return False - - -class SnapWidgetCommon(SnapUtilities): - snap_to_update = False - - def handler(self, scene): - cls = SnapWidgetCommon - if cls.snap_to_update is False: - last_operator = self.wm_operators[-1] if self.wm_operators else None - if (not last_operator or - last_operator.name not in {'Select', 'Loop Select', '(De)select All'}): - cls.snap_to_update = self.depsgraph.id_type_updated('MESH') or\ - self.depsgraph.id_type_updated('OBJECT') - - def draw_point_and_elem(self): - if self.bm: - if self.bm.is_valid and self.geom.is_valid: - self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom) - else: - self.bm = None - self.geom = None - self.sctx.update_all() - - self.draw_cache.draw(self.type, self.location, None, None, None) - - def init_snapwidget(self, context, snap_edge_and_vert = True): - self.snap_context_init(context, snap_edge_and_vert) - self.snap_context_update(context) - self.mode = context.mode - self.last_mval = None - - self.wm_operators = context.window_manager.operators - self.depsgraph = context.depsgraph - bpy.app.handlers.depsgraph_update_post.append(self.handler) - SnapWidgetCommon.snap_to_update = False - - SnapUtilities.snapwidgets.append(self) - - def end_snapwidget(self): - SnapUtilities.snapwidgets.remove(self) - - #from .snap_context_l import global_snap_context_get - #sctx = global_snap_context_get(None, None, None) - - StructRNA = bpy.types.bpy_struct - sctx = super(StructRNA, self).__getattribute__("sctx") - if sctx and not SnapUtilities.snapwidgets: - sctx.clear_snap_objects() - - handler = super(StructRNA, self).__getattribute__("handler") - bpy.app.handlers.depsgraph_update_post.remove(handler) - - def update_snap(self, context, mval): - if self.last_mval == mval: - return -1 - else: - self.last_mval = mval - - if (SnapWidgetCommon.snap_to_update): - ## Something has changed since the last time. - # Has the mesh been changed? - # In the doubt lets clear the snap context. - self.snap_context_update(context) - SnapWidgetCommon.snap_to_update = False - - #print('test_select', mval) - space = context.space_data - self.sctx.update_viewport_context(context.depsgraph, context.region, space, True) - - shading = space.shading - snap_face = not ((self.snap_vert or self.snap_edge) and - (shading.show_xray or shading.type == 'WIREFRAME')) - - if snap_face != self.snap_face: - self.snap_face = snap_face - self.sctx.set_snap_mode( - self.snap_vert, self.snap_edge, self.snap_face) - - self.snap_obj, prev_loc, self.location, self.type, self.bm, self.geom, len = snap_utilities( - self.sctx, - None, - mval, - increment=self.incremental - ) - - -class SnapPointWidget(SnapWidgetCommon, bpy.types.Gizmo): - bl_idname = "VIEW3D_GT_snap_point" - - def test_select(self, context, mval): - self.update_snap(context, mval) - self.snap_to_grid() - - context.area.tag_redraw() - return -1 - - def draw(self, context): - self.draw_point_and_elem() - - def setup(self): - self.init_snapwidget(bpy.context) - - -def context_mode_check(context, widget_group): - workspace = context.workspace - mode = workspace.tools_mode - for tool in workspace.tools: - if (tool.widget == widget_group) and (tool.mode == mode): - break - else: - return False - return True - - -class SnapWidgetGroupCommon: - bl_space_type = 'VIEW_3D' - bl_region_type = 'WINDOW' - bl_options = {'3D'} - - @classmethod - def poll(cls, context): - return context_mode_check(context, cls.bl_idname) - - def init_tool(self, context, gizmo_name): - self.widget = self.gizmos.new(gizmo_name) - - def __del__(self): - if hasattr(self, "widget"): - super(bpy.types.bpy_struct, self.widget).__getattribute__("end_snapwidget")() - - -class SnapPointWidgetGroup(SnapWidgetGroupCommon, bpy.types.GizmoGroup): - bl_idname = "MESH_GGT_snap_point" - bl_label = "Draw Snap Point" - - def setup(self, context): - self.init_tool(context, SnapPointWidget.bl_idname) diff --git a/mesh_snap_utilities_line/common_utilities.py b/mesh_snap_utilities_line/common_utilities.py index 2fc0ce6d..a6ecbdc9 100644 --- a/mesh_snap_utilities_line/common_utilities.py +++ b/mesh_snap_utilities_line/common_utilities.py @@ -20,15 +20,16 @@ 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, ) +from .snap_context_l import SnapContext + def get_units_info(scale, unit_system, separate_units): if unit_system == 'METRIC': @@ -149,15 +150,10 @@ def get_snap_bm_geom(sctx, main_snap_obj, mcursor): class SnapCache: snp_obj = None - elem = None + edge = None - v0 = None - v1 = None vmid = None vperp = None - - v2d0 = None - v2d1 = None v2dmid = None v2dperp = None @@ -198,20 +194,18 @@ def snap_utilities( r_loc = loc elif len(elem) == 2: - if SnapCache.snp_obj is not snp_obj or not (elem == SnapCache.elem).all(): + if SnapCache.snp_obj is not snp_obj or not (elem == SnapCache.edge).all(): SnapCache.snp_obj = snp_obj - SnapCache.elem = elem + SnapCache.edge = 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) + v0 = elem_co[0] + v1 = elem_co[1] + SnapCache.vmid = 0.5 * (v0 + 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) + perp_point = intersect_point_line(pvert_co, v0, v1) SnapCache.vperp = perp_point[0] #factor = point_perpendicular[1] SnapCache.v2dperp = location_3d_to_region_2d(sctx.region, sctx.rv3d, perp_point[0]) @@ -222,7 +216,7 @@ def snap_utilities( #else: SnapCache.v2dperp = None if constrain: - t_loc = intersect_line_line(constrain[0], constrain[1], SnapCache.v0, SnapCache.v1) + t_loc = intersect_line_line(constrain[0], constrain[1], elem_co[0], elem_co[1]) if t_loc is None: is_increment = True @@ -266,4 +260,4 @@ def snap_utilities( return snp_obj, loc, r_loc, r_type, bm, bm_geom, r_len -snap_utilities.cache = SnapCache +snap_utilities.edge_cache = SnapCache diff --git a/mesh_snap_utilities_line/drawing_utilities.py b/mesh_snap_utilities_line/drawing_utilities.py new file mode 100644 index 00000000..92facecc --- /dev/null +++ b/mesh_snap_utilities_line/drawing_utilities.py @@ -0,0 +1,224 @@ +### 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 +from mathutils import Vector + + +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): + + import gpu + + 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 + + + def batch_line_strip_create(self, coords): + from gpu.types import ( + GPUVertBuf, + GPUBatch, + ) + + vbo = GPUVertBuf(self._format_pos, len = len(coords)) + vbo.attr_fill(0, data = coords) + batch_lines = GPUBatch(type = "LINE_STRIP", buf = vbo) + return batch_lines + + def batch_lines_smooth_color_create(self, coords, colors): + from gpu.types import ( + GPUVertBuf, + GPUBatch, + ) + + vbo = GPUVertBuf(self._format_pos_and_color, len = len(coords)) + vbo.attr_fill(0, data = coords) + vbo.attr_fill(1, data = colors) + batch_lines = GPUBatch(type = "LINES", buf = vbo) + return batch_lines + + def batch_triangles_create(self, coords): + from gpu.types import ( + GPUVertBuf, + GPUBatch, + ) + + vbo = GPUVertBuf(self._format_pos, len = len(coords)) + vbo.attr_fill(0, data = coords) + batch_tris = GPUBatch(type = "TRIS", buf = vbo) + return batch_tris + + def batch_point_get(self): + if self._batch_point is None: + from gpu.types import ( + GPUVertBuf, + GPUBatch, + ) + vbo = GPUVertBuf(self._format_pos, len = 1) + vbo.attr_fill(0, ((0.0, 0.0, 0.0),)) + self._batch_point = GPUBatch(type = "POINTS", buf = vbo) + return self._batch_point + + def draw(self, type, location, list_verts_co, vector_constrain, prevloc): + import gpu + + # 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): + #TODO: Cache coords (because antialiasing) + import gpu + 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) + + with gpu.matrix.push_pop(): + gpu.matrix.multiply_matrix(snap_obj.mat) + + if isinstance(elem, BMVert): + if elem.link_edges: + import numpy as np + + 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): + if len(snap_obj.data) == 2: + 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) diff --git a/mesh_snap_utilities_line/navigation_ops.py b/mesh_snap_utilities_line/navigation_ops.py new file mode 100644 index 00000000..21eecc19 --- /dev/null +++ b/mesh_snap_utilities_line/navigation_ops.py @@ -0,0 +1,102 @@ +### 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 + + +class VIEW3D_OT_rotate_custom_pivot(bpy.types.Operator): + bl_idname = "view3d.rotate_custom_pivot" + bl_label = "Rotate the view" + bl_options = {'BLOCKING', 'GRAB_CURSOR'} + + pivot: bpy.props.FloatVectorProperty("Pivot", subtype='XYZ') + g_up_axis: bpy.props.FloatVectorProperty("up_axis", default=(0.0, 0.0, 1.0), subtype='XYZ') + sensitivity: bpy.props.FloatProperty("sensitivity", default=0.007) + + def modal(self, context, event): + from mathutils import Matrix + if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}: + dx = self.init_coord[0] - event.mouse_region_x + dy = self.init_coord[1] - event.mouse_region_y + rot_ver = Matrix.Rotation(-dx * self.sensitivity, 3, self.g_up_axis) + rot_hor = Matrix.Rotation(dy * self.sensitivity, 3, self.view_rot[0]) + rot_mat = rot_hor @ rot_ver + view_matrix = self.view_rot @ rot_mat + + pos = self.pos1 @ rot_mat + self.pivot + qua = view_matrix.to_quaternion() + qua.invert() + + self.rv3d.view_location = pos + self.rv3d.view_rotation = qua + + context.area.tag_redraw() + return {'RUNNING_MODAL'} + + return {'FINISHED'} + + def invoke(self, context, event): + self.rv3d = context.region_data + self.init_coord = event.mouse_region_x, event.mouse_region_y + self.pos1 = self.rv3d.view_location - self.pivot + self.view_rot = self.rv3d.view_matrix.to_3x3() + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + +class VIEW3D_OT_zoom_custom_target(bpy.types.Operator): + bl_idname = "view3d.zoom_custom_target" + bl_label = "Zoom the view" + bl_options = {'BLOCKING', 'GRAB_CURSOR'} + + target: bpy.props.FloatVectorProperty("target", subtype='XYZ') + delta: bpy.props.IntProperty("delta", default=0) + step_factor = 0.333 + + def modal(self, context, event): + if event.value == 'PRESS' and event.type in {'MOUSEMOVE', 'INBETWEEN_MOUSEMOVE'}: + if not hasattr(self, "init_mouse_region_y"): + self.init_mouse_region_y = event.mouse_region_y + self.heigt_up = context.area.height - self.init_mouse_region_y + self.rv3d.view_location = self.target + + fac = (event.mouse_region_y - self.init_mouse_region_y) / self.heigt_up + ret = 'RUNNING_MODAL' + else: + fac = self.step_factor * self.delta + ret = 'FINISHED' + + self.rv3d.view_location = self.init_loc + (self.target - self.init_loc) * fac + self.rv3d.view_distance = self.init_dist - self.init_dist * fac + + context.area.tag_redraw() + return {ret} + + def invoke(self, context, event): + v3d = context.space_data + dist_range = (v3d.clip_start, v3d.clip_end) + self.rv3d = context.region_data + self.init_dist = self.rv3d.view_distance + if ((self.delta <= 0 and self.init_dist < dist_range[1]) or + (self.delta > 0 and self.init_dist > dist_range[0])): + self.init_loc = self.rv3d.view_location.copy() + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + + return {'FINISHED'} diff --git a/mesh_snap_utilities_line/ops_line.py b/mesh_snap_utilities_line/op_line.py index 891a6d29..585792bd 100644 --- a/mesh_snap_utilities_line/ops_line.py +++ b/mesh_snap_utilities_line/op_line.py @@ -15,24 +15,20 @@ # # ##### END GPL LICENSE BLOCK ##### -import bpy, bmesh - -from bpy.props import FloatProperty +import bpy +import bmesh from mathutils import Vector - from mathutils.geometry import intersect_point_line +from .drawing_utilities import SnapDrawn +from .common_utilities import snap_utilities from .common_classes import ( - SnapDrawn, CharMap, SnapNavigation, SnapUtilities, ) -from .common_utilities import ( - snap_utilities, - ) if not __package__: __package__ = "mesh_snap_utilities_line" @@ -276,7 +272,7 @@ class SnapUtilitiesLine(SnapUtilities, bpy.types.Operator): 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 + snap_utilities.edge_cache.snp_obj = None # hack for snap edge elemens update else: self.bool_update = False @@ -381,7 +377,7 @@ class SnapUtilitiesLine(SnapUtilities, bpy.types.Operator): self._exit(context) return {'FINISHED'} else: - snap_utilities.cache.snp_obj = None # hack for snap edge elemens update + snap_utilities.edge_cache.snp_obj = None # hack for snap edge elemens update self.vector_constrain = None self.list_edges = [] self.list_verts = [] diff --git a/mesh_snap_utilities_line/preferences.py b/mesh_snap_utilities_line/preferences.py index af2ca231..7361284e 100644 --- a/mesh_snap_utilities_line/preferences.py +++ b/mesh_snap_utilities_line/preferences.py @@ -68,9 +68,9 @@ class SnapUtilitiesPreferences(bpy.types.AddonPreferences): 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) + face_color: FloatVectorProperty(name="FACE", default=(1.0, 0.8, 0.0, 0.5), size=4, subtype="COLOR", min=0, max=1) + edge_color: FloatVectorProperty(name="EDGE", default=(0.0, 0.8, 1.0, 0.5), size=4, subtype="COLOR", min=0, max=1) + vert_color: FloatVectorProperty(name="VERT", default=(1.0, 0.5, 0.0, 0.5), 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) diff --git a/mesh_snap_utilities_line/snap_context_l/__init__.py b/mesh_snap_utilities_line/snap_context_l/__init__.py index 3f663434..2aab0a3e 100644 --- a/mesh_snap_utilities_line/snap_context_l/__init__.py +++ b/mesh_snap_utilities_line/snap_context_l/__init__.py @@ -329,7 +329,7 @@ class SnapContext(): for snap_obj in self.snap_objects: if len(snap_obj.data) == 2: snap_obj.data[1].free() - snap_obj.data.pop(1) + del snap_obj.data[1:] del snap_obj.data del snap_obj.mat @@ -368,7 +368,7 @@ class SnapContext(): for snap_obj in self.snap_objects: if len(snap_obj.data) == 2: snap_obj.data[1].free() - snap_obj.data.pop(1) + del snap_obj.data[1:] self.update_drawing() @@ -380,7 +380,7 @@ class SnapContext(): def tag_update_drawn_snap_object(self, snap_obj): if len(snap_obj.data) > 1: snap_obj.data[1].free() - snap_obj.data.pop(1) + del snap_obj.data[1:] #self.update_drawing() # Update on next snap_get call # self.proj_mat = None @@ -395,7 +395,7 @@ class SnapContext(): if len(snap_obj.data) > 1: snap_obj.data[1].free() - snap_obj.data.pop(1) + del snap_obj.data[1:] data = GPU_Indices_Mesh(self.depsgraph, snap_obj.data[0], snap_face, snap_edge, snap_vert) snap_obj.data.append(data) diff --git a/mesh_snap_utilities_line/widgets.py b/mesh_snap_utilities_line/widgets.py new file mode 100644 index 00000000..6f8639e0 --- /dev/null +++ b/mesh_snap_utilities_line/widgets.py @@ -0,0 +1,244 @@ +### 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 .common_classes import SnapUtilities +from .common_utilities import snap_utilities +from .drawing_utilities import SnapDrawn + + +#def mesh_runtime_batchcache_isdirty(me): +# import ctypes +# batch_cache = ctypes.c_void_p.from_address(me.as_pointer() + 1440) +# if batch_cache: +# return ctypes.c_bool.from_address(batch_cache.value + 549).value +# return False + + +class SnapWidgetCommon(SnapUtilities): + snap_to_update = False + + def handler(self, scene): + cls = SnapWidgetCommon + if cls.snap_to_update is False: + last_operator = self.wm_operators[-1] if self.wm_operators else None + if (not last_operator or + last_operator.name not in {'Select', 'Loop Select', '(De)select All'}): + cls.snap_to_update = self.depsgraph.id_type_updated('MESH') or\ + self.depsgraph.id_type_updated('OBJECT') + + def draw_point_and_elem(self): + if self.bm: + if self.bm.is_valid and self.geom.is_valid: + self.draw_cache.draw_elem(self.snap_obj, self.bm, self.geom) + else: + self.bm = None + self.geom = None + self.sctx.update_all() + + self.draw_cache.draw(self.type, self.location, None, None, None) + + def init_snapwidget(self, context, snap_edge_and_vert = True): + self.snap_context_init(context, snap_edge_and_vert) + self.snap_context_update(context) + self.mode = context.mode + self.last_mval = None + + self.wm_operators = context.window_manager.operators + self.depsgraph = context.depsgraph + bpy.app.handlers.depsgraph_update_post.append(self.handler) + SnapWidgetCommon.snap_to_update = False + + SnapUtilities.snapwidgets.append(self) + + def end_snapwidget(self): + SnapUtilities.snapwidgets.remove(self) + + #from .snap_context_l import global_snap_context_get + #sctx = global_snap_context_get(None, None, None) + + sctx = object.__getattribute__(self, 'sctx') + if sctx and not SnapUtilities.snapwidgets: + sctx.clear_snap_objects() + + handler = object.__getattribute__(self, 'handler') + bpy.app.handlers.depsgraph_update_post.remove(handler) + + def update_snap(self, context, mval): + if self.last_mval == mval: + return -1 + else: + self.last_mval = mval + + if (SnapWidgetCommon.snap_to_update): + ## Something has changed since the last time. + # Has the mesh been changed? + # In the doubt lets clear the snap context. + self.snap_context_update(context) + SnapWidgetCommon.snap_to_update = False + + #print('test_select', mval) + space = context.space_data + self.sctx.update_viewport_context(context.depsgraph, context.region, space, True) + + shading = space.shading + snap_face = not ((self.snap_vert or self.snap_edge) and + (shading.show_xray or shading.type == 'WIREFRAME')) + + if snap_face != self.snap_face: + self.snap_face = snap_face + self.sctx.set_snap_mode( + self.snap_vert, self.snap_edge, self.snap_face) + + snap_utilities.edge_cache.snp_obj = None # hack for snap edge elemens update + self.snap_obj, prev_loc, self.location, self.type, self.bm, self.geom, len = snap_utilities( + self.sctx, + None, + mval, + increment=self.incremental + ) + + +class SnapPointWidget(SnapWidgetCommon, bpy.types.Gizmo): + bl_idname = "VIEW3D_GT_snap_point" + + def test_select(self, context, mval): + self.update_snap(context, mval) + self.snap_to_grid() + + context.area.tag_redraw() + return -1 + + def draw(self, context): + self.draw_point_and_elem() + + def setup(self): + self.init_snapwidget(bpy.context) + + +class SnapFaceWidget(SnapWidgetCommon, bpy.types.Gizmo): + bl_idname = "VIEW3D_GT_snap_face_point" + + def test_select(self, context, mval): + self.update_snap(context, mval) + self.snap_to_grid() + + context.area.tag_redraw() + return -1 + + def draw(self, context): + self.draw_point_and_elem() + + def setup(self): + self.init_snapwidget(bpy.context, False) + + +class SnapCircleWidget(SnapWidgetCommon, bpy.types.Gizmo): + bl_idname = "VIEW3D_GT_snap_circle" + + from mathutils import Vector + zero_vector = Vector() + default_normal = Vector((0.0, 0.0, 1.0)) + + def get_normal(self, context): + if self.constrain: + return + + view_vector, orig = self.sctx.last_ray + if not self.rv3d.is_perspective: + #temporary hack + orig -= view_vector * 100 + + normal_face = context.scene.ray_cast(context.view_layer, orig, view_vector)[2] + if normal_face and normal_face != self.zero_vector: + self.normal = normal_face + else: + self.normal = self.default_normal + + def test_select(self, context, mval): + self.update_snap(context, mval) + self.snap_to_grid() + self.get_normal(context) + + context.area.tag_redraw() + return -1 + + def draw(self, context): + self.draw_point_and_elem() + self.draw_cache.draw_rot_tool( + self.rv3d.view_distance / 15, + self.location, self.normal, None) + + def setup(self): + context = bpy.context + self.init_snapwidget(context) + self.rv3d = context.region_data + self.normal = self.default_normal + + +def context_mode_check(context, widget_group): + workspace = context.workspace + mode = workspace.tools_mode + for tool in workspace.tools: + if (tool.widget == widget_group) and (tool.mode == mode): + break + else: + context.window_manager.gizmo_group_type_unlink_delayed(widget_group) + return False + return True + + +class SnapWidgetGroupCommon: + bl_space_type = 'VIEW_3D' + bl_region_type = 'WINDOW' + bl_options = {'3D'} + + @classmethod + def poll(cls, context): + return context_mode_check(context, cls.bl_idname) + + def init_tool(self, context, gizmo_name): + self.widget = self.gizmos.new(gizmo_name) + + def __del__(self): + if hasattr(self, "widget"): + object.__getattribute__(self.widget, 'end_snapwidget')() + + +class SnapPointWidgetGroup(SnapWidgetGroupCommon, bpy.types.GizmoGroup): + bl_idname = "MESH_GGT_snap_point" + bl_label = "Draw Snap Point" + + def setup(self, context): + self.init_tool(context, SnapPointWidget.bl_idname) + + +class SnapCircleWidgetGroup(SnapWidgetGroupCommon, bpy.types.GizmoGroup): + bl_idname = "MESH_GGT_snap_circle" + bl_label = "Draw Snap Circle" + + def setup(self, context): + self.init_tool(context, SnapCircleWidget.bl_idname) + + +class SnapFaceWidgetGroup(SnapWidgetGroupCommon, bpy.types.GizmoGroup): + bl_idname = "MESH_GGT_face_snap_point" + bl_label = "Draw Face and Snap Point" + + def setup(self, context): + self.init_tool(context, SnapFaceWidget.bl_idname) |