Files
Campbell Barton 016430de4b Cleanup: remove <pep8 compliant> comment
This is no longer necessary, see: T98554.
2022-06-03 11:50:32 +10:00

268 lines
7.3 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
#
# ----------------------------------------------------------
# Author: Zeffii
# Modified by: Alan Odom (Clockmender) & Rune Morling (ermo)
# ----------------------------------------------------------
#
import bpy
import bmesh
from mathutils.geometry import intersect_line_line as LineIntersect
import itertools
from collections import defaultdict
from . import pdt_cad_module as cm
from .pdt_functions import oops
from .pdt_msg_strings import (
PDT_ERR_EDOB_MODE
)
def order_points(edge, point_list):
"""Order these edges from distance to v1, then sandwich the sorted list with v1, v2."""
v1, v2 = edge
def dist(coord):
"""Measure distance between two coordinates."""
return (v1 - coord).length
point_list = sorted(point_list, key=dist)
return [v1] + point_list + [v2]
def remove_permutations_that_share_a_vertex(bm, permutations):
"""Get useful Permutations.
Args:
bm: Object's Bmesh
permutations: Possible Intersection Edges as a list
Returns:
List of Edges.
"""
final_permutations = []
for edges in permutations:
raw_vert_indices = cm.vertex_indices_from_edges_tuple(bm, edges)
if len(set(raw_vert_indices)) < 4:
continue
# reaches this point if they do not share.
final_permutations.append(edges)
return final_permutations
def get_valid_permutations(bm, edge_indices):
"""Get useful Permutations.
Args:
bm: Object's Bmesh
edge_indices: List of indices of Edges to consider
Returns:
List of suitable Edges.
"""
raw_permutations = itertools.permutations(edge_indices, 2)
permutations = [r for r in raw_permutations if r[0] < r[1]]
return remove_permutations_that_share_a_vertex(bm, permutations)
def can_skip(closest_points, vert_vectors):
"""Check if the intersection lies on both edges and return True
when criteria are not met, and thus this point can be skipped.
Args:
closest_points: List of Coordinates of points to consider
vert_vectors: List of Coordinates of vertices to consider
Returns:
Boolean.
"""
if not closest_points:
return True
if not isinstance(closest_points[0].x, float):
return True
if cm.num_edges_point_lies_on(closest_points[0], vert_vectors) < 2:
return True
# if this distance is larger than than 1.0e-5, we can skip it.
cpa, cpb = closest_points
return (cpa - cpb).length > 1.0e-5
def get_intersection_dictionary(bm, edge_indices):
"""Return a dictionary of edge indices and points found on those edges.
Args:
bm, Object's Bmesh
edge_indices: List of Edge Indices
Returns:
Dictionary of Vectors.
"""
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
permutations = get_valid_permutations(bm, edge_indices)
list_k = defaultdict(list)
list_d = defaultdict(list)
for edges in permutations:
raw_vert_indices = cm.vertex_indices_from_edges_tuple(bm, edges)
vert_vectors = cm.vectors_from_indices(bm, raw_vert_indices)
points = LineIntersect(*vert_vectors)
# some can be skipped. (NaN, None, not on both edges)
if can_skip(points, vert_vectors):
continue
# reaches this point only when an intersection happens on both edges.
[list_k[edge].append(points[0]) for edge in edges]
# list_k will contain a dict of edge indices and points found on those edges.
for edge_idx, unordered_points in list_k.items():
tv1, tv2 = bm.edges[edge_idx].verts
v1 = bm.verts[tv1.index].co
v2 = bm.verts[tv2.index].co
ordered_points = order_points((v1, v2), unordered_points)
list_d[edge_idx].extend(ordered_points)
return list_d
def update_mesh(bm, int_dict):
"""Make new geometry (delete old first).
Args:
bm, Object's Bmesh
int_dict: Dictionary of Indices of Vertices
Returns:
Nothing.
"""
orig_e = bm.edges
orig_v = bm.verts
new_verts = []
collect = new_verts.extend
for _, point_list in int_dict.items():
num_edges_to_add = len(point_list) - 1
for i in range(num_edges_to_add):
coord_a = orig_v.new(point_list[i])
coord_b = orig_v.new(point_list[i + 1])
orig_e.new((coord_a, coord_b))
bm.normal_update()
collect([coord_a, coord_b])
bmesh.ops.delete(bm, geom=[edge for edge in bm.edges if edge.select], context="EDGES")
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
def unselect_nonintersecting(bm, d_edges, edge_indices):
"""Deselects Non-Intersection Edges.
Args:
bm, Object's Bmesh
d_edges: List of Intersecting Edges
edge_indices: List of Edge Indices to consider
Returns:
Nothing.
"""
if len(edge_indices) > len(d_edges):
reserved_edges = set(edge_indices) - set(d_edges)
for edge in reserved_edges:
bm.edges[edge].select = False
def intersect_all(context):
"""Computes All intersections with Crossing Geometry.
Note:
Deletes original edges and replaces with new intersected edges
Args:
context: Blender bpy.context instance.
Returns:
Status Set.
"""
pg = context.scene.pdt_pg
obj = context.active_object
if all([bool(obj), obj.type == "MESH", obj.mode == "EDIT"]):
# must force edge selection mode here
bpy.context.tool_settings.mesh_select_mode = (False, True, False)
if obj.mode == "EDIT":
bm = bmesh.from_edit_mesh(obj.data)
selected_edges = [edge for edge in bm.edges if edge.select]
edge_indices = [i.index for i in selected_edges]
int_dict = get_intersection_dictionary(bm, edge_indices)
unselect_nonintersecting(bm, int_dict.keys(), edge_indices)
update_mesh(bm, int_dict)
bmesh.update_edit_mesh(obj.data)
else:
pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})"
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
return
return
else:
pg.error = f"{PDT_ERR_EDOB_MODE},{obj.mode})"
context.window_manager.popup_menu(oops, title="Error", icon="ERROR")
return
class PDT_OT_IntersectAllEdges(bpy.types.Operator):
"""Cut Selected Edges at All Intersections"""
bl_idname = "pdt.intersectall"
bl_label = "Intersect All Edges"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
"""Check to see object is in correct condition.
Args:
context: Blender bpy.context instance.
Returns:
Boolean
"""
obj = context.active_object
if obj is None:
return False
return obj is not None and obj.type == "MESH" and obj.mode == "EDIT"
def execute(self, context):
"""Computes All intersections with Crossing Geometry.
Note:
Deletes original edges and replaces with new intersected edges
Args:
context: Blender bpy.context instance.
Returns:
Status Set.
"""
pg = context.scene.pdt_pg
pg.command = f"intall"
return {"FINISHED"}