116 lines
3.6 KiB
Python
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()
|