Skip to content

Commit 8366d9a

Browse files
committed
Merge pull request tpaviot#3 from tpaviot/tp/topo-improvements
Topology fixes/improvements
2 parents 567f707 + e9ceb8a commit 8366d9a

File tree

2 files changed

+254
-90
lines changed

2 files changed

+254
-90
lines changed

OCCUtils/Topology.py

Lines changed: 151 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,30 @@
1717
##You should have received a copy of the GNU Lesser General Public License
1818
##along with pythonOCC. If not, see <http://www.gnu.org/licenses/>.
1919

20-
'''
21-
TODO:
22-
Hide WireExplorer...
23-
BRepTools.Map3DEdges()
24-
History:
25-
20-01-2009: initial version
26-
23-03-2009: completed and updated for modular pythonOCC build
27-
23-04-2009: fixed a reference issue ( fixed using ReInit )
28-
'''
2920
from __future__ import print_function
3021

31-
import sys
32-
import itertools
22+
__all__ = ['Topo', 'WireExplorer', 'dumpTopology']
3323

34-
from OCC.TopAbs import *
35-
from OCC.TopExp import *
36-
from OCC.TopoDS import *
37-
from OCC.TopTools import *
38-
from OCC.BRepTools import *
39-
from OCC.BRep import *
40-
__all__ = ['Topo', 'WireExplorer']
24+
from OCC.BRep import BRep_Tool
25+
26+
from OCC.BRepTools import BRepTools_WireExplorer
27+
from OCC.TopAbs import (TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE, TopAbs_WIRE,
28+
TopAbs_SHELL, TopAbs_SOLID, TopAbs_COMPOUND,
29+
TopAbs_COMPSOLID)
30+
from OCC.TopExp import TopExp_Explorer, topexp_MapShapesAndAncestors
31+
from OCC.TopTools import (TopTools_ListOfShape,
32+
TopTools_ListIteratorOfListOfShape,
33+
TopTools_IndexedDataMapOfShapeListOfShape)
34+
from OCC.TopoDS import (topods, TopoDS_Wire, TopoDS_Vertex, TopoDS_Edge,
35+
TopoDS_Face, TopoDS_Shell, TopoDS_Solid,
36+
TopoDS_Compound, TopoDS_CompSolid, topods_Edge,
37+
topods_Vertex, TopoDS_Iterator)
4138

4239

4340
class WireExplorer(object):
44-
''' '''
41+
'''
42+
Wire traversal
43+
'''
4544
def __init__(self, wire):
4645
assert isinstance(wire, TopoDS_Wire), 'not a TopoDS_Wire'
4746
self.wire = wire
@@ -90,12 +89,52 @@ def ordered_vertices(self):
9089

9190
class Topo(object):
9291
'''
93-
sketch for a pythonic topology wrapper
94-
note that `myShape` should be self, which is in return a occ.TopoShape
95-
with this
92+
Topology traversal
9693
'''
97-
def __init__(self, myShape):
94+
95+
def __init__(self, myShape, ignore_orientation=False):
96+
"""
97+
98+
implements topology traversal from any TopoDS_Shape
99+
this class lets you find how various topological entities are connected from one to another
100+
find the faces connected to an edge, find the vertices this edge is made from, get all faces connected to
101+
a vertex, and find out how many topological elements are connected from a source
102+
103+
*note* when traversing TopoDS_Wire entities, its advised to use the specialized
104+
``WireExplorer`` class, which will return the vertices / edges in the expected order
105+
106+
:param myShape: the shape which topology will be traversed
107+
108+
:param ignore_orientation: filter out TopoDS_* entities of similar TShape but different Orientation
109+
110+
for instance, a cube has 24 edges, 4 edges for each of 6 faces
111+
112+
that results in 48 vertices, while there are only 8 vertices that have a unique
113+
geometric coordinate
114+
115+
in certain cases ( computing a graph from the topology ) its preferable to return
116+
topological entities that share similar geometry, though differ in orientation
117+
by setting the ``ignore_orientation`` variable
118+
to True, in case of a cube, just 12 edges and only 8 vertices will be returned
119+
120+
for further reference see TopoDS_Shape IsEqual / IsSame methods
121+
122+
"""
98123
self.myShape = myShape
124+
self.ignore_orientation = ignore_orientation
125+
126+
# the topoFactory dicts maps topology types and functions that can
127+
# create this topology
128+
self.topoFactory = {
129+
TopAbs_VERTEX: topods.Vertex,
130+
TopAbs_EDGE: topods.Edge,
131+
TopAbs_FACE: topods.Face,
132+
TopAbs_WIRE: topods.Wire,
133+
TopAbs_SHELL: topods.Shell,
134+
TopAbs_SOLID: topods.Solid,
135+
TopAbs_COMPOUND: topods.Compound,
136+
TopAbs_COMPSOLID: topods.CompSolid
137+
}
99138

100139
def _loop_topo(self, topologyType, topologicalEntity=None, topologyTypeToAvoid=None):
101140
'''
@@ -104,15 +143,16 @@ def _loop_topo(self, topologyType, topologicalEntity=None, topologyTypeToAvoid=N
104143
for face in srf.faces:
105144
processFace(face)
106145
'''
107-
topoTypes = {TopAbs_VERTEX: topods_Vertex,
108-
TopAbs_EDGE: topods_Edge,
109-
TopAbs_FACE: topods_Face,
110-
TopAbs_WIRE: topods_Wire,
111-
TopAbs_SHELL: topods_Shell,
112-
TopAbs_SOLID: topods_Solid,
113-
TopAbs_COMPOUND: topods_Compound,
114-
TopAbs_COMPSOLID: topods_CompSolid}
115-
assert topologyType in topoTypes.keys(), '%s not one of %s' % (topologyType, topoTypes.keys())
146+
topoTypes = {TopAbs_VERTEX: TopoDS_Vertex,
147+
TopAbs_EDGE: TopoDS_Edge,
148+
TopAbs_FACE: TopoDS_Face,
149+
TopAbs_WIRE: TopoDS_Wire,
150+
TopAbs_SHELL: TopoDS_Shell,
151+
TopAbs_SOLID: TopoDS_Solid,
152+
TopAbs_COMPOUND: TopoDS_Compound,
153+
TopAbs_COMPSOLID: TopoDS_CompSolid}
154+
155+
assert topologyType in topoTypes.keys(), '%s not one of %s' % (topologyType, topoTypes.keys())
116156
self.topExp = TopExp_Explorer()
117157
# use self.myShape if nothing is specified
118158
if topologicalEntity is None and topologyTypeToAvoid is None:
@@ -122,24 +162,43 @@ def _loop_topo(self, topologyType, topologicalEntity=None, topologyTypeToAvoid=N
122162
elif topologyTypeToAvoid is None:
123163
self.topExp.Init(topologicalEntity, topologyType)
124164
elif topologyTypeToAvoid:
125-
self.topExp.Init(topologicalEntity, topologyType, topologyTypeToAvoid)
165+
self.topExp.Init(topologicalEntity,
166+
topologyType,
167+
topologyTypeToAvoid)
126168
seq = []
127169
hashes = [] # list that stores hashes to avoid redundancy
128170
occ_seq = TopTools_ListOfShape()
129171
while self.topExp.More():
130172
current_item = self.topExp.Current()
131173
current_item_hash = current_item.__hash__()
174+
132175
if not current_item_hash in hashes:
133176
hashes.append(current_item_hash)
134177
occ_seq.Append(current_item)
178+
135179
self.topExp.Next()
136180
# Convert occ_seq to python list
137181
occ_iterator = TopTools_ListIteratorOfListOfShape(occ_seq)
138182
while occ_iterator.More():
139-
topo_to_add = topoTypes[topologyType](occ_iterator.Value())
183+
topo_to_add = self.topoFactory[topologyType](occ_iterator.Value())
140184
seq.append(topo_to_add)
141185
occ_iterator.Next()
142-
return iter(seq)
186+
187+
if self.ignore_orientation:
188+
# filter out those entities that share the same TShape
189+
# but do *not* share the same orientation
190+
filter_orientation_seq = []
191+
for i in seq:
192+
_present = False
193+
for j in filter_orientation_seq:
194+
if i.IsSame(j):
195+
_present = True
196+
break
197+
if _present is False:
198+
filter_orientation_seq.append(i)
199+
return filter_orientation_seq
200+
else:
201+
return iter(seq)
143202

144203
def faces(self):
145204
'''
@@ -252,29 +311,34 @@ def _map_shapes_and_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
252311
results = _map.FindFromKey(topologicalEntity)
253312
if results.IsEmpty():
254313
yield None
255-
topoTypes = {TopAbs_VERTEX: topods_Vertex,
256-
TopAbs_EDGE: topods_Edge,
257-
TopAbs_FACE: topods_Face,
258-
TopAbs_WIRE: topods_Wire,
259-
TopAbs_SHELL: topods_Shell,
260-
TopAbs_SOLID: topods_Solid,
261-
TopAbs_COMPOUND: topods_Compound,
262-
TopAbs_COMPSOLID: topods_CompSolid}
314+
263315
topology_iterator = TopTools_ListIteratorOfListOfShape(results)
264316
while topology_iterator.More():
265-
topo_entity = topoTypes[topoTypeB](topology_iterator.Value())
317+
318+
topo_entity = self.topoFactory[topoTypeB](topology_iterator.Value())
319+
266320
# return the entity if not in set
267321
# to assure we're not returning entities several times
268322
if not topo_entity in topo_set:
269-
yield topo_entity
323+
if self.ignore_orientation:
324+
unique = True
325+
for i in topo_set:
326+
if i.IsSame(topo_entity):
327+
unique = False
328+
break
329+
if unique:
330+
yield topo_entity
331+
else:
332+
yield topo_entity
333+
270334
topo_set.add(topo_entity)
271335
topology_iterator.Next()
272336

273337
def _number_shapes_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
274338
'''returns the number of shape ancestors
275339
If you want to know how many edges a faces has:
276340
_number_shapes_ancestors(self, TopAbs_EDGE, TopAbs_FACE, edg)
277-
will return the number of edges a faces has
341+
will return the number of edges a faces has
278342
@param topoTypeA:
279343
@param topoTypeB:
280344
@param topologicalEntity:
@@ -291,16 +355,31 @@ def _number_shapes_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
291355
topology_iterator.Next()
292356
return len(topo_set)
293357

294-
#==========================================================================
295-
# EDGE <-> FACE
296-
#==========================================================================
358+
# ======================================================================
359+
# EDGE <-> FACE
360+
# ======================================================================
297361
def faces_from_edge(self, edge):
362+
"""
363+
364+
:param edge:
365+
:return:
366+
"""
298367
return self._map_shapes_and_ancestors(TopAbs_EDGE, TopAbs_FACE, edge)
299368

300369
def number_of_faces_from_edge(self, edge):
370+
"""
371+
372+
:param edge:
373+
:return:
374+
"""
301375
return self._number_shapes_ancestors(TopAbs_EDGE, TopAbs_FACE, edge)
302376

303377
def edges_from_face(self, face):
378+
"""
379+
380+
:param face:
381+
:return:
382+
"""
304383
return self._loop_topo(TopAbs_EDGE, face)
305384

306385
def number_of_edges_from_face(self, face):
@@ -309,9 +388,9 @@ def number_of_edges_from_face(self, face):
309388
cnt += 1
310389
return cnt
311390

312-
#===========================================================================
313-
# VERTEX <-> EDGE
314-
#===========================================================================
391+
# ======================================================================
392+
# VERTEX <-> EDGE
393+
# ======================================================================
315394
def vertices_from_edge(self, edg):
316395
return self._loop_topo(TopAbs_VERTEX, edg)
317396

@@ -327,9 +406,9 @@ def edges_from_vertex(self, vertex):
327406
def number_of_edges_from_vertex(self, vertex):
328407
return self._number_shapes_ancestors(TopAbs_VERTEX, TopAbs_EDGE, vertex)
329408

330-
#===========================================================================
331-
# WIRE <-> EDGE
332-
#===========================================================================
409+
# ======================================================================
410+
# WIRE <-> EDGE
411+
# ======================================================================
333412
def edges_from_wire(self, wire):
334413
return self._loop_topo(TopAbs_EDGE, wire)
335414

@@ -342,12 +421,15 @@ def number_of_edges_from_wire(self, wire):
342421
def wires_from_edge(self, edg):
343422
return self._map_shapes_and_ancestors(TopAbs_EDGE, TopAbs_WIRE, edg)
344423

424+
def wires_from_vertex(self, edg):
425+
return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_WIRE, edg)
426+
345427
def number_of_wires_from_edge(self, edg):
346428
return self._number_shapes_ancestors(TopAbs_EDGE, TopAbs_WIRE, edg)
347429

348-
#===========================================================================
349-
# WIRE <-> FACE
350-
#===========================================================================
430+
# ======================================================================
431+
# WIRE <-> FACE
432+
# ======================================================================
351433
def wires_from_face(self, face):
352434
return self._loop_topo(TopAbs_WIRE, face)
353435

@@ -363,9 +445,9 @@ def faces_from_wire(self, wire):
363445
def number_of_faces_from_wires(self, wire):
364446
return self._number_shapes_ancestors(TopAbs_WIRE, TopAbs_FACE, wire)
365447

366-
#===========================================================================
367-
# VERTEX <-> FACE
368-
#===========================================================================
448+
# ======================================================================
449+
# VERTEX <-> FACE
450+
# ======================================================================
369451
def faces_from_vertex(self, vertex):
370452
return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_FACE, vertex)
371453

@@ -381,9 +463,9 @@ def number_of_vertices_from_face(self, face):
381463
cnt += 1
382464
return cnt
383465

384-
#===========================================================================
385-
# FACE <-> SOLID
386-
#===========================================================================
466+
# ======================================================================
467+
# FACE <-> SOLID
468+
# ======================================================================
387469
def solids_from_face(self, face):
388470
return self._map_shapes_and_ancestors(TopAbs_FACE, TopAbs_SOLID, face)
389471

@@ -406,11 +488,12 @@ def dumpTopology(shape, level=0):
406488
"""
407489
brt = BRep_Tool()
408490
s = shape.ShapeType()
409-
print("." * level, end="")
410-
print(shapeTypeString(shape), end="")
411491
if s == TopAbs_VERTEX:
412492
pnt = brt.Pnt(topods_Vertex(shape))
413-
print("<Vertex: %s %s %s>" % (pnt.X(), pnt.Y(), pnt.Z()))
493+
print(".." * level + "<Vertex %i: %s %s %s>" % (hash(shape), pnt.X(), pnt.Y(), pnt.Z()))
494+
else:
495+
print(".." * level, end="")
496+
print(shapeTypeString(shape))
414497
it = TopoDS_Iterator(shape)
415498
while it.More():
416499
shp = it.Value()
@@ -437,4 +520,4 @@ def shapeTypeString(shape):
437520
s = "Compound."
438521
if st == TopAbs_COMPSOLID:
439522
s = "Compsolid."
440-
return s + ":" + str(shape.HashCode(23232232))
523+
return "%s: %i" % (s, hash(shape))

0 commit comments

Comments
 (0)