-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGenerate.py
372 lines (336 loc) · 15 KB
/
Generate.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
import os
def walk(root):
for parent, dirs, files in os.walk(root):
for name in files:
yield os.path.join(parent, name)
def findFirstOf(data, options, startAt):
result = len(data)
resultI = -1
for i, option in enumerate(options):
index = data.find(option, startAt)
if index == -1: continue
if index < result:
result = index
resultI = i
if resultI != -1:
return result, resultI
return None
def scan(path):
with open(path, 'r') as fh:
code = fh.read()
startTokens = ('NODE_BEGIN(', 'ENUM_BEGIN(', 'COMPOUND_BEGIN(', 'DEFORMER_BEGIN(', 'TYPED_DEFORMER_BEGIN(')
endTokens = ('NODE_END', 'ENUM_END', 'COMPOUND_END', 'DEFORMER_END', 'DEFORMER_END')
output = tuple([] for _ in range(len(startTokens)))
cursor = 0
while True:
result = findFirstOf(code, startTokens, cursor)
if result is None:
return output
cursor, tokenId = result
endCursor = code.find(endTokens[tokenId], cursor)
if endCursor == -1:
raise RuntimeError(
'Code in "%s" is missing an _END token for "%s" token at %d' % (path, startTokens[tokenId], cursor))
output[tokenId].append(code[cursor:endCursor])
cursor = endCursor + len(endTokens[tokenId])
def scanEnum(code):
cursor = code.find('(')
assert cursor != -1
tmp = code.find(')', cursor + 1)
assert tmp != -1
enumName = code[cursor + 1:tmp]
prevValue = -1
enumOptions = []
for stuff in code[tmp + 1:].split('ENUM')[1:]:
a = stuff.find('(')
b = stuff.find(')')
assert a != -1 and b != -1
args = stuff[a + 1:b].split(',')
if len(args) == 1:
prevValue += 1
enumOptions.append((args[0].strip(), prevValue))
elif len(args) == 2:
prevValue = int(args[1].strip())
assert prevValue not in (pair[1] for pair in enumOptions)
enumOptions.append((args[0].strip(), prevValue))
else:
raise ValueError('Error parsing enum element for "%s": "ENUM%s"' % (enumName, stuff))
return enumName, enumOptions
def processEnum(enumName, enumOptions):
namesAsStr = '"%s"' % '", "'.join(pair[0] for pair in enumOptions)
valuesAsStr = ', '.join(str(pair[1]) for pair in enumOptions)
return """template<> %s fallback<%s>() { return %s::%s; }
template<> %s get<%s>(MDataHandle& element) { return (%s)element.asShort(); }
template<> void set<%s>(MDataHandle& element, const %s& value) {
element.setShort((short)value);
element.setClean();
}
template<> MStatus initialize<%s>(MObject& dst, const char* name) { return enumInitialize(dst, name, {%s}, {%s}); }
""" % (
enumName, enumName, enumName, enumOptions[0][0], # fallback
enumName, enumName, enumName, # get
enumName, enumName, # set
enumName, namesAsStr, valuesAsStr, # init
)
def scanCompound(code):
cursor = code.find('(')
assert cursor != -1
tmp = code.find(')', cursor + 1)
assert tmp != -1
compoundName = code[cursor + 1:tmp]
members = []
for stuff in code[tmp + 1:].split('COMPOUND')[1:]:
a = stuff.find('(')
b = stuff.find(')')
assert a != -1 and b != -1
isArray = stuff[:a].endswith('_ARRAY')
if not isArray:
assert stuff[:a].endswith('_VALUE')
args = stuff[a + 1:b].split(',')
assert len(args) == 2
members.append((args[0].strip(), args[1].strip(), isArray))
return compoundName, members
def processCompound(compoundType, compoundTypeNames):
name, attrs = compoundType
code = ["""template<> MStatus initialize<%s>(MObject& dst, const char* name, std::vector<MObject>& children) {
MStatus status;
MFnCompoundAttribute fn;
dst = fn.create(name, name, &status);
CHECK_MSTATUS_AND_RETURN_IT(status);
size_t offset = children.size();
for(int i = 0; i < %d; ++i) children.emplace_back();
""" % (name, len(attrs))]
for index, (attrType, attrName, isArray) in enumerate(attrs):
extra = ''
if isArray:
extra = """\n status = MFnAttribute(attr).setArray(true); CHECK_MSTATUS_AND_RETURN_IT(status);"""
isCompound = attrType in compoundTypeNames
code.append("""
{
MObject& attr = children[offset + %d];
MString childName = name;
childName += "_%s";
status = initialize<%s>(attr, childName.asChar()%s); CHECK_MSTATUS_AND_RETURN_IT(status);%s
status = fn.addChild(children[offset + %d]); CHECK_MSTATUS_AND_RETURN_IT(status);
}
""" % (index, attrName, attrType, ', children' if isCompound else '', extra, index))
code.append("""
return status;
}""")
code.append("""
template<> %s get<%s>(MDataHandle& element, const MObject* objects) {
%s result;
MDataHandle child;
""" % (name, name, name))
offset = len(attrs)
sizes = [compoundTypeNames.get(attr[0], 0) for attr in attrs]
for index, (attrType, attrName, isArray) in enumerate(attrs):
args = 'child'
if isArray:
args = 'handle'
isCompound = attrType in compoundTypeNames
if isCompound:
args += ', objects + %d' % offset
if isArray:
code.append(""" child = element.child(objects[%d]); { MArrayDataHandle handle(child); result.%s = get<%s>(%s); }\n""" % (index, attrName, attrType, args))
else:
code.append(""" child = element.child(objects[%d]); result.%s = get<%s>(%s);\n""" % (index, attrName, attrType, args))
offset += sizes[index]
code.append(""" return result;
}""")
code.append("""
template<> void set<%s>(MDataHandle& element, const MObject* objects, const %s& value) {
MDataHandle child;
""" % (name, name))
offset = len(attrs)
sizes = [compoundTypeNames.get(attr[0], 0) for attr in attrs]
for index, (attrType, attrName, isArray) in enumerate(attrs):
args = 'child'
if isArray:
args = 'handle'
isCompound = attrType in compoundTypeNames
if isCompound:
args += ', objects + %d' % offset
if isArray:
code.append(""" child = element.child(objects[%s]); { MArrayDataHandle handle(child); set<%s>(%s, value.%s); }\n child.setClean();\n\n""" % (index, attrType, args, attrName))
else:
code.append(""" child = element.child(objects[%s]); set<%s>(%s, value.%s);\n child.setClean();\n\n""" % (index, attrType, args, attrName))
offset += sizes[index]
code.append(""" element.setClean();\n}\n\n""")
return ''.join(code)
def scanNode(code, compoundTypeNames):
cursor = code.find('(')
assert cursor != -1
tmp = code.find(')', cursor + 1)
assert tmp != -1
nodeName = code[cursor + 1:tmp]
# for deformers we must strip additional arguments inside the macro
nodeName = nodeName.split(',')[0].strip()
nodeAttrs = []
tokens = ('INPUT(', 'INOUT(', 'OUTPUT(', 'INPUT_ARRAY(', 'INOUT_ARRAY(', 'OUTPUT_ARRAY(')
for ln in code.splitlines():
ln = ln.strip()
for token in tokens:
if ln.startswith(token):
break
else:
continue # There might be some more code inlined
assert ln.endswith(')')
ln = ln.rstrip(')')
isArray = token.endswith('_ARRAY(')
label, args = ln.split('(', 1)
if isArray:
label = label[:-6]
isIn = label == 'INPUT'
isOut = label == 'OUTPUT'
if not isIn and not isOut:
assert label == 'INOUT', label
args = args.split(',')
assert len(args) == 2
args = tuple(arg.strip() for arg in args)
isCompound = args[0] in compoundTypeNames
nodeAttrs.append((isIn, isOut, isArray, isCompound, args[0], args[1]))
return nodeName, nodeAttrs
def processNode(nodeName, nodeAttrs, isDeformer=False):
code = []
inputNames = []
outputNames = []
inoutNames = []
for isIn, isOut, isArray, isCompound, attrType, attrName in nodeAttrs:
code.append('MObject %s::%sAttr;' % (nodeName, attrName))
if isIn:
inputNames.append(attrName)
elif isOut:
outputNames.append(attrName)
else:
inoutNames.append(attrName)
if isCompound:
code.append('std::vector<MObject> _%s_%s_children;' % (nodeName, attrName))
if not isDeformer:
if not inputNames:
code.append("""
bool %s::isInputPlug(const MPlug& p) {
return false;
}
""" % nodeName)
else:
code.append("""
bool %s::isInputPlug(const MPlug& p) {
return (p == %sAttr);
}
""" % (nodeName, 'Attr || p == '.join(inputNames)))
code.append('MStatus %s::initialize() {' % nodeName)
code.append(' MStatus status;\n')
for isIn, isOut, isArray, isCompound, attrType, attrName in nodeAttrs:
args = ''
if isCompound:
args = ', _%s_%s_children' % (nodeName, attrName)
code.append(' status = ::initialize<%s>(%sAttr, "%s"%s); CHECK_MSTATUS_AND_RETURN_IT(status);' % (
attrType, attrName, attrName, args))
if isArray:
code.append(' status = MFnAttribute(%sAttr).setArray(true); CHECK_MSTATUS_AND_RETURN_IT(status);' % attrName)
code.append(' status = addAttribute(%sAttr); CHECK_MSTATUS_AND_RETURN_IT(status);\n' % attrName)
if isDeformer:
nodeAttrs.append((False, True, False, False, None, 'outputGeom'))
for isIn, isOut, isArray, isCompound, attrType, attrName in nodeAttrs:
if isIn:
continue
if isOut and isDeformer:
pass
outName = attrName if attrName == (isDeformer and 'outputGeom') else attrName + 'Attr'
# this is an output or inout attribute, make it affected by all inputs
for isIn2, isOut2, isArray2, isCompound2, attrType2, attrName2 in nodeAttrs:
if not isOut:
# only inputs affect inouts
if not isIn:
continue
else:
# inputs and inouts affect outputs
if isOut2:
continue
code.append(' status = attributeAffects(%sAttr, %s); CHECK_MSTATUS_AND_RETURN_IT(status);' % (attrName2, outName))
if isCompound:
# The input affects each of the output's chidlren
code.append(' for(const MObject& obj : _%s_%s_children) {' % (nodeName, attrName))
code.append(
' status = attributeAffects(%sAttr, obj); CHECK_MSTATUS_AND_RETURN_IT(status);' % attrName2)
# Each of the input's children affect each of the output's children
if isCompound2:
code.append(' for(const MObject& obj2 : _%s_%s_children) {' % (nodeName, attrName2))
code.append(' status = attributeAffects(obj2, obj); CHECK_MSTATUS_AND_RETURN_IT(status);')
code.append(' }')
code.append(' }')
if isCompound2:
# Each of the input's children affect each the output
code.append(' for(const MObject& obj2 : _%s_%s_children) {' % (nodeName, attrName2))
code.append(' status = attributeAffects(obj2, %s); CHECK_MSTATUS_AND_RETURN_IT(status);' % outName)
# Each of the input's children affect each of the output's children'
# if isCompound:
# code.append(' for(const MObject& obj : _%s_%s_children) {' % (nodeName, attrName))
# code.append(' status = attributeAffects(obj2, obj); CHECK_MSTATUS_AND_RETURN_IT(status);')
# code.append(' }')
code.append(' }')
code.append('')
if isDeformer:
nodeAttrs.pop(-1)
if isDeformer:
code.append(' makePaintable("%s");' % nodeName)
code.append(' return status;')
code.append('}\n\n')
for isIn, isOut, isArray, isCompound, attrType, attrName in nodeAttrs:
compoundArgs = ''
if isCompound:
compoundArgs = ', _%s_%s_children' % (nodeName, attrName)
if isArray:
code.append('int %s::%sSize(Meta dataBlock) { return arraySize(dataBlock, %sAttr); }' % (nodeName, attrName, attrName))
if not isOut:
code.append('%s %s::%s(Meta dataBlock, int index) { return getArray<%s>(dataBlock, %sAttr%s, index); }' % (attrType, nodeName, attrName, attrType, attrName, compoundArgs))
if not isIn:
code.append('void %s::%sSet(Meta dataBlock, const std::vector<%s>& value) { setArray<%s>(dataBlock, %sAttr%s, value); }' % (nodeName, attrName, attrType, attrType, attrName, compoundArgs))
else:
if not isOut:
code.append('%s %s::%s(Meta dataBlock) { return getAttr<%s>(dataBlock, %sAttr%s); }' % (attrType, nodeName, attrName, attrType, attrName, compoundArgs))
if not isIn:
code.append('void %s::%sSet(Meta dataBlock, const %s& value) { setAttr<%s>(dataBlock, %sAttr%s, value); }' % (nodeName, attrName, attrType, attrType, attrName, compoundArgs))
code.append('')
return '\n'.join(code)
def main():
root = os.path.abspath('.') # TODO: sys.argv?
output = 'generated.inc'
with open(output, 'w') as fh:
registerPlugin = []
enumNames = []
compoundTypes = []
codeBlocks = []
for path in walk(root):
if os.path.splitext(path)[1].lower() != '.h':
continue
if path.lower().replace('/', '\\').endswith('\\core.h'):
continue
fh.write('#include "%s"\n\n' % os.path.relpath(path, root))
nodeCode, enumCode, compoundCode, deformerCode, typedDeformerCode = scan(path)
for code in enumCode:
enumName, enumOptions = scanEnum(code)
enumNames.append(enumName)
fh.write(processEnum(enumName, enumOptions))
for code in compoundCode:
# compounds can reference other compounds, which we need before we can generate any code
compoundTypes.append(scanCompound(code))
codeBlocks.append((nodeCode, deformerCode, typedDeformerCode))
compoundTypeNames = {name: len(members) for (name, members) in compoundTypes}
for compoundType in compoundTypes:
fh.write(processCompound(compoundType, compoundTypeNames))
for codeBlock in codeBlocks:
for grpId, grp in enumerate(codeBlock):
isDeformer = grpId in (1, 2)
for code in grp:
nodeName, nodeAttrs = scanNode(code, compoundTypeNames)
if isDeformer:
registerPlugin.append('REGISTER_DEFORMER(%s)' % nodeName)
else:
registerPlugin.append('REGISTER_NODE(%s)' % nodeName)
fh.write(processNode(nodeName, nodeAttrs, isDeformer))
if registerPlugin:
fh.write('\n#define INITIALIZE_PLUGIN %s' % ' '.join(registerPlugin))
if __name__ == '__main__':
main()