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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'mesh_snap_utilities_line/ops_line.py')
-rw-r--r--mesh_snap_utilities_line/ops_line.py551
1 files changed, 551 insertions, 0 deletions
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()