STATICAI commited on
Commit
f70c35d
·
verified ·
1 Parent(s): 2f0834a

Integrate Standard animation library with Motion Rig

Browse files
Files changed (1) hide show
  1. patch_runtime.py +261 -0
patch_runtime.py CHANGED
@@ -10,6 +10,8 @@ for relative_path in (
10
  "model.py",
11
  "models_ae.py",
12
  "app.py",
 
 
13
  ):
14
  pristine = subprocess.check_output(
15
  ["git", "-c", "safe.directory=/data/app", "-C", "app", "show", f"HEAD:{relative_path}"]
@@ -98,3 +100,262 @@ else:
98
  print("[app/app.py] not found")
99
 
100
  print("patch_runtime.py done.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  "model.py",
11
  "models_ae.py",
12
  "app.py",
13
+ "app_blender.py",
14
+ "util/blender_utils.py",
15
  ):
16
  pristine = subprocess.check_output(
17
  ["git", "-c", "safe.directory=/data/app", "-C", "app", "show", f"HEAD:{relative_path}"]
 
100
  print("[app/app.py] not found")
101
 
102
  print("patch_runtime.py done.")
103
+
104
+
105
+ # STATICAI Standard animation library integration
106
+ def _replace_once(path, old, new, label):
107
+ source = path.read_text()
108
+ count = source.count(old)
109
+ if count != 1:
110
+ raise RuntimeError(f"{label}: expected exactly one patch target, found {count}")
111
+ path.write_text(source.replace(old, new, 1))
112
+
113
+
114
+ blender_utils_path = Path("app/util/blender_utils.py")
115
+ _replace_once(
116
+ blender_utils_path,
117
+ "def load_mixamo_anim(char_file: str, anim_file: str, do_retarget=False, inplace=False, to_tris=False):",
118
+ "def load_mixamo_anim(char_file: str, anim_file: str, do_retarget=False, inplace=False, to_tris=False, action_name=None):",
119
+ "blender_utils signature",
120
+ )
121
+ _replace_once(
122
+ blender_utils_path,
123
+ ' assert anim_armature.animation_data is not None and len(bpy.data.actions) > 0, f"Animation not found in {anim_file}"\n\n set_action(char_armature, anim_armature.animation_data.action)',
124
+ ''' assert anim_armature.animation_data is not None and len(bpy.data.actions) > 0, f"Animation not found in {anim_file}"
125
+
126
+ if action_name:
127
+ candidates = [
128
+ action
129
+ for action in bpy.data.actions
130
+ if action.name == action_name
131
+ or action.name.endswith("|" + action_name)
132
+ or action.name.startswith(action_name + ".")
133
+ ]
134
+ if not candidates:
135
+ available = ", ".join(sorted(action.name for action in bpy.data.actions))
136
+ raise ValueError(f"Animation action {action_name!r} not found. Available: {available}")
137
+ anim_armature.animation_data.action = candidates[0]
138
+ print(f"Selected animation action: {candidates[0].name}")
139
+
140
+ set_action(char_armature, anim_armature.animation_data.action)''',
141
+ "blender_utils action selection",
142
+ )
143
+
144
+ app_blender_path = Path("app/app_blender.py")
145
+ _replace_once(
146
+ app_blender_path,
147
+ ''' blender_utils.load_mixamo_anim(
148
+ [armature_obj, mesh_obj], args.animation_path, do_retarget=args.retarget, inplace=args.inplace
149
+ )''',
150
+ ''' blender_utils.load_mixamo_anim(
151
+ [armature_obj, mesh_obj],
152
+ args.animation_path,
153
+ do_retarget=args.retarget,
154
+ inplace=args.inplace,
155
+ action_name=args.animation_action,
156
+ )''',
157
+ "app_blender load_mixamo_anim",
158
+ )
159
+ _replace_once(
160
+ app_blender_path,
161
+ ' parser.add_argument("--animation_path", type=str, default=None)\n',
162
+ ' parser.add_argument("--animation_path", type=str, default=None)\n parser.add_argument("--animation_action", type=str, default=None)\n',
163
+ "app_blender parser action",
164
+ )
165
+
166
+ app_path = Path("app/app.py")
167
+ _replace_once(app_path, "import gc\nimport os\n", "import gc\nimport json\nimport os\n", "app json import")
168
+ _replace_once(
169
+ app_path,
170
+ ''' animation_file: str,
171
+ retarget: bool,''',
172
+ ''' animation_file: str,
173
+ animation_action: str,
174
+ retarget: bool,''',
175
+ "vis_blender action argument",
176
+ )
177
+ _replace_once(
178
+ app_path,
179
+ ''' animation_path=animation_file,
180
+ retarget=retarget,''',
181
+ ''' animation_path=animation_file,
182
+ animation_action=animation_action,
183
+ retarget=retarget,''',
184
+ "Namespace action argument",
185
+ )
186
+ _replace_once(
187
+ app_path,
188
+ ''' if animation_file is not None:
189
+ cmd += f" --animation_path '{os.path.abspath(animation_file)}'"
190
+ if retarget:
191
+ cmd += " --retarget"
192
+ if inplace:
193
+ cmd += " --inplace"''',
194
+ ''' if animation_file is not None:
195
+ cmd += f" --animation_path '{os.path.abspath(animation_file)}'"
196
+ if animation_action:
197
+ safe_action = animation_action.replace("'", "")
198
+ cmd += f" --animation_action '{safe_action}'"
199
+ if retarget:
200
+ cmd += " --retarget"
201
+ if inplace:
202
+ cmd += " --inplace"''',
203
+ "subprocess action argument",
204
+ )
205
+ _replace_once(
206
+ app_path,
207
+ ''' animation_file: str = None,
208
+ retarget=True,''',
209
+ ''' animation_file: str = None,
210
+ animation_action: str = None,
211
+ retarget=True,''',
212
+ "pipeline action argument",
213
+ )
214
+ _replace_once(
215
+ app_path,
216
+ ''' yield vis_blender(
217
+ reset_to_rest, no_fingers, rest_pose_type, ignore_pose_parts, animation_file, retarget, inplace, db
218
+ )''',
219
+ ''' yield vis_blender(
220
+ reset_to_rest,
221
+ no_fingers,
222
+ rest_pose_type,
223
+ ignore_pose_parts,
224
+ animation_file,
225
+ animation_action,
226
+ retarget,
227
+ inplace,
228
+ db,
229
+ )''',
230
+ "pipeline vis_blender action",
231
+ )
232
+ _replace_once(app_path, ' title = "Make-It-Animatable"\n', ' title = "Motion Rig"\n', "Motion Rig title")
233
+ _replace_once(
234
+ app_path,
235
+ ' animation_dir = "data/Mixamo/animation"\n',
236
+ ''' animation_dir = "data/Mixamo/animation"
237
+ standard_library_root = "/data/animations/Standard"
238
+ standard_manifest_path = os.path.join(standard_library_root, "ANIMATION_MANIFEST.json")
239
+ standard_animation_lookup = {}
240
+ standard_animation_choices = []
241
+ if os.path.isfile(standard_manifest_path):
242
+ with open(standard_manifest_path, "r", encoding="utf-8") as fh:
243
+ manifest = json.load(fh)
244
+ for entry_index, entry in enumerate(manifest.get("entries", [])):
245
+ motion_file = os.path.join(standard_library_root, entry["file"])
246
+ variant = "Root Motion" if entry.get("root_motion") else "In Place"
247
+ for action in entry.get("actions", []):
248
+ if not action or action == "A_TPose":
249
+ continue
250
+ key = f"{entry_index}:{action}"
251
+ label = f"Standard {entry.get('set')} | {action} | {variant}"
252
+ standard_animation_lookup[key] = {"path": motion_file, "action": action}
253
+ standard_animation_choices.append((label, key))
254
+ ''',
255
+ "Standard library manifest load",
256
+ )
257
+ _replace_once(
258
+ app_path,
259
+ ' sorted(glob("*.fbx", root_dir=animation_dir)),\n',
260
+ ' standard_animation_choices,\n',
261
+ "Standard animation choices",
262
+ )
263
+ _replace_once(
264
+ app_path,
265
+ ' label="Select Animation File",\n',
266
+ ' label="Standard Animation Library",\n',
267
+ "Standard animation label",
268
+ )
269
+ _replace_once(
270
+ app_path,
271
+ ''' input_animation_file = gr.File(
272
+ label="Animation File",
273
+ file_types=[".fbx"],
274
+ value=lambda: "./data/Standard Run.fbx",
275
+ interactive=True,
276
+ )''',
277
+ ''' input_animation_file = gr.File(
278
+ label="Animation File",
279
+ file_types=[".fbx"],
280
+ value=lambda: "./data/Standard Run.fbx",
281
+ interactive=True,
282
+ )
283
+ animation_action_state = gr.State(value=None)''',
284
+ "animation action state",
285
+ )
286
+ _replace_once(
287
+ app_path,
288
+ ''' input_animation_file,
289
+ input_retarget,''',
290
+ ''' input_animation_file,
291
+ animation_action_state,
292
+ input_retarget,''',
293
+ "inputs tuple action state",
294
+ )
295
+ _replace_once(
296
+ app_path,
297
+ ''' animation_file=inputs[input_animation_file],
298
+ retarget=inputs[input_retarget],''',
299
+ ''' animation_file=inputs[input_animation_file],
300
+ animation_action=inputs[animation_action_state],
301
+ retarget=inputs[input_retarget],''',
302
+ "pipeline UI action",
303
+ )
304
+ _replace_once(
305
+ app_path,
306
+ ''' input_animation_file,
307
+ input_retarget,''',
308
+ ''' input_animation_file,
309
+ animation_action_state,
310
+ input_retarget,''',
311
+ "animate inputs action state",
312
+ )
313
+ _replace_once(
314
+ app_path,
315
+ ''' def select2file(selected: str):
316
+ return None if selected is None else os.path.join(animation_dir, selected)
317
+
318
+ select_animation_file.input(
319
+ lambda x: (select2file(x), gr.Checkbox(value=True)),
320
+ inputs=select_animation_file,
321
+ outputs=[input_animation_file, input_reset_to_rest],
322
+ preprocess=False,
323
+ )
324
+ input_animation_file.upload(
325
+ lambda: (gr.Dropdown(value=None), gr.Checkbox(value=True)),
326
+ outputs=[select_animation_file, input_reset_to_rest],
327
+ )
328
+ input_animation_file.clear(
329
+ lambda: gr.Dropdown(value=None), outputs=select_animation_file, show_progress="hidden"
330
+ )''',
331
+ ''' def select2file(selected: str):
332
+ if selected is None:
333
+ return None, None
334
+ item = standard_animation_lookup.get(selected)
335
+ if item is None:
336
+ raise gr.Error(f"Unknown Standard animation selection: {selected}")
337
+ return item["path"], item["action"]
338
+
339
+ def select_library_animation(selected: str):
340
+ path, action = select2file(selected)
341
+ return path, action, gr.Checkbox(value=True)
342
+
343
+ select_animation_file.input(
344
+ select_library_animation,
345
+ inputs=select_animation_file,
346
+ outputs=[input_animation_file, animation_action_state, input_reset_to_rest],
347
+ preprocess=False,
348
+ )
349
+ input_animation_file.upload(
350
+ lambda: (gr.Dropdown(value=None), None, gr.Checkbox(value=True)),
351
+ outputs=[select_animation_file, animation_action_state, input_reset_to_rest],
352
+ )
353
+ input_animation_file.clear(
354
+ lambda: (gr.Dropdown(value=None), None),
355
+ outputs=[select_animation_file, animation_action_state],
356
+ show_progress="hidden",
357
+ )''',
358
+ "Standard selection events",
359
+ )
360
+
361
+ print("STATICAI Standard animation library integration patched.")