opensim-model-creator 0.1.0__tar.gz

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,13 @@
1
+ Metadata-Version: 2.2
2
+ Name: opensim_model_creator
3
+ Version: 0.1.0
4
+ Summary: Python code for OpenSim model creation using an articulated statistical shape model.
5
+ Author-email: Josh Plummer <jplu752@aucklanduni.ac.nz>, Timothy Salemink <tim.nicolas@outlook.com>
6
+ Project-URL: Repository, https://github.com/Josh10203/opensim_model_creator
7
+ Requires-Dist: numpy
8
+ Requires-Dist: scipy
9
+ Requires-Dist: pandas
10
+ Requires-Dist: trimesh
11
+ Requires-Dist: pyvista
12
+ Requires-Dist: plotly
13
+ Requires-Dist: articulated-ssm
@@ -0,0 +1,37 @@
1
+ Below is an overview of how model creation works using openism_model_creator, what general steps have been taken to create the model, and what has been done so far in terms of muscle creation.
2
+
3
+ Bone Creation:
4
+
5
+ The primary inputs are participant specific tracking data (.trc files) related to a static (static.trc) and dynamic (kneeoptimisation.trc, along with a static pickle file (static.pkl, unsure what this is FYI).
6
+
7
+ Model creation begins by utilising an articulated shape model (asm) to generate meshes that better represent the underlying bone geometry of the participant, as part of the asm's workflow it also predicts anatomical landmarks which are retained.
8
+
9
+ As the asm does not generate foot specific meshes, the gait 2392 feet meshes are initially loaded to be repurposed and fit to each participant.
10
+
11
+ The first body to be created is the pelvis, a realignment of the pelvis is performed using the ASISs. This rotates the pelvis so that the vector between the ASISs is aligned with the horizontal axis (i.e side to side/ left to right etc), this was done as participants may be standing slightly rotated during their static trial, where the generated meshes (by asm) retain this slight rotation. This ensures that the pelvis rotation variable tracked within Opensim is consistent between participants where the "0" position is the pelvis facing perfectly forward. No reorientations were applied to posterior/ anterior tilt, or pelvic obliquity. Markers from the static trial and anatomical landmarks are then added to the pelvis body/mesh.
12
+
13
+ Secondly the Femur bodies are created, and the markers/anatomical landmarks are attached to the meshes. As participants have pathologies that make it difficult to stand in a commonly neutral pose, the femurs (and other limbs) have the option to be reorientated to a more neutral pose, this was done to ensure accurate representation of joint angles. Reorientation of the femurs currently places the femoral epicondyles in a horizontal position (as above with the ASISs for the pelvis), as well as positioning the epicondylar midpoint vertically beneath the hip joint centre. The hip joint orientation was defined to as best match ISB coordinate standards with the flexion extension axis aligned with the ASISs and the rotational axis aligned with a vector passing form the hip joint centre to the epicondylar midpoint, with the remaining axis (adduction/abduction) being the cross product between the 2 aforementioned axes. As joint axes need to be orthogonal to one another, the flexion/extension axis was chosen as the primary axis and perfectly fits to that of its anatomical definition (ASISs), whilst the rotational axis (hip joint centre to epicondylar midpoint) was numerically optimised to be as close as possible whilst keeping the flexion/extension axis in place, the remaining vector as stated was the remaining orthogonal axis.
14
+
15
+
16
+ Thirdly the tibfib bodies were created, along with attaching the markers/anatomical landmarks to the meshes. As above, reorientation from the participants initial pose is possible. The tibfib's initial flexion/extension position was adjusted to position the medial malleoli beneath the medial epicondyle (essentially having the femur and tibfib be vertically aligned for the '0' degrees of flexion/extension for both the hip and knee joints). The knee joint (1 dof, pin joint) went through 2 stages, initially the flexion/extension axis was defined as the vector between the epicondyles, before being numerically optimised through using a walking trial and perturbing the orientation of this joint axis to see if any reductions in marker RMS error are observed.
17
+
18
+ Fourthly, the initial feet bodies from gait 2392 are attached to the model and repositioned to have the talus sitting in the centre of the malleoli, the flexion extension vector for the ankle is also defined using the vector between the malleoli. The subtalar joint angle is maintained as is present in the gait 2392 model, this update is hard coded within the next section on general model updates. From here the feet bodies are reorientated to take into account patient specific foot positioning (as gait 2392 has the feet aligned perfectly in a forward/backward direction, but for participants this is not always the case). This rotates the feet bodies about the y and z axis to align the vector between the toe and heel model markers with that of the toe and heel static trial marker data. In addition the feet are rotated to a position that is considered to be flat with the ground when the joint angles are at their '0' positions.
19
+
20
+ An additional process that occurs after the feet are finalised (but are un-scaled), is a general overall model update where the joint coordinate names and ranges are updated, body segment parameters (mass and inertias) are computed based off the height and weight of the participant(using information and equations provided by Winter, 2009, as mentioned above the subtalar joint is updated, also we appropriately set the mesh paths of the feet to the correct location within the higher_level_inputs folder.
21
+
22
+
23
+ Following this process, the feet are then scaled by their experimental static trial data (other limbs are not, as this is taken into account by the asm).
24
+
25
+ Knee joint optimisation, described above, occurs next.
26
+
27
+ Following this, we moved the model markers to reduce RMS error, this is done via determining the mean error vector between the model markers and the experimental data and then applying that offset to reduce RMS error.
28
+
29
+ Validation has not yet been conducted on models produced using this tool.
30
+
31
+ Muscle Creation:
32
+
33
+
34
+ Muscle creation is an in-the-works optional capability of opensim_model_creator, it currently has the capacity to generate muscles between the pelvis, femur, tibia and fibula with appropriate insertion and origin location, it has the capacity to break a muscle (such as the glut med) into multiple "fibers" using principal component analysis to distribute the fibers across the overall muscle attachment surface. The muscles exist with generic muscle parameters and without any form of muscle wrapping. The sacrum and spine currently do not exist within the model at all and as such muscles originating from these do not exist (glut max exists, just doesn't have the ability to have origins on the sacrum as it is not present within the model). Origins and insertions for muscles of the feet currently are not present.
35
+
36
+
37
+ To add a new participant, create a new folder with the participants name, create a folder called "Inputs", in there place the required files (listed previously) - this is for running test cases, implementation with Tim & Laura's tools follows a different method of use.
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [options]
6
+ package_dir = {'' = 'src'}
7
+
8
+ [project]
9
+ name = "opensim_model_creator"
10
+ description = "Python code for OpenSim model creation using an articulated statistical shape model."
11
+ authors = [{name = "Josh Plummer", email = "jplu752@aucklanduni.ac.nz" },
12
+ {name = "Timothy Salemink", email = "tim.nicolas@outlook.com" }]
13
+ version = "0.1.0"
14
+ dynamic = ["dependencies"]
15
+
16
+ [tool.setuptools.dynamic]
17
+ dependencies = {file = ["requirements.txt"]}
18
+
19
+ [project.urls]
20
+ Repository = "https://github.com/Josh10203/opensim_model_creator"
@@ -0,0 +1,7 @@
1
+ numpy
2
+ scipy
3
+ pandas
4
+ trimesh
5
+ pyvista
6
+ plotly
7
+ articulated-ssm
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,415 @@
1
+
2
+ import os
3
+ import numpy as np
4
+ import opensim as osim
5
+
6
+ from articulated_ssm_both_sides.MainASM import run_asm
7
+
8
+ #%%Import functions from folders
9
+ from opensim_model_creator.Functions.general_utils import *
10
+ from opensim_model_creator.Functions.bone_utils import *
11
+ from opensim_model_creator.Functions.muscle_utils import *
12
+ from opensim_model_creator.Functions.file_utils import reset_folder
13
+
14
+
15
+ root_directory = os.path.dirname(os.path.abspath(__file__))
16
+ high_level_inputs = os.path.join(root_directory, "High_Level_Inputs")
17
+
18
+
19
+ def create_model(participant_folder, static_marker_data, weight, height, create_muscles=False, testing=False):
20
+ """
21
+ Creates an OpenSim model for a given participant, optionally adding muscles.
22
+
23
+ Args:
24
+ participant_folder (str): Path to the participant's folder.
25
+ static_marker_data (dict): Static marker data coordinates.
26
+ create_muscles (bool): Whether to add muscles to the model.
27
+ testing (bool): If True, runs in test mode - reduces knee optimisation iteration count for computational speed
28
+ weight (float, optional): Participant's weight in kg.
29
+ height (float, optional): Participant's height in meters.
30
+
31
+ Returns:
32
+ None
33
+ """
34
+
35
+ #%% Setup of folders
36
+ # Define paths for inputs and outputs
37
+ participant_inputs = os.path.join(participant_folder, "Inputs")
38
+ output_folder = os.path.join(participant_folder, "Models")
39
+ meshes = os.path.join(output_folder, "Meshes")
40
+
41
+ # Clear output and mesh folders to avoid residuals from previous runs
42
+ reset_folder(output_folder)
43
+ reset_folder(meshes)
44
+
45
+ #%%Initialisation
46
+
47
+ # Generate mesh files using ASM
48
+ run_asm(static_marker_data, meshes)
49
+
50
+ # Move foot mesh files into the meshes directory.
51
+ copy_mesh_files(high_level_inputs, meshes)
52
+
53
+ # Scale marker data from millimeters to meters (variable currently unused)
54
+ scale_marker_data(static_marker_data, 0.001)
55
+
56
+ # Process and extract meshes from STL files
57
+ process_participant_meshes(meshes, meshes)
58
+
59
+ # Initializes muscle linkage directory
60
+ muscle_linkages = muscle_initialisation(meshes)
61
+
62
+ #Splits specific muscles into a number of segments
63
+ segment_muscle_origins_insertions(muscle_linkages, "Glut med", num_segments=3)
64
+ segment_muscle_origins_insertions(muscle_linkages, "Glut min", num_segments=3)
65
+ segment_muscle_origins_insertions(muscle_linkages, "Add mag", pair_to_segment=0, num_segments=2)
66
+
67
+ # Apply a swap for Adductor Magnus origins to have better anatomical consistency
68
+ swap_muscle_attachments(muscle_linkages, "Add mag", 0, 2, attachment_type="ori")
69
+
70
+ #Initialises model, trc files, and landmarks
71
+ empty_model, state, left_landmarks, right_landmarks, mocap_static_trc, mocap_trc_file = initialize_model_and_extract_landmarks(meshes)
72
+
73
+ # %% Creation of the pelvis body and pelvis joint
74
+ pelvis, pelvis_joint, rotated_pelvis_center, pelvis_realignment, pelvis_center = create_pelvis_body_and_joint(
75
+ empty_model, left_landmarks, right_landmarks, meshes, mocap_static_trc, realign_pelvis=True
76
+ )
77
+
78
+ # %% Creation of femur bodies and attachment of meshes, markers, and landmarks
79
+ (l_LEC, l_MEC, l_HJC, l_EC_midpoint, left_femur, femur_l_center, rotated_l_femur_center,
80
+ LKNE_alignment_angles, LHIP_vert_alignment_angles, r_LEC, r_MEC, r_HJC, r_EC_midpoint, right_femur, femur_r_center, rotated_r_femur_center,
81
+ RKNE_alignment_angles, RHIP_vert_alignment_angles) = create_femur_bodies_and_hip_joints(empty_model, left_landmarks, right_landmarks, meshes, mocap_static_trc, rotated_pelvis_center, pelvis_realignment, pelvis, realign_femurs= True)
82
+
83
+ # %% Creation of the Tibia/Fibula (TibFib) Bodies
84
+ rotated_l_tibfib_center, rotated_r_tibfib_center, tibfib_l_center, tibfib_r_center, left_tibfib, right_tibfib = create_tibfib_bodies_and_knee_joints(
85
+ empty_model, left_landmarks, right_landmarks, meshes, mocap_static_trc,
86
+ rotated_l_femur_center, rotated_r_femur_center, LHIP_vert_alignment_angles, RHIP_vert_alignment_angles,
87
+ left_femur, right_femur,l_LEC, l_MEC, l_HJC, l_EC_midpoint, r_LEC, r_MEC, r_HJC, r_EC_midpoint, realign_tibias=True
88
+ )
89
+
90
+ #%% Create feet bodies
91
+ repurpose_feet_bodies_and_create_joints(empty_model, left_landmarks, right_landmarks, rotated_l_tibfib_center, rotated_r_tibfib_center, l_EC_midpoint, r_EC_midpoint, left_tibfib, right_tibfib)
92
+
93
+ #Further augment the muscle linkages dictionary and model to contain markers represenitng origins and insertions for all muscles (must be done prior to scaling as unused markers are removed via scaling process)
94
+ empty_model, muscle_linkages = add_all_muscle_attachment_markers(empty_model,muscle_linkages,{
95
+ "Pelvis": pelvis_center,
96
+ "Femur": [femur_l_center,femur_r_center],
97
+ "Tibfib": [tibfib_l_center,tibfib_r_center],
98
+ })
99
+
100
+ # Finalise the connections of the model
101
+ empty_model.finalizeConnections()
102
+
103
+ # Extract the directory name as the model name and replace spaces with underscores
104
+ model_name = os.path.basename(participant_folder).replace(" ", "_")
105
+
106
+ # Update the model name
107
+ empty_model.setName(model_name)
108
+
109
+ # Ensure the output folder exists
110
+ os.makedirs(output_folder, exist_ok=True)
111
+
112
+ # Combine the folder path and filename
113
+ output_path = os.path.join(output_folder, f"{model_name}.osim")
114
+
115
+ # Save the model to output folder
116
+ empty_model.printToXML(output_path)
117
+ print(f"Model saved to: {output_path}")
118
+
119
+ #%% Perform a long series of updates to the model
120
+ output_file = perform_updates(empty_model, output_folder, meshes, model_name, weight, height)
121
+
122
+ # Reload the model
123
+ empty_model = osim.Model(output_file)
124
+
125
+ #%% Reinitialise the model for further feet adjustments (aligning with static trc as gait2392 feet are perfectly straight whilst participants may have their feet angled when neutral)
126
+ feet_adjustments(output_file, empty_model, mocap_static_trc, realign_feet= True)
127
+
128
+ # Finalise the non-scaled foot
129
+ empty_model.finalizeConnections()
130
+ empty_model.printToXML(output_file)
131
+
132
+ #Extract local muscle positions prior to scaling (unused markers, such as those of the muscles, are removed during the scaling process)
133
+ local_muscle_positions = extract_local_muscle_positions(empty_model)
134
+
135
+
136
+
137
+ #%% Look to scale the size of the feet automatically and move the markers to appropriate positions
138
+ perform_scaling(participant_folder, output_file, mocap_trc_file)
139
+
140
+
141
+ #%% Create variables required by knee joint optimisation
142
+ source_file_path1 = os.path.join(participant_folder, "Models", "scaled_foot.osim") # Source path
143
+ knee_optimisation_trc_file = search_files_by_keywords(participant_inputs, "optimisation")[0] # Find the TRC file containing marker data
144
+ ignore,(start_time, end_time),knee_optimisation_marker_dictionary = read_trc_file_as_dict(knee_optimisation_trc_file,True)
145
+
146
+
147
+ #%%Adjusting & Optimising the Knee Joint Orientations
148
+
149
+ # Default temporary model paths
150
+ temp_model_path_1 = output_folder+ "/temp1.osim"
151
+ temp_model_path_2 = output_folder+ "/temp2.osim"
152
+ optimised_knee_model = output_folder+ "/Optimised_Knee_Axes.osim"
153
+
154
+ #marker weights used in the IK process
155
+ marker_weights = {
156
+ "RASI": 5, "LASI": 5, "RTHI": 1, "RTIB": 1,
157
+ "RANK": 10, "LTHI": 1, "LTIB": 1, "LANK": 10,
158
+ "RPSI": 1, "LPSI": 1, "RHEE": 1, "LHEE": 1,
159
+ "RTOE": 1, "LTOE": 1, "RKNE": 2.5, "LKNE": 2.5
160
+ }
161
+
162
+ #itersation count of 5 appears to allow convergence whilst not taking overtly long
163
+ if testing:
164
+ iteration_count = 1
165
+ else:
166
+ iteration_count = 5
167
+
168
+
169
+ #This runs the knee joint optimisation
170
+ run_knee_joint_optimisation(source_file_path1, knee_optimisation_trc_file, start_time, end_time, temp_model_path_1, temp_model_path_2,marker_weights,optimised_knee_model, iteration_count= iteration_count)
171
+
172
+
173
+
174
+
175
+ #%% Run IK, and extract the model marker positions and compare to those of the actual marker positions across the entire time trial, then adjust.
176
+
177
+ optimised_knee_moved_marker_model = output_folder+"/Optimised_Knee_Axes_Moved_Markers.osim"
178
+
179
+ compute_and_adjust_markers(optimised_knee_model,"ik_output.mot","_ik_model_marker_locations.sto",knee_optimisation_marker_dictionary,optimised_knee_moved_marker_model)
180
+
181
+
182
+ # Run the Inverse Kinematics (IK) analysis and print results for the 3 different models
183
+ print("\n")
184
+ print(f"Prior to Knee Alignment & Marker Movement - name of file: {os.path.basename(source_file_path1)}")
185
+ ik_result_1 = perform_IK(source_file_path1, knee_optimisation_trc_file, start_time, end_time, marker_weights)
186
+ print(ik_result_1)
187
+ print("\n")
188
+
189
+ print(
190
+ f"Following Knee Alignment but Prior to Marker Adjustment - name of file: {os.path.basename(optimised_knee_model)}")
191
+ ik_result_2 = perform_IK(optimised_knee_model, knee_optimisation_trc_file, start_time, end_time, marker_weights)
192
+ print(ik_result_2)
193
+ print("\n")
194
+
195
+ print(
196
+ f"Following Both Knee Alignment & Marker Adjustment - name of file: {os.path.basename(optimised_knee_moved_marker_model)}")
197
+ ik_result_3 = perform_IK(optimised_knee_moved_marker_model, knee_optimisation_trc_file, start_time, end_time,
198
+ marker_weights)
199
+ print(ik_result_3)
200
+ print("\n")
201
+
202
+ # Extract Average RMS Errors from the results
203
+ models = {
204
+ source_file_path1: ik_result_1["Average RMS Error"],
205
+ optimised_knee_model: ik_result_2["Average RMS Error"],
206
+ optimised_knee_moved_marker_model: ik_result_3["Average RMS Error"]
207
+ }
208
+
209
+ # Find the model with the lowest Average RMS Error
210
+ best_model_path = min(models, key=models.get)
211
+ best_model_error = models[best_model_path]
212
+
213
+ # Format the participant's name by replacing spaces with underscores
214
+ participant_name = os.path.basename(participant_folder).replace(" ", "_")
215
+
216
+ # Define the output file name
217
+ final_model_filename = f"Final_Bone_Model_{participant_name}.osim"
218
+ final_model_path = os.path.join(os.path.dirname(best_model_path), final_model_filename)
219
+
220
+ # Check if the final model file already exists, and remove it if it does
221
+ if os.path.exists(final_model_path):
222
+ os.remove(final_model_path) # Delete the existing file
223
+
224
+ # Now rename (move) the best model to the final filename
225
+ os.rename(best_model_path, final_model_path)
226
+
227
+ print(f"Final model selected: {os.path.basename(best_model_path)} with an Average RMS Error of {best_model_error}")
228
+ print(f"This model was renamed to: {os.path.basename(final_model_path)}")
229
+
230
+
231
+
232
+ #creation of muscles is optional, work in progress (contains no wrapping or participant specific muscle parameters)
233
+ if create_muscles:
234
+
235
+
236
+ #Load the model
237
+ model = osim.Model(final_model_path)
238
+
239
+ #Adds muscles to the model
240
+ add_all_muscles_to_model_with_simple_names(model, local_muscle_positions,muscle_linkages)
241
+
242
+ #Saves the model
243
+ muscle_model_name = os.path.basename(participant_folder).replace(" ", "_")
244
+ muscle_model = output_folder+"/Muscle_" +muscle_model_name+ ".osim"
245
+ model.setName("Muscle_"+muscle_model_name)
246
+ model.finalizeConnections()
247
+ model.printToXML(muscle_model)
248
+
249
+ # Remove temporary .osim files.
250
+ for osim_file in [temp_model_path_1, temp_model_path_2, optimised_knee_model, source_file_path1]:
251
+ if os.path.isfile(osim_file):
252
+ os.remove(osim_file)
253
+
254
+
255
+ #END##############################################################################################################
256
+
257
+ #begin attempt at adding wrapping objects to muscles
258
+
259
+ #get the marker set of the model and find some markers
260
+ #compute the midpoint between the LASI and LPSI markers using the midpoint_3d function
261
+
262
+
263
+ '''
264
+
265
+ marker_model = osim.Model(empty_model)
266
+ state = marker_model.initSystem()
267
+ marker_set = marker_model.getMarkerSet()
268
+
269
+
270
+
271
+ #%% Setting translations for glute max 1 wrapping objects (and determining acceptable radii of cylinders)
272
+
273
+
274
+ #%% Pelvis Wrap Objects
275
+ # Get the reference frame (Pelvis)
276
+ pelvis_frame = empty_model.getBodySet().get("pelvis_b")
277
+ # Ratio = SIS_x_distance / radii
278
+ desired_glut_radii_ratio = 3.15
279
+
280
+ # Left side
281
+ l_obt_ext_marker = marker_set.get("ins_l_iliacus")
282
+ l_obt_ext_global = l_obt_ext_marker.getLocationInGround(state) # Get the marker's position in global coordinates
283
+
284
+ l_glut_wrap_position_pelvis = compute_marker_midpoint(marker_model, "ori_l_rect_fem_1", "ori_l_gem_1")
285
+ l_glut_wrap_global = pelvis_frame.findStationLocationInAnotherFrame(state, osim.Vec3(l_glut_wrap_position_pelvis), empty_model.getGround()) # Convert to OpenSim Vec3
286
+
287
+ # Set the desired global forward-backward (anterior-posterior) position
288
+ l_glut_wrap_global[0] = l_obt_ext_global.get(0) # Modify the x position in the global frame
289
+
290
+ # Convert the updated position back to the pelvis's local frame
291
+ l_glut_wrap_local = empty_model.getGround().findStationLocationInAnotherFrame(state, l_glut_wrap_global,pelvis_frame)
292
+
293
+ # Update the wrap object position
294
+ l_glut_wrap_position_pelvis = np.array([l_glut_wrap_local.get(i) for i in range(3)])
295
+
296
+
297
+ # Right side
298
+ r_obt_ext_marker = marker_set.get("ins_r_iliacus")
299
+ r_obt_ext_global = r_obt_ext_marker.getLocationInGround(state) # Get the marker's position in global coordinates
300
+
301
+ r_glut_wrap_position_pelvis = compute_marker_midpoint(marker_model, "ori_r_rect_fem_1", "ori_r_gem_1")
302
+ r_glut_wrap_global = pelvis_frame.findStationLocationInAnotherFrame(state, osim.Vec3(r_glut_wrap_position_pelvis), empty_model.getGround()) # Convert to OpenSim Vec3
303
+
304
+ # Set the desired global forward-backward (anterior-posterior) position
305
+ r_glut_wrap_global[0] = r_obt_ext_global.get(0) # Modify the x position in the global frame
306
+
307
+ # Convert the modified position back to the pelvis's local frame
308
+ r_glut_wrap_local = empty_model.getGround().findStationLocationInAnotherFrame(state, r_glut_wrap_global,pelvis_frame)
309
+
310
+ # Update the wrap object position
311
+ r_glut_wrap_position_pelvis = np.array([r_glut_wrap_local.get(i) for i in range(3)])
312
+
313
+
314
+ #radius
315
+ # Get global positions of the markers
316
+ l_asis_global = marker_set.get("lms_LASI").getLocationInGround(state)
317
+ l_psis_global = marker_set.get("lms_LPSI").getLocationInGround(state)
318
+
319
+ # Compute the global X-distance
320
+ SIS_x_dist = l_asis_global.get(0) - l_psis_global.get(0)
321
+
322
+ # Compute the radius
323
+ radius_1 = SIS_x_dist / desired_glut_radii_ratio
324
+
325
+
326
+
327
+
328
+ #%% Femur Wrap Objects
329
+
330
+ l_femur_frame = empty_model.getBodySet().get("femur_l_b")
331
+
332
+
333
+ # Left side
334
+ l_obt_ext_marker = marker_set.get("ins_l_iliacus")
335
+ l_obt_ext_global = l_obt_ext_marker.getLocationInGround(state) # Get the marker's position in global coordinates
336
+
337
+ l_glut_wrap_position_femur = compute_marker_midpoint(marker_model, "ins_l_glut_med", "ins_l_obt_ext")
338
+ l_glut_wrap_global = l_femur_frame.findStationLocationInAnotherFrame(state, osim.Vec3(l_glut_wrap_position_femur), empty_model.getGround()) # Convert to OpenSim Vec3
339
+
340
+ # Set the desired global forward-backward (anterior-posterior) position
341
+ l_glut_wrap_global[2] = l_obt_ext_global.get(2) # Modify the x position in the global frame
342
+
343
+ # Convert the updated position back to the pelvis's local frame
344
+ l_glut_wrap_local = empty_model.getGround().findStationLocationInAnotherFrame(state, l_glut_wrap_global,l_femur_frame)
345
+
346
+ # Update the wrap object position
347
+ l_glut_wrap_position_femur = np.array([l_glut_wrap_local.get(i) for i in range(3)])
348
+
349
+
350
+ r_femur_frame = empty_model.getBodySet().get("femur_r_b")
351
+
352
+ # Right side
353
+ r_obt_ext_marker = marker_set.get("ins_r_iliacus")
354
+ r_obt_ext_global = r_obt_ext_marker.getLocationInGround(state) # Get the marker's position in global coordinates
355
+
356
+ r_glut_wrap_position_femur = compute_marker_midpoint(marker_model, "ins_r_glut_med", "ins_r_obt_ext")
357
+ r_glut_wrap_global = r_femur_frame.findStationLocationInAnotherFrame(state, osim.Vec3(r_glut_wrap_position_femur), empty_model.getGround()) # Convert to OpenSim Vec3
358
+
359
+ # Set the desired global forward-backward (anterior-posterior) position
360
+ r_glut_wrap_global[2] = r_obt_ext_global.get(2) # Modify the z position in the global frame
361
+
362
+ # Convert the modified position back to the pelvis's local frame
363
+ r_glut_wrap_local = empty_model.getGround().findStationLocationInAnotherFrame(state, r_glut_wrap_global,r_femur_frame)
364
+
365
+ # Update the wrap object position
366
+ r_glut_wrap_position_femur = np.array([r_glut_wrap_local.get(i) for i in range(3)])
367
+
368
+
369
+
370
+
371
+
372
+ wrapping_objects = {
373
+ "l_glut_max_1": [ # Muscle name (key), list of wrapping objects (values)
374
+ { # Wrapping object 1 (Pelvis)
375
+ "name": "l_glut_max_1_1_pelvis_wrap", # Unique name
376
+ "body": "pelvis_b",
377
+ "type": "cylinder",
378
+ "translation": tuple(l_glut_wrap_position_pelvis),
379
+ "rotation": (0.75, -0.390000, 0),
380
+ "radius": radius_1,
381
+ "length": 0.1,
382
+ "quadrant": "-x"
383
+ },
384
+ { # Wrapping object 2 (Femur)
385
+ "name": "l_glut_max_1_1_femur_wrap", # Unique name
386
+ "body": "femur_l_b",
387
+ "type": "cylinder",
388
+ "translation": tuple(l_glut_wrap_position_femur),
389
+ "rotation": (-0.143263, -0.123715, 0.421776),
390
+ "radius": radius_1*0.45,
391
+ "length": 0.1,
392
+ "quadrant": "-x"
393
+ }
394
+
395
+ ],
396
+ "r_glut_max_1": [ # Muscle name (key), list of wrapping objects (values)
397
+ { # Wrapping object 1 (Pelvis)
398
+ "name": "r_glut_max_1_1_pelvis_wrap", # Unique name
399
+ "body": "pelvis_b",
400
+ "type": "cylinder",
401
+ "translation": tuple(r_glut_wrap_position_pelvis),
402
+ "rotation": (-0.750000, 0.390000, 0),
403
+ "radius": radius_1,
404
+ "length": 0.1,
405
+ "quadrant": "-x"
406
+ },
407
+ ]
408
+ }
409
+
410
+
411
+
412
+ #model = add_wrapping_objects_to_model(model, wrapping_objects)
413
+ #model.finalizeConnections()
414
+ #model.printToXML(muscle_model)
415
+ '''