diff options
Diffstat (limited to 'mesh_snap_utilities_line/ops_line.py')
-rw-r--r-- | mesh_snap_utilities_line/ops_line.py | 551 |
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() |