example-robot-data-loaders 5.0.0__1-py313-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1293 @@
1
+ import numpy as np
2
+ import pinocchio as pin
3
+
4
+ from .utils import RobotLoader
5
+
6
+
7
+ class HumanLoader(RobotLoader):
8
+ path = "human_description"
9
+ urdf_filename = "human.urdf"
10
+ free_flyer = True
11
+ ref_posture = "anatomical"
12
+ # Enforced, unchangeable free-flyer orientation (90° about X, and Y↔Z swap)
13
+ freeflyer_ori: np.ndarray = np.array(
14
+ [
15
+ [1, 0, 0],
16
+ [0, 0, -1],
17
+ [0, 1, 0],
18
+ ]
19
+ )
20
+
21
+ def __init__(
22
+ self,
23
+ height: float | None = None,
24
+ weight: float | None = None,
25
+ gender: str = "m",
26
+ verbose: bool = False,
27
+ ):
28
+ """
29
+ Initialize the robot loader with optional scaling and configuration parameters. The scaling of the model is based on Dumas 2007, except for the
30
+ abdomen and thorax segments, which are scaled based de Leva 1996.
31
+ Parameters:
32
+ height (float, optional): The height to scale the robot model to. If None, uses the default height in the urdf of 1.80m.
33
+ weight (float, optional): The weight to scale the robot model to. If None, uses the default weight in the urdf 75kg.
34
+ gender (str, optional): The gender specification for scaling, default is 'm'.
35
+ verbose (bool, optional): If True, enables verbose output. Default is False.
36
+ Behavior:
37
+ - Calls the base loader's initializer.
38
+ - If height or weight is provided, scales the robot model accordingly.
39
+ - The free-flyer is present, it enforces its orientation and reapplies joint limits.
40
+ """
41
+
42
+ # call base loader
43
+ super().__init__(verbose=verbose)
44
+
45
+ if height is not None or weight is not None:
46
+ self._scale_pin_model(
47
+ self.robot.model, self.robot.visual_model, height, weight, gender
48
+ )
49
+
50
+ # automatically apply the enforced free-flyer orientation
51
+ if self.free_flyer:
52
+ # get joint index
53
+ j_id = self.robot.model.getJointId("root_joint")
54
+ # assign enforced rotation
55
+ self.robot.model.jointPlacements[j_id].rotation = self.freeflyer_ori
56
+ # re-apply limits
57
+ self.addFreeFlyerJointLimits()
58
+
59
+ @staticmethod
60
+ def get_dict_inertial_param(
61
+ height: float, weight: float, gender: str = "m"
62
+ ) -> dict:
63
+ inertial_segment_names = [
64
+ "middle_pelvis",
65
+ "left_upperleg",
66
+ "left_lowerleg",
67
+ "left_foot",
68
+ "middle_abdomen",
69
+ "middle_thorax",
70
+ "middle_head",
71
+ "left_upperarm",
72
+ "left_lowerarm",
73
+ "left_hand",
74
+ "right_upperarm",
75
+ "right_lowerarm",
76
+ "right_hand",
77
+ "right_upperleg",
78
+ "right_lowerleg",
79
+ "right_foot",
80
+ ]
81
+
82
+ sgt_lengths = {}
83
+
84
+ dicts = []
85
+
86
+ # Ratios from Dumas 2007 and De Leva 1996 for abdomen and thorax
87
+ for name in inertial_segment_names:
88
+ dict_sgmt = {}
89
+ if name == "middle_pelvis":
90
+ length = (0.0634 if gender == "f" else 0.0505) * height
91
+ sgt_lengths["middle_pelvis_0"] = length
92
+ sgt_lengths["middle_pelvis_0_width"] = (
93
+ 0.1478 if gender == "f" else 0.1265
94
+ ) * height
95
+ dict_sgmt["mass"] = np.round(
96
+ (0.146 if gender == "f" else 0.142) * weight, 2
97
+ )
98
+ dict_sgmt["com"] = np.round(
99
+ (
100
+ np.array([-0.009, -0.232, 0.002])
101
+ if gender == "f"
102
+ else np.array([0.028, -0.28, -0.006])
103
+ )
104
+ * length,
105
+ 3,
106
+ )
107
+ dict_sgmt["inertia"] = np.round(
108
+ (
109
+ np.array(
110
+ [
111
+ (length * 0.91) ** 2,
112
+ -((length * 0.34) ** 2),
113
+ -((length * 0.01) ** 2),
114
+ (length * 1) ** 2,
115
+ -((length * 0.01) ** 2),
116
+ (length * 0.79) ** 2,
117
+ ]
118
+ )
119
+ if gender == "f"
120
+ else np.array(
121
+ [
122
+ (length * 1.01) ** 2,
123
+ -((length * 0.25) ** 2),
124
+ -((length * 0.12) ** 2),
125
+ (length * 1.06) ** 2,
126
+ -((length * 0.08) ** 2),
127
+ (length * 0.95) ** 2,
128
+ ]
129
+ )
130
+ )
131
+ * dict_sgmt["mass"],
132
+ 5,
133
+ )
134
+ dicts.append(dict_sgmt)
135
+ elif name == "left_upperleg":
136
+ length = (0.2354 if gender == "f" else 0.2441) * height
137
+ sgt_lengths["left_upperleg_0"] = length
138
+ dict_sgmt["mass"] = np.round(
139
+ (0.146 if gender == "f" else 0.123) * weight, 2
140
+ )
141
+ dict_sgmt["com"] = np.round(
142
+ (
143
+ np.array([-0.077, -0.377, 0.009])
144
+ if gender == "f"
145
+ else np.array([-0.041, -0.429, 0.033])
146
+ )
147
+ * length,
148
+ 3,
149
+ )
150
+ dict_sgmt["inertia"] = np.round(
151
+ (
152
+ np.array(
153
+ [
154
+ (length * 0.31) ** 2,
155
+ (length * 0.07) ** 2,
156
+ -((length * 0.02) ** 2),
157
+ (length * 0.19) ** 2,
158
+ -((length * 0.07) ** 2),
159
+ (length * 0.32) ** 2,
160
+ ]
161
+ )
162
+ if gender == "f"
163
+ else np.array(
164
+ [
165
+ (length * 0.29) ** 2,
166
+ (length * 0.07) ** 2,
167
+ -((length * 0.02) ** 2),
168
+ (length * 0.15) ** 2,
169
+ -((length * 0.07) ** 2),
170
+ (length * 0.3) ** 2,
171
+ ]
172
+ )
173
+ )
174
+ * dict_sgmt["mass"],
175
+ 5,
176
+ )
177
+ dicts.append(dict_sgmt)
178
+ elif name == "left_lowerleg":
179
+ length = (0.2410 if gender == "f" else 0.2446) * height
180
+ sgt_lengths["left_lowerleg_0"] = length
181
+ dict_sgmt["mass"] = np.round(
182
+ (0.045 if gender == "f" else 0.048) * weight, 2
183
+ )
184
+ dict_sgmt["com"] = np.round(
185
+ (
186
+ np.array([-0.049, -0.404, 0.031])
187
+ if gender == "f"
188
+ else np.array([-0.048, -0.41, 0.007])
189
+ )
190
+ * length,
191
+ 3,
192
+ )
193
+ dict_sgmt["inertia"] = np.round(
194
+ (
195
+ np.array(
196
+ [
197
+ (length * 0.28) ** 2,
198
+ (length * 0.02) ** 2,
199
+ (length * 0.01) ** 2,
200
+ (length * 0.1) ** 2,
201
+ (length * 0.06) ** 2,
202
+ (length * 0.28) ** 2,
203
+ ]
204
+ )
205
+ if gender == "f"
206
+ else np.array(
207
+ [
208
+ (length * 0.28) ** 2,
209
+ (length * 0.04) ** 2,
210
+ (length * 0.02) ** 2,
211
+ (length * 0.1) ** 2,
212
+ (length * 0.05) ** 2,
213
+ (length * 0.28) ** 2,
214
+ ]
215
+ )
216
+ )
217
+ * dict_sgmt["mass"],
218
+ 5,
219
+ )
220
+ dicts.append(dict_sgmt)
221
+ elif name == "left_foot":
222
+ length = (0.1447 if gender == "f" else 0.1497) * height
223
+ sgt_lengths["left_foot_0"] = length
224
+ dict_sgmt["mass"] = np.round(
225
+ (0.01 if gender == "f" else 0.012) * weight, 2
226
+ )
227
+ dict_sgmt["com"] = np.round(
228
+ (
229
+ np.array([0.27, -0.218, 0.039])
230
+ if gender == "f"
231
+ else np.array([0.382, -0.151, 0.026])
232
+ )
233
+ * length,
234
+ 3,
235
+ )
236
+ dict_sgmt["inertia"] = np.round(
237
+ (
238
+ np.array(
239
+ [
240
+ (length * 0.17) ** 2,
241
+ -((length * 0.10) ** 2),
242
+ (length * 0.06) ** 2,
243
+ (length * 0.36) ** 2,
244
+ -((length * 0.04) ** 2),
245
+ (length * 0.35) ** 2,
246
+ ]
247
+ )
248
+ if gender == "f"
249
+ else np.array(
250
+ [
251
+ (length * 0.17) ** 2,
252
+ (length * 0.13) ** 2,
253
+ -((length * 0.08) ** 2),
254
+ (length * 0.37) ** 2,
255
+ (length * 0) ** 2,
256
+ (length * 0.36) ** 2,
257
+ ]
258
+ )
259
+ )
260
+ * dict_sgmt["mass"],
261
+ 5,
262
+ )
263
+ dicts.append(dict_sgmt)
264
+ elif name == "middle_abdomen":
265
+ length = (0.1183 if gender == "f" else 0.1237) * height
266
+ sgt_lengths["middle_abdomen_0"] = length
267
+ dict_sgmt["mass"] = np.round(
268
+ (0.1465 if gender == "f" else 0.1633) * weight, 2
269
+ )
270
+ dict_sgmt["com"] = np.round(
271
+ (
272
+ np.array([-0.016, -0.4512, -0.006])
273
+ if gender == "f"
274
+ else np.array([-0.036, 0.4502, -0.002])
275
+ )
276
+ * length,
277
+ 3,
278
+ )
279
+ dict_sgmt["inertia"] = np.round(
280
+ (
281
+ np.array(
282
+ [
283
+ (length * 0.29) ** 2,
284
+ (length * 0.22) ** 2,
285
+ (length * 0.05) ** 2,
286
+ (length * 0.27) ** 2,
287
+ -((length * 0.05) ** 2),
288
+ (length * 0.29) ** 2,
289
+ ]
290
+ )
291
+ if gender == "f"
292
+ else np.array(
293
+ [
294
+ (length * 0.27) ** 2,
295
+ (length * 0.18) ** 2,
296
+ -((length * 0.02) ** 2),
297
+ (length * 0.25) ** 2,
298
+ -((length * 0.04) ** 2),
299
+ (length * 0.28) ** 2,
300
+ ]
301
+ )
302
+ )
303
+ * dict_sgmt["mass"],
304
+ 5,
305
+ )
306
+ dicts.append(dict_sgmt)
307
+ elif name == "middle_thorax":
308
+ length = (0.1314 if gender == "f" else 0.1390) * height
309
+ sgt_lengths["middle_thorax_0"] = length
310
+ dict_sgmt["mass"] = np.round(
311
+ (0.1545 if gender == "f" else 0.1596) * weight, 2
312
+ )
313
+ dict_sgmt["com"] = np.round(
314
+ (
315
+ np.array([-0.016, 0.5050, -0.006])
316
+ if gender == "f"
317
+ else np.array([-0.036, 0.5066, -0.002])
318
+ )
319
+ * length,
320
+ 3,
321
+ )
322
+ dict_sgmt["inertia"] = np.round(
323
+ (
324
+ np.array(
325
+ [
326
+ (length * 0.29) ** 2,
327
+ (length * 0.22) ** 2,
328
+ (length * 0.05) ** 2,
329
+ (length * 0.27) ** 2,
330
+ -((length * 0.05) ** 2),
331
+ (length * 0.29) ** 2,
332
+ ]
333
+ )
334
+ if gender == "f"
335
+ else np.array(
336
+ [
337
+ (length * 0.27) ** 2,
338
+ (length * 0.18) ** 2,
339
+ -((length * 0.02) ** 2),
340
+ (length * 0.25) ** 2,
341
+ -((length * 0.04) ** 2),
342
+ (length * 0.28) ** 2,
343
+ ]
344
+ )
345
+ )
346
+ * dict_sgmt["mass"],
347
+ 5,
348
+ )
349
+ dicts.append(dict_sgmt)
350
+ elif name == "middle_head":
351
+ length = (0.1308 if gender == "f" else 0.1310) * height
352
+ sgt_lengths["middle_head_0"] = length
353
+ dict_sgmt["mass"] = np.round(
354
+ 0.067 * weight, 2
355
+ ) # same for male and female
356
+ dict_sgmt["com"] = np.round(
357
+ (
358
+ np.array([-0.07, 0.597, 0])
359
+ if gender == "f"
360
+ else np.array([-0.062, 0.555, 0.001])
361
+ )
362
+ * length,
363
+ 3,
364
+ )
365
+ dict_sgmt["inertia"] = np.round(
366
+ (
367
+ np.array(
368
+ [
369
+ (length * 0.32) ** 2,
370
+ -((length * 0.06) ** 2),
371
+ (length * 0.01) ** 2,
372
+ (length * 0.27) ** 2,
373
+ -((length * 0.01) ** 2),
374
+ (length * 0.34) ** 2,
375
+ ]
376
+ )
377
+ if gender == "f"
378
+ else np.array(
379
+ [
380
+ (length * 0.31) ** 2,
381
+ -((length * 0.09) ** 2),
382
+ -((length * 0.02) ** 2),
383
+ (length * 0.25) ** 2,
384
+ (length * 0.03) ** 2,
385
+ (length * 0.33) ** 2,
386
+ ]
387
+ )
388
+ )
389
+ * dict_sgmt["mass"],
390
+ 5,
391
+ )
392
+ dicts.append(dict_sgmt)
393
+ elif name == "left_upperarm":
394
+ length = (0.1510 if gender == "f" else 0.1531) * height
395
+ sgt_lengths["left_upperarm_0"] = length
396
+ dict_sgmt["mass"] = np.round(
397
+ (0.022 if gender == "f" else 0.024) * weight, 2
398
+ )
399
+ dict_sgmt["com"] = np.round(
400
+ (
401
+ np.array([-0.073, -0.454, -0.028])
402
+ if gender == "f"
403
+ else np.array([0.017, -0.452, -0.026])
404
+ )
405
+ * length,
406
+ 3,
407
+ )
408
+ dict_sgmt["inertia"] = np.round(
409
+ (
410
+ np.array(
411
+ [
412
+ (length * 0.33) ** 2,
413
+ (length * 0.03) ** 2,
414
+ -((length * 0.05) ** 2),
415
+ (length * 0.17) ** 2,
416
+ -((length * 0.14) ** 2),
417
+ (length * 0.33) ** 2,
418
+ ]
419
+ )
420
+ if gender == "f"
421
+ else np.array(
422
+ [
423
+ (length * 0.31) ** 2,
424
+ (length * 0.06) ** 2,
425
+ (length * 0.05) ** 2,
426
+ (length * 0.14) ** 2,
427
+ (length * 0.02) ** 2,
428
+ (length * 0.32) ** 2,
429
+ ]
430
+ )
431
+ )
432
+ * dict_sgmt["mass"],
433
+ 5,
434
+ )
435
+ dicts.append(dict_sgmt)
436
+ elif name == "left_lowerarm":
437
+ length = (0.1534 if gender == "f" else 0.1593) * height
438
+ sgt_lengths["left_lowerarm_0"] = length
439
+ dict_sgmt["mass"] = np.round(
440
+ (0.013 if gender == "f" else 0.017) * weight, 2
441
+ )
442
+ dict_sgmt["com"] = np.round(
443
+ (
444
+ np.array([0.021, -0.411, 0.019])
445
+ if gender == "f"
446
+ else np.array([0.01, -0.417, 0.014])
447
+ )
448
+ * length,
449
+ 3,
450
+ )
451
+ dict_sgmt["inertia"] = np.round(
452
+ (
453
+ np.array(
454
+ [
455
+ (length * 0.26) ** 2,
456
+ (length * 0.1) ** 2,
457
+ (length * 0.04) ** 2,
458
+ (length * 0.14) ** 2,
459
+ -((length * 0.13) ** 2),
460
+ (length * 0.25) ** 2,
461
+ ]
462
+ )
463
+ if gender == "f"
464
+ else np.array(
465
+ [
466
+ (length * 0.28) ** 2,
467
+ (length * 0.03) ** 2,
468
+ (length * 0.02) ** 2,
469
+ (length * 0.11) ** 2,
470
+ -((length * 0.08) ** 2),
471
+ (length * 0.27) ** 2,
472
+ ]
473
+ )
474
+ )
475
+ * dict_sgmt["mass"],
476
+ 5,
477
+ )
478
+ dicts.append(dict_sgmt)
479
+ elif name == "left_hand":
480
+ length = (0.0989 if gender == "f" else 0.1014) * height
481
+ sgt_lengths["left_hand_0"] = length
482
+ dict_sgmt["mass"] = np.round(
483
+ (0.005 if gender == "f" else 0.006) * weight, 2
484
+ )
485
+ dict_sgmt["com"] = np.round(
486
+ (
487
+ np.array([0.077, -0.768, 0.048])
488
+ if gender == "f"
489
+ else np.array([0.082, -0.839, 0.074])
490
+ )
491
+ * length,
492
+ 3,
493
+ )
494
+ dict_sgmt["inertia"] = np.round(
495
+ (
496
+ np.array(
497
+ [
498
+ (length * 0.63) ** 2,
499
+ (length * 0.29) ** 2,
500
+ (length * 0.23) ** 2,
501
+ (length * 0.43) ** 2,
502
+ -((length * 0.28) ** 2),
503
+ (length * 0.58) ** 2,
504
+ ]
505
+ )
506
+ if gender == "f"
507
+ else np.array(
508
+ [
509
+ (length * 0.61) ** 2,
510
+ (length * 0.22) ** 2,
511
+ (length * 0.15) ** 2,
512
+ (length * 0.38) ** 2,
513
+ -((length * 0.2) ** 2),
514
+ (length * 0.56) ** 2,
515
+ ]
516
+ )
517
+ )
518
+ * dict_sgmt["mass"],
519
+ 5,
520
+ )
521
+ dicts.append(dict_sgmt)
522
+ elif name == "right_upperarm":
523
+ length = (0.1510 if gender == "f" else 0.1531) * height
524
+ sgt_lengths["right_upperarm_0"] = length
525
+ dict_sgmt["mass"] = np.round(
526
+ (0.022 if gender == "f" else 0.024) * weight, 2
527
+ )
528
+ dict_sgmt["com"] = np.round(
529
+ (
530
+ np.array([-0.073, -0.454, -0.028])
531
+ if gender == "f"
532
+ else np.array([0.017, -0.452, -0.026])
533
+ )
534
+ * length,
535
+ 3,
536
+ )
537
+ dict_sgmt["inertia"] = np.round(
538
+ (
539
+ np.array(
540
+ [
541
+ (length * 0.33) ** 2,
542
+ (length * 0.03) ** 2,
543
+ -((length * 0.05) ** 2),
544
+ (length * 0.17) ** 2,
545
+ -((length * 0.14) ** 2),
546
+ (length * 0.33) ** 2,
547
+ ]
548
+ )
549
+ if gender == "f"
550
+ else np.array(
551
+ [
552
+ (length * 0.31) ** 2,
553
+ (length * 0.06) ** 2,
554
+ (length * 0.05) ** 2,
555
+ (length * 0.14) ** 2,
556
+ (length * 0.02) ** 2,
557
+ (length * 0.32) ** 2,
558
+ ]
559
+ )
560
+ )
561
+ * dict_sgmt["mass"],
562
+ 5,
563
+ )
564
+ dicts.append(dict_sgmt)
565
+ elif name == "right_lowerarm":
566
+ length = (0.1534 if gender == "f" else 0.1593) * height
567
+ sgt_lengths["right_lowerarm_0"] = length
568
+ dict_sgmt["mass"] = np.round(
569
+ (0.013 if gender == "f" else 0.017) * weight, 2
570
+ )
571
+ dict_sgmt["com"] = np.round(
572
+ (
573
+ np.array([0.021, -0.411, 0.019])
574
+ if gender == "f"
575
+ else np.array([0.01, -0.417, 0.014])
576
+ )
577
+ * length,
578
+ 3,
579
+ )
580
+ dict_sgmt["inertia"] = np.round(
581
+ (
582
+ np.array(
583
+ [
584
+ (length * 0.26) ** 2,
585
+ (length * 0.1) ** 2,
586
+ (length * 0.04) ** 2,
587
+ (length * 0.14) ** 2,
588
+ -((length * 0.13) ** 2),
589
+ (length * 0.25) ** 2,
590
+ ]
591
+ )
592
+ if gender == "f"
593
+ else np.array(
594
+ [
595
+ (length * 0.28) ** 2,
596
+ (length * 0.03) ** 2,
597
+ (length * 0.02) ** 2,
598
+ (length * 0.11) ** 2,
599
+ -((length * 0.08) ** 2),
600
+ (length * 0.27) ** 2,
601
+ ]
602
+ )
603
+ )
604
+ * dict_sgmt["mass"],
605
+ 5,
606
+ )
607
+ dicts.append(dict_sgmt)
608
+ elif name == "right_hand":
609
+ length = (0.0989 if gender == "f" else 0.1014) * height
610
+ sgt_lengths["right_hand_0"] = length
611
+ dict_sgmt["mass"] = np.round(
612
+ (0.005 if gender == "f" else 0.006) * weight, 2
613
+ )
614
+ dict_sgmt["com"] = np.round(
615
+ (
616
+ np.array([0.077, -0.768, 0.048])
617
+ if gender == "f"
618
+ else np.array([0.082, -0.839, 0.074])
619
+ )
620
+ * length,
621
+ 3,
622
+ )
623
+ dict_sgmt["inertia"] = np.round(
624
+ (
625
+ np.array(
626
+ [
627
+ (length * 0.63) ** 2,
628
+ (length * 0.29) ** 2,
629
+ (length * 0.23) ** 2,
630
+ (length * 0.43) ** 2,
631
+ -((length * 0.28) ** 2),
632
+ (length * 0.58) ** 2,
633
+ ]
634
+ )
635
+ if gender == "f"
636
+ else np.array(
637
+ [
638
+ (length * 0.61) ** 2,
639
+ (length * 0.22) ** 2,
640
+ (length * 0.15) ** 2,
641
+ (length * 0.38) ** 2,
642
+ -((length * 0.2) ** 2),
643
+ (length * 0.56) ** 2,
644
+ ]
645
+ )
646
+ )
647
+ * dict_sgmt["mass"],
648
+ 5,
649
+ )
650
+ dicts.append(dict_sgmt)
651
+ elif name == "right_upperleg":
652
+ length = (0.2354 if gender == "f" else 0.2441) * height
653
+ sgt_lengths["right_upperleg_0"] = length
654
+ dict_sgmt["mass"] = np.round(
655
+ (0.146 if gender == "f" else 0.123) * weight, 2
656
+ )
657
+ dict_sgmt["com"] = np.round(
658
+ (
659
+ np.array([-0.077, -0.377, 0.009])
660
+ if gender == "f"
661
+ else np.array([-0.041, -0.429, 0.033])
662
+ )
663
+ * length,
664
+ 3,
665
+ )
666
+ dict_sgmt["inertia"] = np.round(
667
+ (
668
+ np.array(
669
+ [
670
+ (length * 0.31) ** 2,
671
+ -((length * 0.07) ** 2),
672
+ -((length * 0.02) ** 2),
673
+ (length * 0.19) ** 2,
674
+ -((length * 0.07) ** 2),
675
+ (length * 0.32) ** 2,
676
+ ]
677
+ )
678
+ if gender == "f"
679
+ else np.array(
680
+ [
681
+ (length * 0.29) ** 2,
682
+ -((length * 0.07) ** 2),
683
+ -((length * 0.02) ** 2),
684
+ (length * 0.15) ** 2,
685
+ -((length * 0.07) ** 2),
686
+ (length * 0.3) ** 2,
687
+ ]
688
+ )
689
+ )
690
+ * dict_sgmt["mass"],
691
+ 5,
692
+ )
693
+ dicts.append(dict_sgmt)
694
+ elif name == "right_lowerleg":
695
+ length = (0.2410 if gender == "f" else 0.2446) * height
696
+ sgt_lengths["right_lowerleg_0"] = length
697
+ dict_sgmt["mass"] = np.round(
698
+ (0.045 if gender == "f" else 0.048) * weight, 2
699
+ )
700
+ dict_sgmt["com"] = np.round(
701
+ (
702
+ np.array([-0.049, -0.404, 0.031])
703
+ if gender == "f"
704
+ else np.array([-0.048, -0.41, 0.007])
705
+ )
706
+ * length,
707
+ 3,
708
+ )
709
+ dict_sgmt["inertia"] = np.round(
710
+ (
711
+ np.array(
712
+ [
713
+ (length * 0.28) ** 2,
714
+ (length * 0.02) ** 2,
715
+ (length * 0.01) ** 2,
716
+ (length * 0.1) ** 2,
717
+ (length * 0.06) ** 2,
718
+ (length * 0.28) ** 2,
719
+ ]
720
+ )
721
+ if gender == "f"
722
+ else np.array(
723
+ [
724
+ (length * 0.28) ** 2,
725
+ (length * 0.04) ** 2,
726
+ (length * 0.02) ** 2,
727
+ (length * 0.1) ** 2,
728
+ (length * 0.05) ** 2,
729
+ (length * 0.28) ** 2,
730
+ ]
731
+ )
732
+ )
733
+ * dict_sgmt["mass"],
734
+ 5,
735
+ )
736
+ dicts.append(dict_sgmt)
737
+ elif name == "right_foot":
738
+ length = (0.1447 if gender == "f" else 0.1497) * height
739
+ sgt_lengths["right_foot_0"] = length
740
+ dict_sgmt["mass"] = np.round(
741
+ (0.01 if gender == "f" else 0.012) * weight, 2
742
+ )
743
+ dict_sgmt["com"] = np.round(
744
+ (
745
+ np.array([0.27, -0.218, 0.039])
746
+ if gender == "f"
747
+ else np.array([0.382, -0.151, 0.026])
748
+ )
749
+ * length,
750
+ 3,
751
+ )
752
+ dict_sgmt["inertia"] = np.round(
753
+ (
754
+ np.array(
755
+ [
756
+ (length * 0.17) ** 2,
757
+ -((length * 0.10) ** 2),
758
+ (length * 0.06) ** 2,
759
+ (length * 0.36) ** 2,
760
+ -((length * 0.04) ** 2),
761
+ (length * 0.35) ** 2,
762
+ ]
763
+ )
764
+ if gender == "f"
765
+ else np.array(
766
+ [
767
+ (length * 0.17) ** 2,
768
+ (length * 0.13) ** 2,
769
+ -((length * 0.08) ** 2),
770
+ (length * 0.37) ** 2,
771
+ (length * 0) ** 2,
772
+ (length * 0.36) ** 2,
773
+ ]
774
+ )
775
+ )
776
+ * dict_sgmt["mass"],
777
+ 5,
778
+ )
779
+ dicts.append(dict_sgmt)
780
+
781
+ return dict(zip(inertial_segment_names, dicts)), sgt_lengths
782
+
783
+ @staticmethod
784
+ def get_dict_joint_placements(height: float, gender: str = "m") -> dict:
785
+ joint_names = [
786
+ "middle_thoracic_Z",
787
+ "middle_cervical_Z",
788
+ "left_clavicle_joint_X",
789
+ "right_clavicle_joint_X",
790
+ "left_shoulder_Z",
791
+ "left_elbow_Z",
792
+ "left_wrist_Z",
793
+ "right_shoulder_Z",
794
+ "right_elbow_Z",
795
+ "right_wrist_Z",
796
+ "left_hip_Z",
797
+ "left_knee_Z",
798
+ "left_ankle_Z",
799
+ "right_hip_Z",
800
+ "right_knee_Z",
801
+ "right_ankle_Z",
802
+ ]
803
+
804
+ lengths_names = [
805
+ "L_abdomen",
806
+ "L_thorax_cerv",
807
+ "L_thorax_supr",
808
+ "L_upperarm",
809
+ "L_lowerarm",
810
+ "L_upperleg",
811
+ "L_lowerleg",
812
+ ]
813
+ ratios = np.array(
814
+ [
815
+ 0.1183
816
+ if gender == "f"
817
+ else 0.1237, # L_abdomen MPT from XYP to OMPH (De Leva 1996)
818
+ 0.1314
819
+ if gender == "f"
820
+ else 0.1390, # L_thorax UPT from CERV to XYPH (De Leva 1996)
821
+ 0.0821 if gender == "f" else 0.0980, # from SUPR to XYPH (De Leva 1996)
822
+ 0.1510 if gender == "f" else 0.1531, # L_upperarm (Dumas 2007)
823
+ 0.1534 if gender == "f" else 0.1593, # L_lowerarm (Dumas 2007)
824
+ 0.2354 if gender == "f" else 0.2441, # L_upperleg (Dumas 2007)
825
+ 0.2410 if gender == "f" else 0.2446, # L_lowerleg (Dumas 2007)
826
+ ]
827
+ )
828
+ # Ratios from De Leva : HJC from Seidel
829
+ # SJC Schnorenberg-style AC/H = 0.132 (F) or 0.0129 (M)
830
+ SJ_thorax_X_r = 0.0043 if gender == "f" else 0.0046
831
+ SJ_thorax_Y_r = -0.0449 if gender == "f" else -0.0416
832
+ SJ_thorax_Z_r = 0.1108 if gender == "f" else 0.1164
833
+ HJ_pelvis_X_r = 0.0138 if gender == "f" else 0.0126
834
+ HJ_pelvis_Y_r = -0.0570 if gender == "f" else -0.0558
835
+ HJ_pelvis_Z_r = 0.0548 if gender == "f" else 0.0457
836
+
837
+ lengths = np.round(ratios * height, 3) # mm accuracy
838
+ dict_lengths = dict(zip(lengths_names, lengths))
839
+
840
+ joint_placements = []
841
+ for j in joint_names:
842
+ if j == "middle_thoracic_Z":
843
+ joint_placements.append(np.array([0, dict_lengths["L_abdomen"], 0]))
844
+ elif j == "middle_cervical_Z":
845
+ joint_placements.append(np.array([0, dict_lengths["L_thorax_cerv"], 0]))
846
+ elif j == "left_clavicle_joint_X":
847
+ joint_placements.append(np.array([0, dict_lengths["L_thorax_supr"], 0]))
848
+ elif j == "right_clavicle_joint_X":
849
+ joint_placements.append(np.array([0, dict_lengths["L_thorax_supr"], 0]))
850
+ elif j == "left_shoulder_Z":
851
+ joint_placements.append(
852
+ np.round(
853
+ height
854
+ * np.array([SJ_thorax_X_r, SJ_thorax_Y_r, -SJ_thorax_Z_r]),
855
+ 3,
856
+ )
857
+ )
858
+ elif j == "left_elbow_Z":
859
+ joint_placements.append(np.array([0, -dict_lengths["L_upperarm"], 0]))
860
+ elif j == "left_wrist_Z":
861
+ joint_placements.append(np.array([0, -dict_lengths["L_lowerarm"], 0]))
862
+ elif j == "right_shoulder_Z":
863
+ joint_placements.append(
864
+ np.round(
865
+ height
866
+ * np.array([SJ_thorax_X_r, SJ_thorax_Y_r, SJ_thorax_Z_r]),
867
+ 3,
868
+ )
869
+ )
870
+ elif j == "right_elbow_Z":
871
+ joint_placements.append(np.array([0, -dict_lengths["L_upperarm"], 0]))
872
+ elif j == "right_wrist_Z":
873
+ joint_placements.append(np.array([0, -dict_lengths["L_lowerarm"], 0]))
874
+ elif j == "left_hip_Z":
875
+ joint_placements.append(
876
+ np.round(
877
+ height
878
+ * np.array([HJ_pelvis_X_r, HJ_pelvis_Y_r, -HJ_pelvis_Z_r]),
879
+ 3,
880
+ )
881
+ )
882
+ elif j == "left_knee_Z":
883
+ joint_placements.append(np.array([0, -dict_lengths["L_upperleg"], 0]))
884
+ elif j == "left_ankle_Z":
885
+ joint_placements.append(np.array([0, -dict_lengths["L_lowerleg"], 0]))
886
+ elif j == "right_hip_Z":
887
+ joint_placements.append(
888
+ np.round(
889
+ height
890
+ * np.array([HJ_pelvis_X_r, HJ_pelvis_Y_r, HJ_pelvis_Z_r]),
891
+ 3,
892
+ )
893
+ )
894
+ elif j == "right_knee_Z":
895
+ joint_placements.append(np.array([0, -dict_lengths["L_upperleg"], 0]))
896
+ elif j == "right_ankle_Z":
897
+ joint_placements.append(np.array([0, -dict_lengths["L_lowerleg"], 0]))
898
+
899
+ return dict(zip(joint_names, joint_placements))
900
+
901
+ @staticmethod
902
+ def get_dict_meshes_scale(
903
+ height: float, sgt_lengths: dict, gender: str = "m"
904
+ ) -> dict:
905
+ meshes_names = [
906
+ "middle_pelvis_0",
907
+ "left_upperleg_0",
908
+ "left_lowerleg_0",
909
+ "left_lowerleg_1",
910
+ "left_foot_0",
911
+ "middle_abdomen_0",
912
+ "middle_thorax_0",
913
+ "middle_head_0",
914
+ "middle_head_1",
915
+ "left_upperarm_0",
916
+ "left_upperarm_1",
917
+ "left_lowerarm_0",
918
+ "left_lowerarm_1",
919
+ "left_hand_0",
920
+ "right_upperarm_0",
921
+ "right_upperarm_1",
922
+ "right_lowerarm_0",
923
+ "right_lowerarm_1",
924
+ "right_hand_0",
925
+ "right_upperleg_0",
926
+ "right_lowerleg_0",
927
+ "right_lowerleg_1",
928
+ "right_foot_0",
929
+ ]
930
+
931
+ scales = []
932
+
933
+ for name in meshes_names:
934
+ if (
935
+ name == "middle_pelvis_0"
936
+ ): # pelvis mesh size is : X = 37.050m, Y = 32.551m, Z = 39.252m
937
+ scale = np.round(
938
+ np.array(
939
+ [
940
+ 1.09 * sgt_lengths["middle_pelvis_0_width"] / 39.252,
941
+ sgt_lengths["middle_pelvis_0_width"] / 39.252,
942
+ sgt_lengths["middle_pelvis_0_width"] / 39.252,
943
+ ]
944
+ ),
945
+ 4,
946
+ )
947
+ scales.append(scale)
948
+ elif (
949
+ name == "left_upperleg_0"
950
+ ): # upperleg mesh size is : X = 20.977m, Y = 68.524m, Z = 20.977m
951
+ scale = np.round(
952
+ np.array(
953
+ [
954
+ 0.96 * sgt_lengths["left_upperleg_0"] / 68.524,
955
+ sgt_lengths["left_upperleg_0"] / 68.524,
956
+ 1.08 * sgt_lengths["left_upperleg_0"] / 68.524,
957
+ ]
958
+ ),
959
+ 4,
960
+ )
961
+ scales.append(scale)
962
+ elif (
963
+ name == "left_lowerleg_0"
964
+ ): # lowerleg mesh size is : X = 17.986m, Y = 67m, Z = 17.977m
965
+ scale = np.round(
966
+ np.array(
967
+ [
968
+ 0.96 * sgt_lengths["left_lowerleg_0"] / 67,
969
+ sgt_lengths["left_lowerleg_0"] / 67,
970
+ 1.07 * sgt_lengths["left_lowerleg_0"] / 67,
971
+ ]
972
+ ),
973
+ 4,
974
+ )
975
+ scales.append(scale)
976
+ elif name == "left_lowerleg_1": # knee mesh is a sphere of 12.5m
977
+ scale = np.round(
978
+ np.array(
979
+ [
980
+ 0.7
981
+ * (0.053 * height if gender == "f" else 0.056 * height)
982
+ / 12.5,
983
+ 0.7
984
+ * (0.053 * height if gender == "f" else 0.056 * height)
985
+ / 12.5,
986
+ 0.7
987
+ * (0.053 * height if gender == "f" else 0.056 * height)
988
+ / 12.5,
989
+ ]
990
+ ),
991
+ 4,
992
+ )
993
+ scales.append(scale)
994
+ elif (
995
+ name == "left_foot_0"
996
+ ): # foot mesh size is : X = 40.5m, Y = 16.5m, Z = 17.999m
997
+ scale = np.round(
998
+ np.array(
999
+ [
1000
+ sgt_lengths["left_foot_0"] / 40.5,
1001
+ sgt_lengths["left_foot_0"] / 40.5,
1002
+ sgt_lengths["left_foot_0"] / 40.5,
1003
+ ]
1004
+ ),
1005
+ 4,
1006
+ )
1007
+ scales.append(scale)
1008
+ elif name == "middle_abdomen_0": # abdomen meshis a sphere of radius 30 m
1009
+ scale = np.round(
1010
+ np.array(
1011
+ [
1012
+ sgt_lengths["middle_abdomen_0"] / 30,
1013
+ sgt_lengths["middle_abdomen_0"] / 30,
1014
+ sgt_lengths["middle_abdomen_0"] / 30,
1015
+ ]
1016
+ ),
1017
+ 4,
1018
+ )
1019
+ scales.append(scale)
1020
+ elif (
1021
+ name == "middle_thorax_0"
1022
+ ): # torso mesh size is : X = 36.991m, Y = 35m, Z = 40.029m
1023
+ scale = np.round(
1024
+ np.array(
1025
+ [
1026
+ sgt_lengths["middle_thorax_0"] / 35,
1027
+ sgt_lengths["middle_thorax_0"] / 35,
1028
+ sgt_lengths["middle_thorax_0"] / 35,
1029
+ ]
1030
+ ),
1031
+ 4,
1032
+ )
1033
+ scales.append(scale)
1034
+ elif (
1035
+ name == "middle_head_0"
1036
+ ): # head mesh size is : X = 32.049m, Y = 42m, Z = 32.049m
1037
+ scale = np.round(
1038
+ np.array(
1039
+ [
1040
+ sgt_lengths["middle_head_0"] / 42,
1041
+ sgt_lengths["middle_head_0"] / 42,
1042
+ sgt_lengths["middle_head_0"] / 42,
1043
+ ]
1044
+ ),
1045
+ 4,
1046
+ )
1047
+ scales.append(scale)
1048
+ elif name == "middle_head_1": # neck mesh
1049
+ scale = np.round(
1050
+ np.array(
1051
+ [
1052
+ (0.078 * height if gender == "f" else 0.086 * height)
1053
+ / 31.0,
1054
+ (0.078 * height if gender == "f" else 0.086 * height)
1055
+ / 31.0,
1056
+ (0.078 * height if gender == "f" else 0.086 * height)
1057
+ / 31.0,
1058
+ ]
1059
+ ),
1060
+ 4,
1061
+ )
1062
+ scales.append(scale)
1063
+ elif name == "left_upperarm_0": # shoulder mesh is a shpere of radius 17.5
1064
+ scale = np.round(
1065
+ np.array(
1066
+ [
1067
+ (0.037 * height if gender == "f" else 0.038 * height)
1068
+ / 17.5,
1069
+ (0.037 * height if gender == "f" else 0.038 * height)
1070
+ / 17.5,
1071
+ (0.037 * height if gender == "f" else 0.038 * height)
1072
+ / 17.5,
1073
+ ]
1074
+ ),
1075
+ 4,
1076
+ )
1077
+ scales.append(scale)
1078
+ elif (
1079
+ name == "left_upperarm_1"
1080
+ ): # upperarm mesh is of size : X = 17.158m, Y = 46m, Z = 17.183m
1081
+ scale = np.round(
1082
+ np.array(
1083
+ [
1084
+ 1.05 * sgt_lengths["left_upperarm_0"] / 46,
1085
+ sgt_lengths["left_upperarm_0"] / 46,
1086
+ 1.16 * sgt_lengths["left_upperarm_0"] / 46,
1087
+ ]
1088
+ ),
1089
+ 4,
1090
+ )
1091
+ scales.append(scale)
1092
+ elif (
1093
+ name == "left_lowerarm_0"
1094
+ ): # lowerarm mesh is of size : X = 15.547m, Y = 45m, Z = 15.541m
1095
+ scale = np.round(
1096
+ np.array(
1097
+ [
1098
+ sgt_lengths["left_lowerarm_0"] / 45,
1099
+ sgt_lengths["left_lowerarm_0"] / 45,
1100
+ sgt_lengths["left_lowerarm_0"] / 45,
1101
+ ]
1102
+ ),
1103
+ 4,
1104
+ )
1105
+ scales.append(scale)
1106
+ elif name == "left_lowerarm_1": # elbow mesh is a sphere of radius 12.5
1107
+ scale = np.round(
1108
+ np.array(
1109
+ [
1110
+ (0.037 * height if gender == "f" else 0.038 * height)
1111
+ / 12.5,
1112
+ (0.037 * height if gender == "f" else 0.038 * height)
1113
+ / 12.5,
1114
+ (0.037 * height if gender == "f" else 0.038 * height)
1115
+ / 12.5,
1116
+ ]
1117
+ ),
1118
+ 4,
1119
+ )
1120
+ scales.append(scale)
1121
+ elif (
1122
+ name == "left_hand_0"
1123
+ ): # hand mesh is of size : X = 12.055m, Y = 30m, Z = 16.646m
1124
+ scale = np.round(
1125
+ np.array(
1126
+ [
1127
+ sgt_lengths["left_hand_0"] / 30,
1128
+ sgt_lengths["left_hand_0"] / 30,
1129
+ sgt_lengths["left_hand_0"] / 30,
1130
+ ]
1131
+ ),
1132
+ 4,
1133
+ )
1134
+ scales.append(scale)
1135
+ elif name == "right_upperarm_0": # shoulder mesh
1136
+ scale = np.round(
1137
+ np.array(
1138
+ [
1139
+ (0.037 * height if gender == "f" else 0.038 * height)
1140
+ / 17.5,
1141
+ (0.037 * height if gender == "f" else 0.038 * height)
1142
+ / 17.5,
1143
+ (0.037 * height if gender == "f" else 0.038 * height)
1144
+ / 17.5,
1145
+ ]
1146
+ ),
1147
+ 4,
1148
+ )
1149
+ scales.append(scale)
1150
+ elif name == "right_upperarm_1": # upperarm mesh
1151
+ scale = np.round(
1152
+ np.array(
1153
+ [
1154
+ 1.05 * sgt_lengths["right_upperarm_0"] / 46,
1155
+ sgt_lengths["right_upperarm_0"] / 46,
1156
+ 1.16 * sgt_lengths["right_upperarm_0"] / 46,
1157
+ ]
1158
+ ),
1159
+ 4,
1160
+ )
1161
+ scales.append(scale)
1162
+ elif name == "right_lowerarm_0": # lowerarm mesh
1163
+ scale = np.round(
1164
+ np.array(
1165
+ [
1166
+ sgt_lengths["right_lowerarm_0"] / 45,
1167
+ sgt_lengths["right_lowerarm_0"] / 45,
1168
+ sgt_lengths["right_lowerarm_0"] / 45,
1169
+ ]
1170
+ ),
1171
+ 4,
1172
+ )
1173
+ scales.append(scale)
1174
+ elif name == "right_lowerarm_1": # elbow mesh
1175
+ scale = np.round(
1176
+ np.array(
1177
+ [
1178
+ (0.037 * height if gender == "f" else 0.038 * height)
1179
+ / 12.5,
1180
+ (0.037 * height if gender == "f" else 0.038 * height)
1181
+ / 12.5,
1182
+ (0.037 * height if gender == "f" else 0.038 * height)
1183
+ / 12.5,
1184
+ ]
1185
+ ),
1186
+ 4,
1187
+ )
1188
+ scales.append(scale)
1189
+ elif name == "right_hand_0": # hand mesh
1190
+ scale = np.round(
1191
+ np.array(
1192
+ [
1193
+ sgt_lengths["right_hand_0"] / 30,
1194
+ sgt_lengths["right_hand_0"] / 30,
1195
+ sgt_lengths["right_hand_0"] / 30,
1196
+ ]
1197
+ ),
1198
+ 4,
1199
+ )
1200
+ scales.append(scale)
1201
+ elif name == "right_upperleg_0": # upperleg mesh
1202
+ scale = np.round(
1203
+ np.array(
1204
+ [
1205
+ 0.96 * sgt_lengths["right_upperleg_0"] / 68.524,
1206
+ sgt_lengths["right_upperleg_0"] / 68.524,
1207
+ 1.08 * sgt_lengths["right_upperleg_0"] / 68.524,
1208
+ ]
1209
+ ),
1210
+ 4,
1211
+ )
1212
+ scales.append(scale)
1213
+ elif name == "right_lowerleg_0": # lowerleg mesh
1214
+ scale = np.round(
1215
+ np.array(
1216
+ [
1217
+ 0.96 * sgt_lengths["right_lowerleg_0"] / 67,
1218
+ sgt_lengths["right_lowerleg_0"] / 67,
1219
+ 1.07 * sgt_lengths["right_lowerleg_0"] / 67,
1220
+ ]
1221
+ ),
1222
+ 4,
1223
+ )
1224
+ scales.append(scale)
1225
+ elif name == "right_lowerleg_1": # knee mesh
1226
+ scale = np.round(
1227
+ np.array(
1228
+ [
1229
+ 0.7
1230
+ * (0.053 * height if gender == "f" else 0.056 * height)
1231
+ / 12.5,
1232
+ 0.7
1233
+ * (0.053 * height if gender == "f" else 0.056 * height)
1234
+ / 12.5,
1235
+ 0.7
1236
+ * (0.053 * height if gender == "f" else 0.056 * height)
1237
+ / 12.5,
1238
+ ]
1239
+ ),
1240
+ 4,
1241
+ )
1242
+ scales.append(scale)
1243
+ elif name == "right_foot_0": # foot mesh
1244
+ scale = np.round(
1245
+ np.array(
1246
+ [
1247
+ sgt_lengths["right_foot_0"] / 40.5,
1248
+ sgt_lengths["right_foot_0"] / 40.5,
1249
+ sgt_lengths["right_foot_0"] / 40.5,
1250
+ ]
1251
+ ),
1252
+ 4,
1253
+ )
1254
+ scales.append(scale)
1255
+
1256
+ return dict(zip(meshes_names, scales))
1257
+
1258
+ def _scale_pin_model(
1259
+ self,
1260
+ model: pin.Model,
1261
+ visual_model: pin.GeometryModel,
1262
+ height: float,
1263
+ weight: float,
1264
+ gender: str = "m",
1265
+ ) -> None:
1266
+ """
1267
+ Overwrite model.inertias, model.jointPlacements, and model.geometryObjects.meshScale
1268
+ based on anthropometry.
1269
+ """
1270
+ # compute parameters
1271
+ joints = self.get_dict_joint_placements(height, gender)
1272
+ inert, sgt_lengths = self.get_dict_inertial_param(height, weight, gender)
1273
+ scales = self.get_dict_meshes_scale(height, sgt_lengths, gender)
1274
+
1275
+ # apply inertias
1276
+ for seg, P in inert.items():
1277
+ b_id = model.frames[model.getFrameId(seg)].parentJoint
1278
+
1279
+ Ixx, Ixy, Ixz, Iyy, Iyz, Izz = P["inertia"]
1280
+ I_mat = np.array([[Ixx, Ixy, Ixz], [Ixy, Iyy, Iyz], [Ixz, Iyz, Izz]])
1281
+ model.inertias[b_id] = pin.Inertia(P["mass"], P["com"], I_mat)
1282
+
1283
+ # apply joint placements
1284
+ for j, pos in joints.items():
1285
+ j_id = model.getJointId(j)
1286
+ M = model.jointPlacements[j_id]
1287
+ M.translation = pos
1288
+ model.jointPlacements[j_id] = M
1289
+
1290
+ # scale visuals
1291
+ assert len(visual_model.geometryObjects.tolist()) == len(scales)
1292
+ for geom_obj in visual_model.geometryObjects:
1293
+ geom_obj.meshScale = scales[geom_obj.name]