216 lines
6.0 KiB
Python
216 lines
6.0 KiB
Python
|
import numpy as np
|
||
|
|
||
|
from ... import util
|
||
|
from ... import graph
|
||
|
from ... import grouping
|
||
|
|
||
|
from ..entities import Line, Arc
|
||
|
|
||
|
from collections import deque
|
||
|
|
||
|
|
||
|
def dict_to_path(as_dict):
|
||
|
"""
|
||
|
Turn a pure dict into a dict containing entity objects that
|
||
|
can be sent directly to a Path constructor.
|
||
|
|
||
|
Parameters
|
||
|
------------
|
||
|
as_dict : dict
|
||
|
Has keys: 'vertices', 'entities'
|
||
|
|
||
|
Returns
|
||
|
------------
|
||
|
kwargs : dict
|
||
|
Has keys: 'vertices', 'entities'
|
||
|
"""
|
||
|
# start kwargs with initial value
|
||
|
result = as_dict.copy()
|
||
|
# map of constructors
|
||
|
loaders = {'Arc': Arc, 'Line': Line}
|
||
|
# pre- allocate entity array
|
||
|
entities = [None] * len(as_dict['entities'])
|
||
|
# run constructor for dict kwargs
|
||
|
for entity_index, entity in enumerate(as_dict['entities']):
|
||
|
entities[entity_index] = loaders[entity['type']](
|
||
|
points=entity['points'], closed=entity['closed'])
|
||
|
result['entities'] = entities
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
def lines_to_path(lines):
|
||
|
"""
|
||
|
Turn line segments into a Path2D or Path3D object.
|
||
|
|
||
|
Parameters
|
||
|
------------
|
||
|
lines : (n, 2, dimension) or (n, dimension) float
|
||
|
Line segments or connected polyline curve in 2D or 3D
|
||
|
|
||
|
Returns
|
||
|
-----------
|
||
|
kwargs : dict
|
||
|
kwargs for Path constructor
|
||
|
"""
|
||
|
lines = np.asanyarray(lines, dtype=np.float64)
|
||
|
|
||
|
if util.is_shape(lines, (-1, (2, 3))):
|
||
|
# the case where we have a list of points
|
||
|
# we are going to assume they are connected
|
||
|
result = {'entities': np.array([Line(np.arange(len(lines)))]),
|
||
|
'vertices': lines}
|
||
|
return result
|
||
|
elif util.is_shape(lines, (-1, 2, (2, 3))):
|
||
|
# case where we have line segments in 2D or 3D
|
||
|
dimension = lines.shape[-1]
|
||
|
# convert lines to even number of (n, dimension) points
|
||
|
lines = lines.reshape((-1, dimension))
|
||
|
# merge duplicate vertices
|
||
|
unique, inverse = grouping.unique_rows(lines)
|
||
|
# use scipy edges_to_path to skip creating
|
||
|
# a bajillion individual line entities which
|
||
|
# will be super slow vs. fewer polyline entities
|
||
|
return edges_to_path(edges=inverse.reshape((-1, 2)),
|
||
|
vertices=lines[unique])
|
||
|
else:
|
||
|
raise ValueError('Lines must be (n,(2|3)) or (n,2,(2|3))')
|
||
|
return result
|
||
|
|
||
|
|
||
|
def polygon_to_path(polygon):
|
||
|
"""
|
||
|
Load shapely Polygon objects into a trimesh.path.Path2D object
|
||
|
|
||
|
Parameters
|
||
|
-------------
|
||
|
polygon : shapely.geometry.Polygon
|
||
|
Input geometry
|
||
|
|
||
|
Returns
|
||
|
-----------
|
||
|
kwargs : dict
|
||
|
Keyword arguments for Path2D constructor
|
||
|
"""
|
||
|
# start with a single polyline for the exterior
|
||
|
entities = deque([Line(points=np.arange(
|
||
|
len(polygon.exterior.coords)))])
|
||
|
# start vertices
|
||
|
vertices = np.array(polygon.exterior.coords).tolist()
|
||
|
|
||
|
# append interiors as single Line objects
|
||
|
for boundary in polygon.interiors:
|
||
|
entities.append(Line(np.arange(len(boundary.coords)) +
|
||
|
len(vertices)))
|
||
|
# append the new vertex array
|
||
|
vertices.extend(boundary.coords)
|
||
|
|
||
|
# make sure result arrays are numpy
|
||
|
kwargs = {'entities': np.array(entities),
|
||
|
'vertices': np.array(vertices)}
|
||
|
|
||
|
return kwargs
|
||
|
|
||
|
|
||
|
def linestrings_to_path(multi):
|
||
|
"""
|
||
|
Load shapely LineString objects into a trimesh.path.Path2D object
|
||
|
|
||
|
Parameters
|
||
|
-------------
|
||
|
multi : shapely.geometry.LineString or MultiLineString
|
||
|
Input 2D geometry
|
||
|
|
||
|
Returns
|
||
|
-------------
|
||
|
kwargs : dict
|
||
|
Keyword arguments for Path2D constructor
|
||
|
"""
|
||
|
# append to result as we go
|
||
|
entities = []
|
||
|
vertices = []
|
||
|
|
||
|
if not util.is_sequence(multi):
|
||
|
multi = [multi]
|
||
|
|
||
|
for line in multi:
|
||
|
# only append geometry with points
|
||
|
if hasattr(line, 'coords'):
|
||
|
coords = np.array(line.coords)
|
||
|
if len(coords) < 2:
|
||
|
continue
|
||
|
entities.append(Line(np.arange(len(coords)) +
|
||
|
len(vertices)))
|
||
|
vertices.extend(coords)
|
||
|
|
||
|
kwargs = {'entities': np.array(entities),
|
||
|
'vertices': np.array(vertices)}
|
||
|
return kwargs
|
||
|
|
||
|
|
||
|
def faces_to_path(mesh, face_ids=None, **kwargs):
|
||
|
"""
|
||
|
Given a mesh and face indices find the outline edges and
|
||
|
turn them into a Path3D.
|
||
|
|
||
|
Parameters
|
||
|
------------
|
||
|
mesh : trimesh.Trimesh
|
||
|
Triangulated surface in 3D
|
||
|
face_ids : (n,) int
|
||
|
Indexes referencing mesh.faces
|
||
|
|
||
|
Returns
|
||
|
---------
|
||
|
kwargs : dict
|
||
|
Kwargs for Path3D constructor
|
||
|
"""
|
||
|
if face_ids is None:
|
||
|
edges = mesh.edges_sorted
|
||
|
else:
|
||
|
# take advantage of edge ordering to index as single row
|
||
|
edges = mesh.edges_sorted.reshape(
|
||
|
(-1, 6))[face_ids].reshape((-1, 2))
|
||
|
# an edge which occurs onely once is on the boundary
|
||
|
unique_edges = grouping.group_rows(
|
||
|
edges, require_count=1)
|
||
|
# add edges and vertices to kwargs
|
||
|
kwargs.update(edges_to_path(edges=edges[unique_edges],
|
||
|
vertices=mesh.vertices))
|
||
|
|
||
|
return kwargs
|
||
|
|
||
|
|
||
|
def edges_to_path(edges,
|
||
|
vertices,
|
||
|
**kwargs):
|
||
|
"""
|
||
|
Given an edge list of indices and associated vertices
|
||
|
representing lines, generate kwargs for a Path object.
|
||
|
|
||
|
Parameters
|
||
|
-----------
|
||
|
edges : (n, 2) int
|
||
|
Vertex indices of line segments
|
||
|
vertices : (m, dimension) float
|
||
|
Vertex positions where dimension is 2 or 3
|
||
|
|
||
|
Returns
|
||
|
----------
|
||
|
kwargs : dict
|
||
|
Kwargs for Path constructor
|
||
|
"""
|
||
|
# sequence of ordered traversals
|
||
|
dfs = graph.traversals(edges, mode='dfs')
|
||
|
# make sure every consecutive index in DFS
|
||
|
# traversal is an edge in the source edge list
|
||
|
dfs_connected = graph.fill_traversals(dfs, edges=edges)
|
||
|
# kwargs for Path constructor
|
||
|
# turn traversals into Line objects
|
||
|
lines = [Line(d) for d in dfs_connected]
|
||
|
|
||
|
kwargs.update({'entities': lines,
|
||
|
'vertices': vertices,
|
||
|
'process': False})
|
||
|
return kwargs
|