-
Notifications
You must be signed in to change notification settings - Fork 6
/
meshcrawlerLib.py
232 lines (191 loc) · 7.45 KB
/
meshcrawlerLib.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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#pylint:disable=missing-docstring
'''
Meshcrawler is an awesome library that allows you to build a 1:1 topology match
between two meshes that don't have the same vertex order.
This is done (in most cases) *without* users specifying any matching vertex points.
It uses some relatively simple heuristics to accomplish this, and they exist mostly
in meshcrawlergen.py
This is the actual matching algorithm. It takes meshes (that provide adjacency
info), and matched ordered pairs, and either returns a set of vertex matches,
or raises an error
'''
from MeshCrawler.meshcrawlerErrors import TopologyMismatch
from Qt.QtWidgets import QApplication
###################################################
### Match Point Order ###
###################################################
def flipMultiDict(eDict, fDict):
""" Make a dict keyed off the values of two dicts
Args:
eDict,fDict: Two dictionaries with hashable values
Returns:
A dictionary in this form: (eValueSet,fValueSet):key
"""
inv = {}
allKeys = eDict.viewkeys() | fDict.viewkeys()
for k in allKeys:
eVal = frozenset(eDict.get(k, ()))
fVal = frozenset(fDict.get(k, ()))
inv.setdefault((eVal, fVal), []).append(k)
return inv
def growTracked(mesh, growSet, allSet):
""" Grow a set of verts along edges and faces
This function takes a set of vertices and returns two
dicts. One that is vert:(edgeAdjVerts..), and another
that is vert:(faceAdjVerts..)
While I'm growing a vert, if all vertices adjacent to
that vert are matched, then I no longer need to grow
from there.
Args:
growSet: A set of Vertex objects to grow. This set
will have all useless verts removed and will be
changed in-place.
allSet: A set of Vertex objects to exclude from
the growth
Returns:
edgeDict: A dictionary with verts as keys and all
edge adjacent vertices as the value
faceDict: A dictionary with verts as keys and all
face adjacent vertices as the value
newGrowSet: The original growSet with all used-up
vertices removed
"""
edgeDict = {}
faceDict = {}
# Verts that are grown, but have no adjacent verts
# that aren't in the allSet
rem = []
for vert in growSet:
edgeFound = False
faceFound = False
for eadj in mesh.adjacentVertsByEdge(vert):
if eadj not in allSet:
edgeFound = True
edgeDict.setdefault(eadj, []).append(vert)
for fadj in mesh.adjacentVertsByFace(vert):
if fadj not in allSet:
faceFound = True
faceDict.setdefault(fadj, []).append(vert)
if not edgeFound and not faceFound:
rem.append(vert)
newGrowSet = growSet - set(rem)
return edgeDict, faceDict, newGrowSet
def matchByTopology(orderMesh, shapeMesh, vertexPairs, matchedNum=None, vertNum=None, symmetry=False, pBar=None):
""" Match the topology of two meshes with different vert orders
Provide a 1:1 vertex index match between two meshes that don't
necessarily have the same vertex order.
At minimum, 3 vertex pairs around a single poly are required.
The algorithm simultaneously performs two different types of
"grow verts" operations on each mesh and each vertex keeps track
of where it was grown from, and how.
New matching verts will be grown from known matching verts in the
same way.
Example:
Starting with two meshes and a "matched" vertex selection on two meshes
I'm calling M1 and M2
Say:
v6 on M1 is an edge away from (v3 ,v5) and a face away from (v3 ,v5,v4)
v9 on M2 is an edge away from (v13,v2) and a face away from (v13,v2,v6)
And we know going in that: M1.v3=M2.v13, M1.v5=M2.v2, M1.v4=M2.v6
Then we can say M1.v6=M2.v9 becaue if we substitute all of our known
matches, we can see that the two vertices are equivalent
Args:
M1: A Mesh object
M2: A second Mesh object
vertPairs: A List of 2-Tuples of vertex indices
that are known matches
matchedNum: The total number of vertices matched up to this point
vertNum: The total number of vertices in the mesh, for
percentageDone purposes. Default: None
symmetry: Boolean value indicating whether the vertPairs
are mirrored indices on the same mesh. Default: False
Returns:
A list of (Vertex,Vertex) pairs that define 1:1 matches
"""
orderVerts, shapeVerts = zip(*vertexPairs)
orderVertsGrow = set(orderVerts)
shapeVertsGrow = set(shapeVerts)
orderVerts = set(orderVerts)
shapeVerts = set(shapeVerts)
centerVerts = set()
orderToShape = {s:t for s, t in vertexPairs}
if vertNum is not None:
vertNum = float(vertNum) #just for percentage reporting
if symmetry:
vertNum /= 2
if not matchedNum:
matchedNum = 0
updated = True
#counter = 0
while updated:
updated = False
if vertNum is not None:
percent = (len(orderVerts) + matchedNum) / vertNum * 100
if pBar is None:
print "\rPercentage processed: {0:.2f}%".format(percent),
else:
pBar.setValue(int(percent))
QApplication.processEvents()
# Grow the vert selection along edges and save as a set
# Grow the vert selection along faces and save as another set
# Remove already selected verts from both lists
#
# The dicts are structured like this:
# {vertex: (orderVertTuple), ...}
if symmetry:
# A fun little hack that allows me to treat the left and
# right hand sides of a model with symmetrical topology as
# two different meshes to match
allSet = orderVerts|shapeVerts|centerVerts
lAllSet = allSet
rAllSet = allSet
else:
lAllSet = orderVerts
rAllSet = shapeVerts
orderEdgeDict, orderFaceDict, orderVertsGrow = growTracked(orderMesh, orderVertsGrow, lAllSet)
shapeEdgeDict, shapeFaceDict, shapeVertsGrow = growTracked(shapeMesh, shapeVertsGrow, rAllSet)
# if a key has a *unique* (face & edge) value
# we can match it to the shape
#
# so flip the dicts and key off of *BOTH* values
# simultaneously
orderEFDict = flipMultiDict(orderEdgeDict, orderFaceDict)
shapeEFDict = flipMultiDict(shapeEdgeDict, shapeFaceDict)
# Then, if the swapped dict's value only has 1 item
# it is a uniquely identified vertex and can be matched
for orderKey, orderValue in orderEFDict.iteritems():
if len(orderValue) == 1:
edgeKey = frozenset(orderToShape[i] for i in orderKey[0])
faceKey = frozenset(orderToShape[i] for i in orderKey[1])
try:
shapeValue = shapeEFDict[(edgeKey, faceKey)]
except KeyError:
area = list(edgeKey) + list(faceKey)
area = list(set(area))
vp = vertexPairs[:]
iidx, jidx = zip(*vp)
m = 'Order {0} to Shape {1}: Match produced no results: Check this order area {2}'.format(iidx, jidx, area)
#if vertNum is not None and pBar is None:
#print #clear the percentage value
raise TopologyMismatch(m)
if len(shapeValue) != 1:
#if vertNum is not None and pBar is None:
#print #clear the percentage value
raise TopologyMismatch('Match produced multiple results')
orderVert = orderValue[0]
shapeVert = shapeValue[0]
if (shapeVert == orderVert) and symmetry:
centerVerts.add(shapeVert)
else:
orderToShape[orderVert] = shapeVert
orderVerts.add(orderVert)
orderVertsGrow.add(orderVert)
shapeVerts.add(shapeVert)
shapeVertsGrow.add(shapeVert)
#pair = (orderVert, shapeVert)
updated = True
if vertNum is not None and pBar is None:
print #clear the percentage value
#if vertNum is not None:
#print "\n"
return [(k, v) for k, v in orderToShape.iteritems()]