agx-openplx 0.15.0__cp39-cp39-macosx_12_0_arm64.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.
Files changed (41) hide show
  1. agx_openplx-0.15.0.dist-info/METADATA +231 -0
  2. agx_openplx-0.15.0.dist-info/RECORD +41 -0
  3. agx_openplx-0.15.0.dist-info/WHEEL +4 -0
  4. agx_openplx-0.15.0.dist-info/entry_points.txt +8 -0
  5. openplx/Core.py +7781 -0
  6. openplx/DriveTrain.py +8574 -0
  7. openplx/Math.py +5372 -0
  8. openplx/Physics.py +36195 -0
  9. openplx/Physics1D.py +6732 -0
  10. openplx/Physics3D.py +42524 -0
  11. openplx/Robotics.py +15762 -0
  12. openplx/Simulation.py +1056 -0
  13. openplx/Terrain.py +3891 -0
  14. openplx/Urdf.py +654 -0
  15. openplx/Vehicles.py +8793 -0
  16. openplx/Visuals.py +3901 -0
  17. openplx/_AgxOpenPlxPyApi.cpython-39-darwin.so +0 -0
  18. openplx/_CorePythonSwig.cpython-39-darwin.so +0 -0
  19. openplx/_DriveTrainSwig.cpython-39-darwin.so +0 -0
  20. openplx/_MathSwig.cpython-39-darwin.so +0 -0
  21. openplx/_Physics1DSwig.cpython-39-darwin.so +0 -0
  22. openplx/_Physics3DSwig.cpython-39-darwin.so +0 -0
  23. openplx/_PhysicsSwig.cpython-39-darwin.so +0 -0
  24. openplx/_RoboticsSwig.cpython-39-darwin.so +0 -0
  25. openplx/_SimulationSwig.cpython-39-darwin.so +0 -0
  26. openplx/_TerrainSwig.cpython-39-darwin.so +0 -0
  27. openplx/_UrdfSwig.cpython-39-darwin.so +0 -0
  28. openplx/_VehiclesSwig.cpython-39-darwin.so +0 -0
  29. openplx/_VisualsSwig.cpython-39-darwin.so +0 -0
  30. openplx/__init__.py +51 -0
  31. openplx/agxtoopenplx.py +55 -0
  32. openplx/anytoopenplx.py +44 -0
  33. openplx/api.py +1337 -0
  34. openplx/migrate.py +136 -0
  35. openplx/migration_hint.py +14 -0
  36. openplx/migrations.py +521 -0
  37. openplx/openplx_application.py +133 -0
  38. openplx/openplx_serialize.py +35 -0
  39. openplx/openplx_validate.py +57 -0
  40. openplx/openplx_view.py +14 -0
  41. openplx/versionaction.py +11 -0
openplx/migrate.py ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Command line utility that helps migrating OpenPLX files to a newer version
4
+ """
5
+ from pathlib import Path
6
+ import itertools
7
+ import os
8
+ import tempfile
9
+ import json
10
+ import urllib.request
11
+ import zipfile
12
+ from io import BytesIO
13
+ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, SUPPRESS
14
+ from openplx import __version__, get_error_strings
15
+ from openplx.Core import OpenPlxContext, parseFromFile, analyze, StringVector, DocumentVector
16
+ from openplx.migrations import collect_migrations, ReplaceOp
17
+ from openplx.versionaction import VersionAction
18
+
19
+ def download_package_version(package_name, version):
20
+ """Download a specific version of a package from PyPI."""
21
+ url = f"https://pypi.org/pypi/{package_name}/{version}/json"
22
+ with urllib.request.urlopen(url, timeout=16) as response:
23
+ content = response.read().decode('utf-8')
24
+ data = json.loads(content)
25
+ return data['urls'][0]['url']
26
+
27
+ def unzip_package(url, extract_to):
28
+ """Download and unzip a package."""
29
+ with urllib.request.urlopen(url, timeout=32) as response:
30
+ file_data = BytesIO(response.read())
31
+ with zipfile.ZipFile(file_data) as zip_file:
32
+ zip_file.extractall(extract_to)
33
+
34
+ def parse_args():
35
+ parser = ArgumentParser(description="Migrates a .openplx file from an older to a newer version", formatter_class=ArgumentDefaultsHelpFormatter)
36
+ parser.add_argument("openplxfile", metavar="path", help="the .openplx file or directory to migrate")
37
+ parser.add_argument("--version", help="Show version", action=VersionAction, nargs=0, default=SUPPRESS)
38
+ parser.add_argument("--from-version", help="Version to convert from", required=True)
39
+ parser.add_argument("--to-version", help="Version to convert to", default=__version__)
40
+ return parser.parse_known_args()
41
+
42
+ def parse_and_analyze(openplxfile, openplx_context):
43
+ parse_result = parseFromFile(str(Path(openplxfile).absolute()), openplx_context)
44
+
45
+ documents = DocumentVector()
46
+
47
+ if parse_result[0] is None:
48
+ return documents
49
+
50
+ analyze(openplx_context, None)
51
+
52
+ documents.push_back(parse_result[0])
53
+ return documents
54
+
55
+ def has_errors(openplx_context):
56
+ if openplx_context.hasErrors():
57
+ error_strings = get_error_strings(openplx_context.getErrors())
58
+ for e_s in error_strings:
59
+ print(e_s)
60
+ return True
61
+ return False
62
+
63
+ def refactor_openplx_file(openplxfile, bundle_path_vec, from_version, to_version) -> bool:
64
+ print(f"Migrating {openplxfile} from {from_version} to {to_version}")
65
+ openplx_context = OpenPlxContext(bundle_path_vec)
66
+ migrations = collect_migrations(from_version, to_version)
67
+
68
+ documents = parse_and_analyze(openplxfile, openplx_context)
69
+
70
+ if has_errors(openplx_context):
71
+ return False
72
+
73
+ print(f"Found {len(migrations)} migrations ", [m.__name__ for m in migrations])
74
+
75
+ order_group = [(key, list(group)) for key, group in itertools.groupby(migrations, lambda m: m.openplx_order)]
76
+ order_group.sort(key=lambda pair: pair[0])
77
+
78
+ for _, migration_group in order_group:
79
+ ops = []
80
+ for migration in migration_group:
81
+ ops.extend(migration(documents))
82
+
83
+ for key, op_group in itertools.groupby(ops, lambda op: op.path):
84
+ if Path(openplxfile).samefile(key):
85
+ with open(key, 'r', encoding="utf8") as file:
86
+ lines = file.readlines()
87
+ lines = ReplaceOp.apply_many(op_group, lines)
88
+ with open(key, 'w', encoding="utf8") as file:
89
+ file.writelines(lines)
90
+ return True
91
+
92
+ def run_openplx_migrate(args):
93
+
94
+ # NOTE: Depending on version, we need to change the package_name
95
+ package_name = 'brickbundles'
96
+
97
+ # Download the package
98
+ url = download_package_version(package_name, args.from_version)
99
+ if url is None:
100
+ print(f"Could not find the source distribution for {package_name}=={args.from_version}.")
101
+ return
102
+
103
+ # Create a temporary directory
104
+ with tempfile.TemporaryDirectory() as tmpdirname:
105
+ tmp_path = str(Path(os.path.realpath(tmpdirname)).absolute())
106
+ print(f"Extracting to temporary directory: {tmp_path}")
107
+ unzip_package(url, tmp_path)
108
+ print(f"Package {package_name}=={args.from_version} extracted to {tmp_path}")
109
+ bundle_path = str((Path(tmp_path) / package_name).absolute())
110
+
111
+ print(f'Using bundle path {bundle_path}')
112
+ print(os.listdir(bundle_path))
113
+
114
+ bundle_path_vec = StringVector()
115
+ bundle_path_vec.push_back(bundle_path)
116
+ success = True
117
+ # Apply the refactoring
118
+ if os.path.isdir(args.openplxfile):
119
+ for root, _, files in os.walk(args.openplxfile):
120
+ for file in files:
121
+ if file.endswith(".openplx") and not file.endswith("config.openplx"):
122
+ openplxfile = os.path.join(root, file)
123
+ if not refactor_openplx_file(openplxfile, bundle_path_vec, args.from_version, args.to_version):
124
+ success = False
125
+ else:
126
+ if not refactor_openplx_file(args.openplxfile, bundle_path_vec, args.from_version, args.to_version):
127
+ success = False
128
+ if success:
129
+ print(f"Refactor from {args.from_version} to {args.to_version} complete!")
130
+ else:
131
+ print(f"Refactor from {args.from_version} to {args.to_version} failed due to errors!")
132
+ print("Note, some files might have been partially migrated.")
133
+
134
+ if __name__ == '__main__':
135
+ arguments, _ = parse_args()
136
+ run_openplx_migrate(arguments)
@@ -0,0 +1,14 @@
1
+ """
2
+ Migration hint for use by cmdline tools
3
+ """
4
+ from openplx import check_if_migrate_hint_is_justified, __version__
5
+
6
+ class Ansi: # pylint: disable=too-few-public-methods # This is basically an enum, but we do not want to use Enum
7
+ WARNING = '\033[93m'
8
+ ENDC = '\033[0m'
9
+
10
+ def check_migration_hint(openplxfile, errors):
11
+ old_version = check_if_migrate_hint_is_justified(__version__, errors)
12
+ if old_version:
13
+ print(f"{Ansi.WARNING}Dependency errors might be due to upgrade. If so, try running: "
14
+ f"openplx_migrate --from-version {old_version} {openplxfile}{Ansi.ENDC}")
openplx/migrations.py ADDED
@@ -0,0 +1,521 @@
1
+ """
2
+ This module contains migration functions that can be used to refactor code in a backwards compatible way.
3
+ """
4
+
5
+ import functools
6
+ import sys
7
+ import re
8
+ from pathlib import Path
9
+ import itertools
10
+ from abc import ABC, abstractmethod
11
+ from openplx.Core import RefactorReplaceOp, RefactorToolkit
12
+
13
+
14
+ def migration(from_version: str, to_version: str, order: int = 0):
15
+ def decorator(func):
16
+ @functools.wraps(func)
17
+ def wrapper(*args, **kwargs):
18
+ result = func(*args, **kwargs)
19
+ return result
20
+
21
+ wrapper.openplx_from_version = from_version
22
+ wrapper.openplx_to_version = to_version
23
+ wrapper.openplx_order = order
24
+ return wrapper
25
+
26
+ return decorator
27
+
28
+
29
+ @migration("0.9.2", "0.9.3")
30
+ def snakecaseify_methods(documents):
31
+ ops = []
32
+ ops.extend(RefactorToolkit.renameMethod(documents, "Math.Vec3.fromXYZ", "from_xyz"))
33
+ ops.extend(
34
+ RefactorToolkit.renameMethod(
35
+ documents, "Math.Vec3.angleBetweenVectors", "angle_between_vectors"
36
+ )
37
+ )
38
+ ops.extend(
39
+ RefactorToolkit.renameMethod(
40
+ documents, "Math.Vec3.getOrthogonalUnitVector", "get_orthogonal_unit_vector"
41
+ )
42
+ )
43
+ ops.extend(
44
+ RefactorToolkit.renameMethod(documents, "Math.Quat.angleAxis", "angle_axis")
45
+ )
46
+ ops.extend(RefactorToolkit.renameMethod(documents, "Math.Quat.fromTo", "from_to"))
47
+ ops.extend(
48
+ RefactorToolkit.renameMethod(documents, "Math.Quat.fromXYZW", "from_xyzw")
49
+ )
50
+ ops.extend(
51
+ RefactorToolkit.renameMethod(
52
+ documents, "Math.AffineTransform.fromAxes", "from_axes"
53
+ )
54
+ )
55
+ ops.extend(
56
+ RefactorToolkit.renameMethod(
57
+ documents, "Math.AffineTransform.inverseOf", "inverse_of"
58
+ )
59
+ )
60
+ ops.extend(
61
+ RefactorToolkit.renameMethod(
62
+ documents, "Math.AffineTransform.transformVec3Point", "transform_vec3_point"
63
+ )
64
+ )
65
+ ops.extend(
66
+ RefactorToolkit.renameMethod(
67
+ documents,
68
+ "Math.AffineTransform.transformVec3Vector",
69
+ "transform_vec3_vector",
70
+ )
71
+ )
72
+ ops.extend(
73
+ RefactorToolkit.renameMethod(
74
+ documents, "Math.Matrix3x3.fromRowMajor", "from_row_major"
75
+ )
76
+ )
77
+ ops.extend(
78
+ RefactorToolkit.renameMethod(documents, "Math.Matrix3x3.fromRows", "from_rows")
79
+ )
80
+ ops.extend(
81
+ RefactorToolkit.renameMethod(
82
+ documents, "Math.Matrix3x3.fromColumns", "from_columns"
83
+ )
84
+ )
85
+ ops.extend(
86
+ RefactorToolkit.renameMethod(
87
+ documents, "Math.Matrix4x4.fromRowMajor", "from_row_major"
88
+ )
89
+ )
90
+ ops.extend(
91
+ RefactorToolkit.renameMethod(documents, "Math.Matrix4x4.fromRows", "from_rows")
92
+ )
93
+ ops.extend(
94
+ RefactorToolkit.renameMethod(
95
+ documents, "Math.Matrix4x4.fromColumns", "from_columns"
96
+ )
97
+ )
98
+ ops.extend(
99
+ RefactorToolkit.renameMethod(
100
+ documents, "Math.Matrix4x4.fromVec3Quat", "from_vec3_quat"
101
+ )
102
+ )
103
+ ops.extend(
104
+ RefactorToolkit.renameMethod(
105
+ documents, "Math.Matrix4x4.getAffineTranslation", "get_affine_translation"
106
+ )
107
+ )
108
+ ops.extend(
109
+ RefactorToolkit.renameMethod(
110
+ documents, "Math.Matrix4x4.getAffineRotation", "get_affine_rotation"
111
+ )
112
+ )
113
+ ops.extend(
114
+ RefactorToolkit.renameMethod(
115
+ documents, "Functions.harmonicMean", "harmonic_mean"
116
+ )
117
+ )
118
+ ops.extend(
119
+ RefactorToolkit.renameMethod(documents, "Math.Line.fromPoints", "from_points")
120
+ )
121
+
122
+ return [ReplaceOp(op) for op in ops]
123
+
124
+
125
+ @migration("0.9.3", "0.10.0")
126
+ def snakecaseify_methods_093(documents):
127
+ ops = []
128
+ # ValueOutputSignal
129
+ ops.extend(
130
+ RefactorToolkit.renameMethod(
131
+ documents, "Physics.Signals.ValueOutputSignal.fromAngle", "from_angle"
132
+ )
133
+ )
134
+ ops.extend(
135
+ RefactorToolkit.renameMethod(
136
+ documents,
137
+ "Physics.Signals.ValueOutputSignal.fromAngularVelocity1D",
138
+ "from_angular_velocity_1d",
139
+ )
140
+ )
141
+ ops.extend(
142
+ RefactorToolkit.renameMethod(
143
+ documents, "Physics.Signals.ValueOutputSignal.fromDistance", "from_distance"
144
+ )
145
+ )
146
+ ops.extend(
147
+ RefactorToolkit.renameMethod(
148
+ documents, "Physics.Signals.ValueOutputSignal.fromForce1D", "from_force_1d"
149
+ )
150
+ )
151
+ ops.extend(
152
+ RefactorToolkit.renameMethod(
153
+ documents,
154
+ "Physics.Signals.ValueOutputSignal.fromVelocity1D",
155
+ "from_velocity_1d",
156
+ )
157
+ )
158
+ ops.extend(
159
+ RefactorToolkit.renameMethod(
160
+ documents,
161
+ "Physics.Signals.ValueOutputSignal.fromTorque1D",
162
+ "from_torque_1d",
163
+ )
164
+ )
165
+ ops.extend(
166
+ RefactorToolkit.renameMethod(
167
+ documents,
168
+ "Physics.Signals.ValueOutputSignal.fromAcceleration3D",
169
+ "from_acceleration_3d",
170
+ )
171
+ )
172
+ ops.extend(
173
+ RefactorToolkit.renameMethod(
174
+ documents,
175
+ "Physics.Signals.ValueOutputSignal.fromAngularAcceleration3D",
176
+ "from_angular_acceleration_3d",
177
+ )
178
+ )
179
+ ops.extend(
180
+ RefactorToolkit.renameMethod(
181
+ documents,
182
+ "Physics.Signals.ValueOutputSignal.fromAngularVelocity3D",
183
+ "from_angular_velocity_3d",
184
+ )
185
+ )
186
+ ops.extend(
187
+ RefactorToolkit.renameMethod(
188
+ documents, "Physics.Signals.ValueOutputSignal.fromForce3D", "from_force_3d"
189
+ )
190
+ )
191
+ ops.extend(
192
+ RefactorToolkit.renameMethod(
193
+ documents,
194
+ "Physics.Signals.ValueOutputSignal.fromTorque3D",
195
+ "from_torque_3d",
196
+ )
197
+ )
198
+ ops.extend(
199
+ RefactorToolkit.renameMethod(
200
+ documents,
201
+ "Physics.Signals.ValueOutputSignal.fromVelocity3D",
202
+ "from_velocity_3d",
203
+ )
204
+ )
205
+ ops.extend(
206
+ RefactorToolkit.renameMethod(
207
+ documents,
208
+ "Physics.Signals.ValueOutputSignal.fromPosition3D",
209
+ "from_position_3d",
210
+ )
211
+ )
212
+ ops.extend(
213
+ RefactorToolkit.renameMethod(
214
+ documents, "Physics.Signals.ValueOutputSignal.fromRPY", "from_rpy"
215
+ )
216
+ )
217
+ ops.extend(
218
+ RefactorToolkit.renameMethod(
219
+ documents, "Physics.Signals.ValueOutputSignal.isReal", "is_real"
220
+ )
221
+ )
222
+ ops.extend(
223
+ RefactorToolkit.renameMethod(
224
+ documents, "Physics.Signals.ValueOutputSignal.asReal", "as_real"
225
+ )
226
+ )
227
+ ops.extend(
228
+ RefactorToolkit.renameMethod(
229
+ documents, "Physics.Signals.ValueOutputSignal.asVec3", "as_vec3"
230
+ )
231
+ )
232
+ ops.extend(
233
+ RefactorToolkit.renameMethod(
234
+ documents, "Physics.Signals.ValueOutputSignal.asAngle", "as_angle"
235
+ )
236
+ )
237
+ ops.extend(
238
+ RefactorToolkit.renameMethod(
239
+ documents,
240
+ "Physics.Signals.ValueOutputSignal.asAngularVelocity1D",
241
+ "as_angular_velocity_1d",
242
+ )
243
+ )
244
+ ops.extend(
245
+ RefactorToolkit.renameMethod(
246
+ documents, "Physics.Signals.ValueOutputSignal.asDistance", "as_distance"
247
+ )
248
+ )
249
+ ops.extend(
250
+ RefactorToolkit.renameMethod(
251
+ documents, "Physics.Signals.ValueOutputSignal.asForce1D", "as_force_1d"
252
+ )
253
+ )
254
+ ops.extend(
255
+ RefactorToolkit.renameMethod(
256
+ documents,
257
+ "Physics.Signals.ValueOutputSignal.asVelocity1D",
258
+ "as_velocity_1d",
259
+ )
260
+ )
261
+ ops.extend(
262
+ RefactorToolkit.renameMethod(
263
+ documents, "Physics.Signals.ValueOutputSignal.asTorque1D", "as_torque_1d"
264
+ )
265
+ )
266
+ ops.extend(
267
+ RefactorToolkit.renameMethod(
268
+ documents,
269
+ "Physics.Signals.ValueOutputSignal.asAcceleration3D",
270
+ "as_acceleration_3d",
271
+ )
272
+ )
273
+ ops.extend(
274
+ RefactorToolkit.renameMethod(
275
+ documents,
276
+ "Physics.Signals.ValueOutputSignal.asAngularAcceleration3D",
277
+ "as_angular_acceleration_3d",
278
+ )
279
+ )
280
+ ops.extend(
281
+ RefactorToolkit.renameMethod(
282
+ documents,
283
+ "Physics.Signals.ValueOutputSignal.asAngularVelocity3D",
284
+ "as_angular_velocity_3d",
285
+ )
286
+ )
287
+ ops.extend(
288
+ RefactorToolkit.renameMethod(
289
+ documents, "Physics.Signals.ValueOutputSignal.asForce3D", "as_force_3d"
290
+ )
291
+ )
292
+ ops.extend(
293
+ RefactorToolkit.renameMethod(
294
+ documents, "Physics.Signals.ValueOutputSignal.asTorque3D", "as_torque_3d"
295
+ )
296
+ )
297
+ ops.extend(
298
+ RefactorToolkit.renameMethod(
299
+ documents,
300
+ "Physics.Signals.ValueOutputSignal.asVelocity3D",
301
+ "as_velocity_3d",
302
+ )
303
+ )
304
+ ops.extend(
305
+ RefactorToolkit.renameMethod(
306
+ documents,
307
+ "Physics.Signals.ValueOutputSignal.asPosition3D",
308
+ "as_position_3d",
309
+ )
310
+ )
311
+ ops.extend(
312
+ RefactorToolkit.renameMethod(
313
+ documents, "Robotics.Signals.RobotInputSignal.fromValues", "from_values"
314
+ )
315
+ )
316
+
317
+ # DriveTrain.Signals
318
+ ops.extend(
319
+ RefactorToolkit.moveAndRenameModel(
320
+ documents,
321
+ "DriveTrain.Signals.CombustionEngineThrottleInput",
322
+ "Physics.Signals",
323
+ "FractionInput",
324
+ )
325
+ )
326
+ ops.extend(
327
+ RefactorToolkit.moveAndRenameModel(
328
+ documents,
329
+ "DriveTrain.Signals.CombustionEngineTorqueOutput",
330
+ "Physics.Signals",
331
+ "Torque1DOutput",
332
+ )
333
+ )
334
+ ops.extend(
335
+ RefactorToolkit.moveAndRenameModel(
336
+ documents,
337
+ "DriveTrain.Signals.GearTorqueOutput",
338
+ "Physics.Signals",
339
+ "Torque1DOutput",
340
+ )
341
+ )
342
+
343
+ return [ReplaceOp(op) for op in ops]
344
+
345
+
346
+ @migration("0.10.3", "0.11.0")
347
+ def migrations_for_0_10_3(documents):
348
+ ops = []
349
+ # Physics3D.Transform -> Math.AffineTransform
350
+ ops.extend(
351
+ RefactorToolkit.moveAndRenameModel(
352
+ documents, "Physics3D.Transform", "Math", "AffineTransform"
353
+ )
354
+ )
355
+ # Robotics.Joints.FlexibleVelocityJoint -> Robotics.Joints.FlexibleAngularVelocityJoint
356
+ ops.extend(
357
+ RefactorToolkit.renameModel(
358
+ documents,
359
+ "Robotics.Joints.FlexibleVelocityJoint",
360
+ "FlexibleAngularVelocityJoint",
361
+ )
362
+ )
363
+ ops.extend(
364
+ RefactorToolkit.renameModel(
365
+ documents,
366
+ "Physics3D.Signals.MateConnector.Velocity3DOutput",
367
+ "LinearVelocity3DOutput",
368
+ )
369
+ )
370
+ return [ReplaceOp(op) for op in ops]
371
+
372
+
373
+ @migration("0.11.0", "0.11.1")
374
+ def migrations_for_0_11_0(documents):
375
+ ops = []
376
+ ops.extend(
377
+ RefactorToolkit.renameModel(
378
+ documents,
379
+ "Physics3D.Signals.RigidBodyVelocityOutput",
380
+ "LinearVelocity3DOutput",
381
+ )
382
+ )
383
+ ops.extend(
384
+ RefactorToolkit.renameAttribute(
385
+ documents, "Physics3D.Signals.RigidBodyVelocityOutput.rigid_body", "source"
386
+ )
387
+ )
388
+ return [ReplaceOp(op) for op in ops]
389
+
390
+
391
+ @migration("0.12.1", "0.12.2")
392
+ def migrations_for_0_12_1(documents):
393
+ ops = []
394
+ ops.extend(
395
+ RefactorToolkit.moveAndRenameModel(
396
+ documents,
397
+ "Physics3D.Signals.PrismaticVelocityOutput",
398
+ "Physics.Signals",
399
+ "LinearVelocity1DOutput",
400
+ )
401
+ )
402
+ ops.extend(
403
+ RefactorToolkit.renameAttribute(
404
+ documents, "Physics3D.Signals.PrismaticVelocityOutput.prismatic", "source"
405
+ )
406
+ )
407
+ ops.extend(
408
+ RefactorToolkit.renameAttribute(
409
+ documents,
410
+ "DriveTrain.ManualClutch.initial_opening_fraction",
411
+ "initial_engagement_fraction",
412
+ )
413
+ )
414
+ ops.extend(
415
+ RefactorToolkit.renameAttribute(
416
+ documents,
417
+ "DriveTrain.ManualClutch.opening_fraction_input",
418
+ "engagement_fraction_input",
419
+ )
420
+ )
421
+ ops.extend(
422
+ RefactorToolkit.renameAttribute(
423
+ documents,
424
+ "DriveTrain.ManualClutch.opening_fraction_output",
425
+ "engagement_fraction_output",
426
+ )
427
+ )
428
+ ops.extend(
429
+ RefactorToolkit.renameAttribute(
430
+ documents,
431
+ "DriveTrain.AutomaticClutch.engage_time",
432
+ "engagement_time",
433
+ )
434
+ )
435
+
436
+ return [ReplaceOp(op) for op in ops]
437
+
438
+
439
+ @migration("0.13.1", "0.13.2")
440
+ def migrations_for_0_13_1(documents):
441
+ ops = []
442
+ # DriveTrain.Gear -> DriveTrain.FlexibleGear
443
+ ops.extend(
444
+ RefactorToolkit.renameModel(
445
+ documents,
446
+ "DriveTrain.Gear",
447
+ "FlexibleGear",
448
+ )
449
+ )
450
+
451
+ return [ReplaceOp(op) for op in ops]
452
+
453
+
454
+ def split_version(v):
455
+ match = re.match(r"^(\d+)\.(\d+)\.(\d+)", v)
456
+
457
+ if not match:
458
+ raise ValueError("Invalid version format")
459
+
460
+ return tuple(int(part) for part in match.groups())
461
+
462
+
463
+ def collect_migrations(from_version, to_version):
464
+ migrations = filter(
465
+ lambda obj: (
466
+ callable(obj)
467
+ and hasattr(obj, "openplx_from_version")
468
+ and hasattr(obj, "openplx_to_version")
469
+ and hasattr(obj, "openplx_order")
470
+ and split_version(from_version) <= split_version(obj.openplx_from_version)
471
+ and split_version(obj.openplx_from_version) < split_version(to_version)
472
+ ),
473
+ [obj for _, obj in globals().items()],
474
+ )
475
+
476
+ return sorted(list(migrations), key=lambda m: split_version(m.openplx_from_version))
477
+
478
+
479
+ class MigrateOp(ABC): # pylint: disable=R0903 # Too few public methods
480
+ @abstractmethod
481
+ def apply_to(self, lines, offset):
482
+ pass
483
+
484
+
485
+ class ReplaceOp(MigrateOp):
486
+ def __init__(self, op: RefactorReplaceOp):
487
+ self.path = Path(op.source_id)
488
+ self.from_line = op.from_line
489
+ self.from_column = op.from_column
490
+ self.end_line = op.end_line
491
+ self.end_column = op.end_column
492
+ self.new_content = op.new_content
493
+
494
+ def __str__(self):
495
+ return f"{{{self.path}, {self.from_line}, {self.from_column}, {self.end_line}, {self.end_column}, {self.new_content} }}"
496
+
497
+ @staticmethod
498
+ def apply_many(ops, lines):
499
+ # There can be multiple ops per line, we need to run them in order sorted by column
500
+ for _, line_group in itertools.groupby(ops, lambda op: op.from_line):
501
+ offset = 0
502
+ for op in sorted(line_group, key=lambda op: op.from_column):
503
+ lines = op.apply_to(lines, offset)
504
+ offset += len(op.new_content) - op.end_column + op.from_column
505
+ return lines
506
+
507
+ def apply_to(self, lines, offset):
508
+
509
+ if self.end_line != self.from_line:
510
+ print("Multiple line replace ops are not supported", file=sys.stderr)
511
+ return []
512
+
513
+ target_line = lines[self.from_line - 1]
514
+
515
+ lines[self.from_line - 1] = (
516
+ target_line[: (self.from_column + offset - 1)]
517
+ + self.new_content
518
+ + target_line[(self.end_column + offset - 1) :]
519
+ )
520
+
521
+ return lines