/
misc.py
215 lines (178 loc) · 5.95 KB
/
misc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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