@@ -16,8 +16,8 @@ def __init__(self, point_on_plane, unit_normal):
16
16
17
17
unit_normal = vx .normalize (unit_normal )
18
18
19
- self ._r0 = point_on_plane
20
- self ._n = unit_normal
19
+ self ._r0 = np . asarray ( point_on_plane )
20
+ self ._n = np . asarray ( unit_normal )
21
21
22
22
def __repr__ (self ):
23
23
return "<Plane of {} through {}>" .format (self .normal , self .reference_point )
@@ -127,7 +127,7 @@ def normal(self):
127
127
Return the plane's normal vector.
128
128
129
129
'''
130
- return np . array ( self ._n )
130
+ return self ._n
131
131
132
132
def flipped (self ):
133
133
'''
@@ -210,25 +210,22 @@ def polyline_xsection(self, polyline):
210
210
return intersection_points
211
211
212
212
def line_xsection (self , pt , ray ):
213
- pt = np .asarray (pt ).ravel ()
214
- ray = np .asarray (ray ).ravel ()
215
- assert len (pt ) == 3
216
- assert len (ray ) == 3
213
+ return self ._line_xsection (np .asarray (pt ).ravel (), np .asarray (ray ).ravel ())
214
+
215
+ def _line_xsection (self , pt , ray ):
217
216
denom = np .dot (ray , self .normal )
218
217
if denom == 0 :
219
218
return None # parallel, either coplanar or non-intersecting
220
219
p = np .dot (self .reference_point - pt , self .normal ) / denom
221
220
return p * ray + pt
222
221
223
222
def line_segment_xsection (self , a , b ):
224
- a = np .asarray (a ).ravel ()
225
- b = np .asarray (b ).ravel ()
226
- assert len (a ) == 3
227
- assert len (b ) == 3
223
+ return self ._line_segment_xsection (np .asarray (a ).ravel (), np .asarray (b ).ravel ())
228
224
229
- pt = self .line_xsection (a , b - a )
225
+ def _line_segment_xsection (self , a , b ):
226
+ pt = self ._line_xsection (a , b - a )
230
227
if pt is not None :
231
- if np .any (pt < np . min (( a , b ), axis = 0 )) or np .any (pt > np . max (( a , b ), axis = 0 )):
228
+ if any ( np .logical_and (pt > a , pt > b )) or any ( np .logical_and (pt < a , pt < b )):
232
229
return None
233
230
return pt
234
231
@@ -245,6 +242,11 @@ def mesh_xsection(self, m, neighborhood=None):
245
242
return Polyline (None )
246
243
return Polyline (np .vstack ([x .v for x in components ]), closed = True )
247
244
245
+ def mesh_intersecting_faces (self , m ):
246
+ sgn_dists = self .signed_distance (m .v )
247
+ which_fs = np .abs (np .sign (sgn_dists )[m .f ].sum (axis = 1 )) != 3
248
+ return m .f [which_fs ]
249
+
248
250
def mesh_xsections (self , m , neighborhood = None ):
249
251
'''
250
252
Takes a cross section of planar point cloud with a Mesh object.
@@ -262,103 +264,131 @@ def mesh_xsections(self, m, neighborhood=None):
262
264
263
265
Returns a list of Polylines.
264
266
'''
265
- import operator
266
267
from blmath .geometry import Polyline
267
268
268
269
# 1: Select those faces that intersect the plane, fs
269
- sgn_dists = self .signed_distance (m .v )
270
- which_fs = np .abs (np .sign (sgn_dists )[m .f ].sum (axis = 1 )) != 3
271
- fs = m .f [which_fs ]
272
-
270
+ fs = self .mesh_intersecting_faces (m )
273
271
if len (fs ) == 0 :
274
272
return [] # Nothing intersects
273
+ # and edges of those faces
274
+ es = np .vstack ((fs [:, (0 , 1 )], fs [:, (1 , 2 )], fs [:, (2 , 0 )]))
275
275
276
276
# 2: Find the edges where each of those faces actually cross the plane
277
- def edge_from_face (f ):
278
- face_verts = [
279
- [m .v [f [0 ]], m .v [f [1 ]]],
280
- [m .v [f [1 ]], m .v [f [2 ]]],
281
- [m .v [f [2 ]], m .v [f [0 ]]],
282
- ]
283
- e = [self .line_segment_xsection (a , b ) for a , b in face_verts ]
284
- e = [x for x in e if x is not None ]
285
- return e
286
- edges = np .vstack ([np .hstack (edge_from_face (f )) for f in fs ])
287
-
288
- # 3: Find the set of unique vertices in `edges`
289
- v1s , v2s = np .hsplit (edges , 2 )
290
- verts = edges .reshape ((- 1 , 3 ))
291
- verts = np .vstack (sorted (verts , key = operator .itemgetter (0 , 1 , 2 )))
292
- eps = 1e-15 # the floating point calculation of the intersection locations is not _quite_ exact
293
- verts = verts [list (np .sqrt (np .sum (np .diff (verts , axis = 0 ) ** 2 , axis = 1 )) > eps ) + [True ]]
294
- # the True at the end there is because np.diff returns pairwise differences; one less element than the original array
277
+ class EdgeMap (object ):
278
+ # A quick two level dictionary where the two keys are interchangeable (i.e. a symmetric graph)
279
+ def __init__ (self ):
280
+ self .d = {} # store indicies into self.values here, to make it easier to get inds or values
281
+ self .values = []
282
+ def _order (self , u , v ):
283
+ if u < v :
284
+ return u , v
285
+ else :
286
+ return v , u
287
+ def add (self , u , v , val ):
288
+ low , high = self ._order (u , v )
289
+ if low not in self .d :
290
+ self .d [low ] = {}
291
+ self .values .append (val )
292
+ self .d [low ][high ] = len (self .values ) - 1
293
+ def contains (self , u , v ):
294
+ low , high = self ._order (u , v )
295
+ if low in self .d and high in self .d [low ]:
296
+ return True
297
+ return False
298
+ def index (self , u , v ):
299
+ low , high = self ._order (u , v )
300
+ try :
301
+ return self .d [low ][high ]
302
+ except KeyError :
303
+ return None
304
+ def get (self , u , v ):
305
+ ii = self .index (u , v )
306
+ if ii is not None :
307
+ return self .values [ii ]
308
+ else :
309
+ return None
310
+
311
+ intersection_map = EdgeMap ()
312
+ for e in es :
313
+ if not intersection_map .contains (e [0 ], e [1 ]):
314
+ val = self ._line_segment_xsection (m .v [e [0 ]], m .v [e [1 ]])
315
+ if val is not None :
316
+ intersection_map .add (e [0 ], e [1 ], val )
317
+ verts = np .array (intersection_map .values )
295
318
296
319
class Graph (object ):
297
- # A little utility class to build a symmetric graph
320
+ # A little utility class to build a symmetric graph and calculate Euler Paths
298
321
def __init__ (self , size ):
299
322
self .size = size
300
323
self .d = {}
301
324
def __len__ (self ):
302
325
return len (self .d )
303
- def add_edge (self , ii , jj ):
304
- assert ii >= 0 and ii < self .size
305
- assert jj >= 0 and jj < self .size
306
- if ii not in self .d :
307
- self .d [ii ] = set ()
308
- if jj not in self .d :
309
- self .d [jj ] = set ()
310
- self .d [ii ].add (jj )
311
- self .d [jj ].add (ii )
326
+ def add_edges (self , edges ):
327
+ for u , v in edges :
328
+ self .add_edge (u , v )
329
+ def add_edge (self , u , v ):
330
+ assert u >= 0 and u < self .size
331
+ assert v >= 0 and v < self .size
332
+ if u not in self .d :
333
+ self .d [u ] = set ()
334
+ if v not in self .d :
335
+ self .d [v ] = set ()
336
+ self .d [u ].add (v )
337
+ self .d [v ].add (u )
338
+ def remove_edge (self , u , v ):
339
+ if u in self .d and v in self .d [u ]:
340
+ self .d [u ].remove (v )
341
+ if v in self .d and u in self .d [v ]:
342
+ self .d [v ].remove (u )
343
+ if v in self .d and len (self .d [v ]) == 0 :
344
+ del self .d [v ]
345
+ if u in self .d and len (self .d [u ]) == 0 :
346
+ del self .d [u ]
347
+ def pop_euler_path (self , allow_multiple_connected_components = True ):
348
+ # Based on code from Przemek Drochomirecki, Krakow, 5 Nov 2006
349
+ # http://code.activestate.com/recipes/498243-finding-eulerian-path-in-undirected-graph/
350
+ # Under PSF License
351
+ # NB: MUTATES d
352
+
353
+ # counting the number of vertices with odd degree
354
+ odd = [x for x in self .d if len (self .d [x ])& 1 ]
355
+ odd .append (self .d .keys ()[0 ])
356
+ if not allow_multiple_connected_components and len (odd ) > 3 :
357
+ return None
358
+ stack = [odd [0 ]]
359
+ path = []
360
+ # main algorithm
361
+ while stack :
362
+ v = stack [- 1 ]
363
+ if v in self .d :
364
+ u = self .d [v ].pop ()
365
+ stack .append (u )
366
+ self .remove_edge (u , v )
367
+ else :
368
+ path .append (stack .pop ())
369
+ return path
312
370
313
371
# 4: Build the edge adjacency graph
314
372
G = Graph (verts .shape [0 ])
315
- def indexof (v , in_this ):
316
- return np .nonzero (np .all (np .abs (in_this - v ) < eps , axis = 1 ))[0 ]
317
- for ii , v in enumerate (verts ):
318
- for other_v in list (v2s [indexof (v , v1s )]) + list (v1s [indexof (v , v2s )]):
319
- neighbors = indexof (other_v , verts )
320
- for jj in neighbors :
321
- G .add_edge (ii , jj )
322
-
323
- def euler_path (graph ):
324
- # Based on code from Przemek Drochomirecki, Krakow, 5 Nov 2006
325
- # http://code.activestate.com/recipes/498243-finding-eulerian-path-in-undirected-graph/
326
- # Under PSF License
327
- # NB: MUTATES graph
328
-
329
- # counting the number of vertices with odd degree
330
- odd = [x for x in graph .keys () if len (graph [x ])& 1 ]
331
- odd .append (graph .keys ()[0 ])
332
- # This check is appropriate if there is a single connected component.
333
- # Since we're willing to take away one connected component per call,
334
- # we skip this check.
335
- # if len(odd)>3:
336
- # return None
337
- stack = [odd [0 ]]
338
- path = []
339
- # main algorithm
340
- while stack :
341
- v = stack [- 1 ]
342
- if v in graph :
343
- u = graph [v ].pop ()
344
- stack .append (u )
345
- # deleting edge u-v (v-u already removed by pop)
346
- graph [u ].remove (v )
347
- # graph[v].remove(u)
348
- if len (graph [v ]) == 0 :
349
- del graph [v ]
350
- if len (graph [u ]) == 0 :
351
- del graph [u ]
352
- else :
353
- path .append (stack .pop ())
354
- return path
373
+ for f in fs :
374
+ # Since we're dealing with a triangle that intersects the plane, exactly two of the edges
375
+ # will intersect (note that the only other sorts of "intersections" are one edge in
376
+ # plane or all three edges in plane, which won't be picked up by mesh_intersecting_faces).
377
+ e0 = intersection_map .index (f [0 ], f [1 ])
378
+ e1 = intersection_map .index (f [0 ], f [2 ])
379
+ e2 = intersection_map .index (f [1 ], f [2 ])
380
+ if e0 is None :
381
+ G .add_edge (e1 , e2 )
382
+ elif e1 is None :
383
+ G .add_edge (e0 , e2 )
384
+ else :
385
+ G .add_edge (e0 , e1 )
355
386
356
387
# 5: Find the paths for each component
357
388
components = []
358
389
components_closed = []
359
390
while len (G ) > 0 :
360
- # This works because euler_path mutates the graph as it goes
361
- path = euler_path (G .d )
391
+ path = G .pop_euler_path ()
362
392
if path is None :
363
393
raise ValueError ("mesh slice has too many odd degree edges; can't find a path along the edge" )
364
394
component_verts = verts [path ]
0 commit comments