hub/venv/lib/python3.7/site-packages/trimesh/path/repair.py

116 lines
3.6 KiB
Python

"""
repair.py
--------------
Try to fix problems with closed regions.
"""
from . import segments
from .. import util
import numpy as np
from scipy.spatial import cKDTree
def fill_gaps(path, distance=.025):
"""
For 3D line segments defined by two points, turn
them in to an origin defined as the closest point along
the line to the zero origin as well as a direction vector
and start and end parameter.
Parameters
------------
segments : (n, 2, 3) float
Line segments defined by start and end points
Returns
--------------
origins : (n, 3) float
Point on line closest to [0, 0, 0]
vectors : (n, 3) float
Unit line directions
parameters : (n, 2) float
Start and end distance pairs for each line
"""
# find any vertex without degree 2 (connected to two things)
broken = np.array([
k for k, d in dict(path.vertex_graph.degree()).items()
if d != 2])
# if all vertices have correct connectivity, exit
if len(broken) == 0:
return
# first find broken vertices with distance
tree = cKDTree(path.vertices[broken])
pairs = tree.query_pairs(r=distance, output_type='ndarray')
connect_seg = []
if len(pairs) > 0:
end_points = {tuple(sorted(e.end_points)) for e in path.entities}
pair_set = {tuple(i) for i in np.sort(broken[pairs], axis=1)}
# we don't want to connect entities to themselves so do a set
# difference
mask = np.array(list(pair_set.difference(end_points)))
if len(mask) > 0:
connect_seg = path.vertices[mask]
# a set of values we can query intersections with quickly
broken_set = set(broken)
# query end points set vs path.dangling to avoid having
# to compute every single path and discrete curve
dangle = [i for i, e in enumerate(path.entities) if
len(broken_set.intersection(e.end_points)) > 0]
segs = []
# mask for which entities to keep
keep = np.ones(len(path.entities), dtype=np.bool)
# save a reference to the line class to avoid circular import
line_class = None
for entity_index in dangle:
# only consider line entities
if path.entities[entity_index].__class__.__name__ != 'Line':
continue
if line_class is None:
line_class = path.entities[entity_index].__class__
# get discrete version of entity
points = path.entities[entity_index].discrete(path.vertices)
# turn connected curve into segments
seg_idx = util.stack_lines(np.arange(len(points)))
# append the segments to our collection
segs.append(points[seg_idx])
# remove this entity and replace with segments
keep[entity_index] = False
# combine segments with connection segments
all_segs = util.vstack_empty((util.vstack_empty(segs),
connect_seg))
# go home early
if len(all_segs) == 0:
return
# split segments at broken vertices so topology can happen
split = segments.split(all_segs, path.vertices[broken])
# merge duplicate segments
final_seg = segments.unique(split)
# add line segments in as line entities
entities = []
for i, seg in enumerate(final_seg):
entities.append(
line_class(
points=np.arange(2) + (i * 2) + len(path.vertices)))
# replace entities with new entities
path.entities = np.append(path.entities[keep], entities)
path.vertices = np.vstack((path.vertices, np.vstack(final_seg)))
path._cache.clear()
path.process()