agx-openplx 0.15.0__cp310-cp310-manylinux_2_35_x86_64.whl → 0.15.1__cp310-cp310-manylinux_2_35_x86_64.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.1
2
+ Name: agx-openplx
3
+ Version: 0.15.1
4
+ Summary: AGX-OpenPLX python interface development
5
+ Home-page: https://pub.algoryx.dev/openplx/
6
+ License: Apache 2.0
7
+ Author: Algoryx
8
+ Author-email: algoryx@algoryx.com
9
+ Requires-Python: >=3.8
10
+ Classifier: Intended Audience :: Manufacturing
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: Other/Proprietary License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Physics
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Requires-Dist: agx (==2.38.0.2)
22
+ Requires-Dist: openplx-bundles (>=0.15.1,<0.16.0)
23
+ Requires-Dist: pclick (>=0.4.1,<0.5.0)
24
+ Description-Content-Type: text/markdown
25
+
26
+ # AGX OpenPLX
27
+
28
+ The agx-openplx package implements all OpenPLX bundles such as Physics, Robotics, DriveTrain and Simulation using [AGX Dynamics Real-time multi-body simulation](https://www.algoryx.se/agx-dynamics/).
29
+ The package contains python bindings and native libraries needed to load and run .openplx files.
30
+
31
+ See [OpenPLX documentation](https://pub.algoryx.dev/openplx/) for more info on OpenPLX.
32
+
33
+ ## Prerequisites
34
+
35
+ - Python 3.9 on Windows or OSX
36
+ - Python 3.8 on Ubuntu 20.04
37
+ - Python 3.10 on Ubuntu 22.04
38
+ - [AGX Dynamics](https://www.algoryx.se/agx-dynamics/) 2.38.0.2 and an AGX Dynamics License
39
+
40
+ At [OpenPLX documentation](https://pub.algoryx.dev/openplx/getopenplx/) you can find out which older version of OpenPLX matches which version of AGX.
41
+
42
+ ## Install
43
+
44
+ To get the version corresponding to your AGX on Windows do:
45
+
46
+ ```bash
47
+ setup_env.bat
48
+ pip install %AGX_DATA_DIR%/agx-pypi
49
+ pip install -U agx-openplx
50
+ ```
51
+
52
+ To get the version corresponding to your AGX on OSX and Linux do:
53
+
54
+ ```bash
55
+ source setup_env.sh
56
+ pip3 install $AGX_DATA_DIR/agx-pypi
57
+ pip3 install -U agx-openplx
58
+ ```
59
+
60
+ ## License
61
+
62
+ [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)
63
+
@@ -10,7 +10,7 @@ openplx/Terrain.py,sha256=9CxizkDVf9UtPIyBumenXzfbxAL6yn2G_d0_ZWbv-JY,184884
10
10
  openplx/Urdf.py,sha256=JOryVyaFIROiyJ9V9OMrZAS3MoUwVmJW_2TTPSsRlH4,23780
11
11
  openplx/Vehicles.py,sha256=QmtdIawHlYcIRtJH4LotXSQNs06rCpxPl6e5gklUvWg,450074
12
12
  openplx/Visuals.py,sha256=lYBFzzPZvMeWaKzXQgzikmO-fuDshcgf5uCJJQ-WKTM,187052
13
- openplx/_AgxOpenPlxPyApi.cpython-310-x86_64-linux-gnu.so,sha256=Q8PCSxrbn9YMn5R1rx_cXnC80rw3CPYNMCqRDfcndy4,17444504
13
+ openplx/_AgxOpenPlxPyApi.cpython-310-x86_64-linux-gnu.so,sha256=9pESeP2gmqOm0ZrWjVL6YQ1SUA7fjf2DnOZhqDuQngc,17444504
14
14
  openplx/_CorePythonSwig.cpython-310-x86_64-linux-gnu.so,sha256=Rv9sYuuAF3oyiTWQvvQQnMNsgJE4fJ_zLfqktt4odUY,4630832
15
15
  openplx/_DriveTrainSwig.cpython-310-x86_64-linux-gnu.so,sha256=eGOu78QAIZjFu0jFxAaSg_HocYxGuLlNTyUbMo0uMWE,9052712
16
16
  openplx/_MathSwig.cpython-310-x86_64-linux-gnu.so,sha256=bqCkwcLnyYZ18QYJv-O6IQOp9WQHbS_wrj_vNFrcB_Q,2895048
@@ -23,19 +23,19 @@ openplx/_TerrainSwig.cpython-310-x86_64-linux-gnu.so,sha256=ikpWGdpw0thVjFL4mAD6
23
23
  openplx/_UrdfSwig.cpython-310-x86_64-linux-gnu.so,sha256=Y9q24B_WbFBu503c5gMEOtQRKsYA8JeJakdNBXVZB_4,5446792
24
24
  openplx/_VehiclesSwig.cpython-310-x86_64-linux-gnu.so,sha256=buAusxRWGBD19Z0RHJsdNDL_yoT3OU78AuBDHPXJVmE,9039936
25
25
  openplx/_VisualsSwig.cpython-310-x86_64-linux-gnu.so,sha256=i55K4RsvD9MbJcJgpY_uMjHWcH9ZxvrbNrDQvw9iGWQ,3097304
26
- openplx/__init__.py,sha256=84JW4DGXG1xhGs6hL701s4_zXS_ksXBfgboChdrwBoE,2050
26
+ openplx/__init__.py,sha256=q3hkfn5aNgyXksGZjKomJ2t1pPGNkIiHWSPBEbMadVI,2050
27
27
  openplx/agxtoopenplx.py,sha256=gxQgZ_Pgda_5scCCBBhyu8D-jjL1yxlE7bZLtxigKCs,2422
28
28
  openplx/anytoopenplx.py,sha256=qAZVnelFOVAQIpCQt3y9LlfCBHQcNmxUWXFbCZuNQUc,2274
29
29
  openplx/api.py,sha256=05ECQBudlYfrMh1OgAYbq8VDsj_UlZKlPxx31RGnES0,46915
30
- openplx/migrate.py,sha256=Lm3dyiAH-1NUV9xjtCCKOj1GldYkw5DggJM8GO_p4us,5602
30
+ openplx/migrate.py,sha256=bPnhmYv22k52bQf_H34IvW89Ep3ekziiEhJPPZOJemQ,6431
31
31
  openplx/migration_hint.py,sha256=2Xs2OB_HikmiynDAk4xHKhmU5N4pqOnPFPpeIWhK37Y,603
32
- openplx/migrations.py,sha256=-j9it2oNK0A4Z-PCPyXRE0UYdU4kMpbVEVoEIjGxkFA,15094
33
- openplx/openplx_application.py,sha256=VDSsIGCDay8__omhsdKkSRaqmvY_vH6yLHQr3fTzyzA,6346
32
+ openplx/migrations.py,sha256=bvz7ZTUwQ-_84UEPf20TmGaZuZTtWFO93R3Slv1leGQ,15591
33
+ openplx/openplx_application.py,sha256=ZJFq2RgfW01IhE4o0qoEMPuwYIc6W6gYqnowcJWTdSA,6401
34
34
  openplx/openplx_serialize.py,sha256=Uul2-6_BwyJzg26JdgAz2MjR37mQJ6kWKjPbmE-sJzk,1456
35
35
  openplx/openplx_validate.py,sha256=pqBg9WBh7OUi63dOmmCpDWt_jTn8OhCSmtgL-nmb1XY,2055
36
- openplx/openplx_view.py,sha256=nUca0BVnTTcCzGoj8q2f3QVmZFpKd3PPptjuKqoHA18,350
36
+ openplx/openplx_view.py,sha256=GCFQSl4W-dPRWL0qLQmfJ69OEwgTMKL05J62YyKSb64,351
37
37
  openplx/versionaction.py,sha256=Obj_q69dn5GnFMZs2as87ZScPMLaLAduPppmXUzLB6I,283
38
- agx_openplx-0.15.0.dist-info/METADATA,sha256=jfj2qYgTFOln0NfOb8u1GhFRNu6UvU-XO-bQtL3_IjU,7875
39
- agx_openplx-0.15.0.dist-info/WHEEL,sha256=9akmB9Yw5qmIU-MKN95Pv0Mip6kpcZ0b8K__D5G3wjE,110
40
- agx_openplx-0.15.0.dist-info/entry_points.txt,sha256=IyHBVBNuH_4ZhfWnB1ZVXYtpKG_QT7dwGIUMLWdyhrc,263
41
- agx_openplx-0.15.0.dist-info/RECORD,,
38
+ agx_openplx-0.15.1.dist-info/METADATA,sha256=dvmCfwyGWb7uK73YK3cGxhw-4__IGcZAB1_fqTicaFY,2128
39
+ agx_openplx-0.15.1.dist-info/WHEEL,sha256=9akmB9Yw5qmIU-MKN95Pv0Mip6kpcZ0b8K__D5G3wjE,110
40
+ agx_openplx-0.15.1.dist-info/entry_points.txt,sha256=IyHBVBNuH_4ZhfWnB1ZVXYtpKG_QT7dwGIUMLWdyhrc,263
41
+ agx_openplx-0.15.1.dist-info/RECORD,,
openplx/__init__.py CHANGED
@@ -5,7 +5,7 @@ __init__ module for OpenPLX package
5
5
  import os
6
6
  import sys
7
7
  __AGXVERSION__ = "2.38.0.2"
8
- __version__ = "0.15.0"
8
+ __version__ = "0.15.1"
9
9
 
10
10
  # pylint doesn't like bare excepts, but we need to catch all exceptions here
11
11
  # pylint: disable=W0702 # bare-except
openplx/migrate.py CHANGED
@@ -15,6 +15,8 @@ from openplx import __version__, get_error_strings
15
15
  from openplx.Core import OpenPlxContext, parseFromFile, analyze, StringVector, DocumentVector
16
16
  from openplx.migrations import collect_migrations, ReplaceOp
17
17
  from openplx.versionaction import VersionAction
18
+ from openplx import register_plugins
19
+ import agx
18
20
 
19
21
  def download_package_version(package_name, version):
20
22
  """Download a specific version of a package from PyPI."""
@@ -60,18 +62,27 @@ def has_errors(openplx_context):
60
62
  return True
61
63
  return False
62
64
 
63
- def refactor_openplx_file(openplxfile, bundle_path_vec, from_version, to_version) -> bool:
65
+ def refactor_openplx_file(openplxfile, bundle_path_vec, from_version, to_version) -> bool: # pylint: disable=too-many-locals
64
66
  print(f"Migrating {openplxfile} from {from_version} to {to_version}")
65
- openplx_context = OpenPlxContext(bundle_path_vec)
66
67
  migrations = collect_migrations(from_version, to_version)
68
+ print(f"Found {len(migrations)} migrations ", [m.__name__ for m in migrations])
69
+
70
+ file_rename_migrations = []
71
+ for migration in [m for m in migrations if m.__name__ == "rename_from_brick_to_openplx"]:
72
+ file_rename_migrations.append(migration(openplxfile))
73
+ if (openplxfile.endswith("config.openplx") or openplxfile.endswith("config.brick")):
74
+ for m in file_rename_migrations:
75
+ m.apply_to(None, None)
76
+ return True
67
77
 
78
+ migrations = [m for m in migrations if m.__name__ != "rename_from_brick_to_openplx"]
79
+ openplx_context = OpenPlxContext(bundle_path_vec)
80
+ register_plugins(openplx_context, None)
68
81
  documents = parse_and_analyze(openplxfile, openplx_context)
69
82
 
70
83
  if has_errors(openplx_context):
71
84
  return False
72
85
 
73
- print(f"Found {len(migrations)} migrations ", [m.__name__ for m in migrations])
74
-
75
86
  order_group = [(key, list(group)) for key, group in itertools.groupby(migrations, lambda m: m.openplx_order)]
76
87
  order_group.sort(key=lambda pair: pair[0])
77
88
 
@@ -84,9 +95,14 @@ def refactor_openplx_file(openplxfile, bundle_path_vec, from_version, to_version
84
95
  if Path(openplxfile).samefile(key):
85
96
  with open(key, 'r', encoding="utf8") as file:
86
97
  lines = file.readlines()
87
- lines = ReplaceOp.apply_many(op_group, lines)
98
+ replace_ops = [op for op in op_group if isinstance(op, ReplaceOp)]
99
+ lines = ReplaceOp.apply_many(replace_ops, lines)
88
100
  with open(key, 'w', encoding="utf8") as file:
89
101
  file.writelines(lines)
102
+
103
+ for m in file_rename_migrations:
104
+ m.apply_to(None, None)
105
+
90
106
  return True
91
107
 
92
108
  def run_openplx_migrate(args):
@@ -118,7 +134,7 @@ def run_openplx_migrate(args):
118
134
  if os.path.isdir(args.openplxfile):
119
135
  for root, _, files in os.walk(args.openplxfile):
120
136
  for file in files:
121
- if file.endswith(".openplx") and not file.endswith("config.openplx"):
137
+ if file.endswith(".openplx") or file.endswith(".brick"):
122
138
  openplxfile = os.path.join(root, file)
123
139
  if not refactor_openplx_file(openplxfile, bundle_path_vec, args.from_version, args.to_version):
124
140
  success = False
@@ -131,6 +147,10 @@ def run_openplx_migrate(args):
131
147
  print(f"Refactor from {args.from_version} to {args.to_version} failed due to errors!")
132
148
  print("Note, some files might have been partially migrated.")
133
149
 
134
- if __name__ == '__main__':
150
+ def run():
135
151
  arguments, _ = parse_args()
152
+ init = agx.AutoInit() # pylint: disable=W0612 # Unused variable 'init'
136
153
  run_openplx_migrate(arguments)
154
+
155
+ if __name__ == '__main__':
156
+ run()
openplx/migrations.py CHANGED
@@ -450,6 +450,11 @@ def migrations_for_0_13_1(documents):
450
450
 
451
451
  return [ReplaceOp(op) for op in ops]
452
452
 
453
+ @migration("0.14.0", "0.15.0")
454
+ def rename_from_brick_to_openplx(document_path):
455
+
456
+ return ChangeExtensionOp(Path(document_path), ".openplx")
457
+
453
458
 
454
459
  def split_version(v):
455
460
  match = re.match(r"^(\d+)\.(\d+)\.(\d+)", v)
@@ -482,6 +487,17 @@ class MigrateOp(ABC): # pylint: disable=R0903 # Too few public methods
482
487
  pass
483
488
 
484
489
 
490
+ class ChangeExtensionOp(MigrateOp):
491
+ def __init__(self, path: Path, new_extension: str):
492
+ self.path = path
493
+ self.new_extension = new_extension
494
+
495
+ def __str__(self):
496
+ return f"{{ {self.path}, {self.new_extension} }}"
497
+
498
+ def apply_to(self, _lines, _offset):
499
+ self.path.rename(self.path.with_suffix(self.new_extension))
500
+
485
501
  class ReplaceOp(MigrateOp):
486
502
  def __init__(self, op: RefactorReplaceOp):
487
503
  self.path = Path(op.source_id)
@@ -21,6 +21,7 @@ def dummy_build_scene():
21
21
 
22
22
  class AgxHelpAction(Action):
23
23
  def __call__(self, parser, namespace, values, option_string=None):
24
+ sys.argv.append("--usage")
24
25
  OpenPlxApplication(dummy_build_scene).start_agx()
25
26
  sys.exit(0)
26
27
 
@@ -38,7 +39,7 @@ class OpenPlxApplication:
38
39
  self.build_scene_file = sys.modules[self.build_scene.__module__].__file__ if build_scene_file is None else build_scene_file
39
40
 
40
41
  @staticmethod
41
- def parse_args(openplxfile = None):
42
+ def setup_args_parser(openplxfile = None):
42
43
  parser = ArgumentParser(description="View OpenPLX models", formatter_class=ArgumentDefaultsHelpFormatter)
43
44
  if openplxfile is None:
44
45
  parser.add_argument("openplxfile", help="the .openplx file to load")
@@ -58,13 +59,13 @@ class OpenPlxApplication:
58
59
  default="info")
59
60
  parser.add_argument("--modelname", help="The model to load (defaults to last model in file)", metavar="<name>", default=None)
60
61
  parser.add_argument("--reload-on-update", help="Reload scene automatically when source is updated", action="store_true", default=SUPPRESS)
61
- parser.add_argument("--usage", help="Show AGX specific help", action=AgxHelpAction, nargs=0, default=SUPPRESS)
62
+ parser.add_argument("--agxhelp", help="Show AGX specific help", action=AgxHelpAction, nargs=0, default=SUPPRESS)
62
63
  parser.add_argument("--version", help="Show version", action=VersionAction, nargs=0, default=SUPPRESS)
63
- return parser.parse_known_args()
64
+ return parser
64
65
 
65
66
  @staticmethod
66
- def prepareScene(openplxfile = None): # pylint: disable=invalid-name
67
- args, extra_args = OpenPlxApplication.parse_args(openplxfile)
67
+ def prepare_scene(openplxfile = None):
68
+ args, extra_args = OpenPlxApplication.setup_args_parser(openplxfile).parse_known_args()
68
69
  set_log_level(args.loglevel)
69
70
  if extra_args:
70
71
  print(f"Passing these args to AGX: {(' ').join(extra_args)}")
@@ -123,11 +124,12 @@ class OpenPlxApplication:
123
124
  def start_agx(self):
124
125
  # Tell AGX where build_scene is located
125
126
  sys.argv[0] = self.build_scene_file
126
- # pylint: disable=W0612 # Unused variable 'init'
127
127
  # Use __main__ otherwise AGX will just skip the init
128
- init = init_app(name="__main__", scenes=[(self.build_scene, "1")], autoStepping=True, onShutdown=self.on_shutdown) # Default: False
128
+ # pylint: disable=unused-variable
129
+ init = init_app(name="__main__", scenes=[(self.build_scene, "1")], autoStepping=True, onShutdown=self.on_shutdown)
130
+ # pylint: enable=unused-variable
129
131
 
130
132
  def run(self):
131
133
  signal.signal(signal.SIGINT, self.ctrl_break_handler)
132
- self.parse_args("")
134
+ self.setup_args_parser("").parse_known_args()
133
135
  self.start_agx()
openplx/openplx_view.py CHANGED
@@ -5,7 +5,7 @@ Command line utility that loads OpenPLX files and loads them into an AGX Simulat
5
5
  from openplx.openplx_application import OpenPlxApplication
6
6
 
7
7
  def openplx_view_build_scene():
8
- OpenPlxApplication.prepareScene()
8
+ OpenPlxApplication.prepare_scene()
9
9
 
10
10
  def run():
11
11
  OpenPlxApplication(openplx_view_build_scene).run()
@@ -1,232 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: agx-openplx
3
- Version: 0.15.0
4
- Summary: AGX-OpenPLX python interface development
5
- Home-page: https://pub.algoryx.dev/openplx/
6
- License: Apache 2.0
7
- Author: Algoryx
8
- Author-email: algoryx@algoryx.com
9
- Requires-Python: >=3.8
10
- Classifier: Intended Audience :: Manufacturing
11
- Classifier: Intended Audience :: Science/Research
12
- Classifier: License :: Other/Proprietary License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Topic :: Scientific/Engineering :: Physics
20
- Classifier: Topic :: Scientific/Engineering :: Visualization
21
- Requires-Dist: agx (==2.38.0.2)
22
- Requires-Dist: openplx-bundles (>=0.15.0,<0.16.0)
23
- Requires-Dist: pclick (>=0.4.1,<0.5.0)
24
- Description-Content-Type: text/markdown
25
-
26
- # AGX OpenPLX
27
-
28
- The agx-openplx package implements all OpenPLX bundles such as Physics, Robotics, DriveTrain and Simulation using [AGX Dynamics Real-time multi-body simulation](https://www.algoryx.se/agx-dynamics/).
29
- The package contains python bindings and native libraries needed to load and run .openplx files.
30
-
31
- See [OpenPLX documentation](https://pub.algoryx.dev/openplx/) for more info on OpenPLX.
32
-
33
- ## Prerequisites
34
-
35
- - Python 3.9 on Windows or OSX
36
- - Python 3.8 on Ubuntu 20.04
37
- - Python 3.10 on Ubuntu 22.04
38
- - [AGX Dynamics](https://www.algoryx.se/agx-dynamics/) 2.38.0.2 and an AGX Dynamics License
39
-
40
- At [OpenPLX documentation](https://pub.algoryx.dev/openplx/getopenplx/) you can find out which older version of OpenPLX matches which version of AGX.
41
-
42
- ## Install
43
-
44
- To get the version corresponding to your AGX on Windows do:
45
-
46
- ```bash
47
- setup_env.bat
48
- pip install %AGX_DATA_DIR%/agx-pypi
49
- pip install -U agx-openplx
50
- ```
51
-
52
- To get the version corresponding to your AGX on OSX and Linux do:
53
-
54
- ```bash
55
- source setup_env.sh
56
- pip3 install $AGX_DATA_DIR/agx-pypi
57
- pip3 install -U agx-openplx
58
- ```
59
-
60
- ## License
61
-
62
- [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0)
63
-
64
- ## Usage Examples
65
-
66
- Given the python file and openplx file below, run with:
67
-
68
- ```bash
69
- python3 inverted_pendulum.py
70
- ```
71
-
72
- To just simulate the openplx file without controllers, do:
73
-
74
- ```bash
75
- openplx_view inverted_pendulum.openplx
76
- ```
77
-
78
- or `python3 -m openplx.openplx_view inverted_pendulum.openplx`
79
-
80
- Store contents below in a new file named `inverted_pendulum.openplx`:
81
-
82
- ```openplx
83
- Rod is Physics3D.Bodies.RigidBody:
84
- inertia.mass: 10
85
- geometry is Physics3D.Charges.Box:
86
- size: Math.Vec3.from_xyz(0.1, 0.1, 1)
87
- arrow is Physics3D.Charges.Box:
88
- local_transform:
89
- position.z: 0.5
90
- rotation: Math.Quat.angleAxis(Math.PI / 4, Math.Vec3.Y_AXIS())
91
- size: Math.Vec3.from_xyz(0.071, 0.1, 0.071)
92
- mate_connector is Physics3D.Charges.MateConnector:
93
- position.z: -geometry.size.z * 0.7
94
- main_axis: Math.Vec3.Y_AXIS()
95
- normal: Math.Vec3.Z_AXIS()
96
-
97
- Cart is Physics3D.Bodies.RigidBody:
98
- inertia.mass: 10
99
- geometry is Physics3D.Charges.Box:
100
- size: Math.Vec3.from_xyz(0.1, 0.1, 0.1)
101
- connector is Physics3D.Charges.MateConnector:
102
- main_axis: Math.Vec3.X_AXIS()
103
- normal: Math.Vec3.Z_AXIS()
104
- rotated_connector is Physics3D.Charges.MateConnector:
105
- main_axis: Math.Vec3.Y_AXIS()
106
- normal: Math.Vec3.Z_AXIS()
107
-
108
- PendulumScene is Physics3D.System:
109
- world_connector is Physics3D.Charges.MateConnector:
110
- main_axis: Math.Vec3.X_AXIS()
111
- normal: Math.Vec3.Z_AXIS()
112
-
113
- cart is Cart
114
- rod is Rod
115
-
116
- prismatic is Physics3D.Interactions.Prismatic:
117
- charges: [world_connector, cart.connector]
118
-
119
- cart_motor is Physics3D.Interactions.LinearVelocityMotor:
120
- desired_speed: 0
121
- charges: prismatic.charges
122
-
123
- hinge is Physics3D.Interactions.Hinge:
124
- initial_angle: 0
125
- charges: [cart.rotated_connector, rod.mate_connector]
126
-
127
- motor_input is Physics3D.Signals.LinearVelocityMotorVelocityInput:
128
- motor: cart_motor
129
-
130
- hinge_angle_output is Physics3D.Signals.HingeAngleOutput:
131
- hinge: hinge
132
- hinge_angular_velocity_output is Physics3D.Signals.HingeAngularVelocityOutput:
133
- hinge: hinge
134
-
135
- cart_position_output is Physics3D.Signals.RigidBodyPositionOutput:
136
- rigid_body: cart
137
- cart_velocity_output is Physics3D.Signals.RigidBodyVelocityOutput:
138
- rigid_body: cart
139
- ```
140
-
141
- Store contents below in a new file named `inverted_pendulum.py`:
142
-
143
- ```python
144
- import os
145
- import signal
146
- import agxOSG
147
- import agxSDK
148
- from openplxbundles import bundle_path
149
-
150
- # Import useful utilities to access the current simulation, graphics root and application
151
- from agxPythonModules.utils.environment import init_app, simulation, root
152
-
153
- from openplx import Math, Physics, Physics3D, Signals
154
- from openplx import InputSignalListener, OutputSignalListener, load_from_file
155
-
156
- def file_dir():
157
- return os.path.dirname(os.path.abspath(__file__))
158
-
159
- def pendulum():
160
- return f"{file_dir()}/inverted_pendulum.openplx"
161
-
162
- class PDController:
163
- def __init__(self, kp, kd, goal):
164
- self.kp = kp
165
- self.kd = kd
166
- self.goal = goal
167
-
168
- def observe(self, x, xdot):
169
- error = self.goal - x
170
- return self.kp * error - self.kd * xdot
171
-
172
-
173
- class CartController(agxSDK.StepEventListener):
174
- motor_input: Physics3D.Signals_LinearVelocityMotorVelocityInput
175
- cart: PDController
176
- pole: PDController
177
-
178
- def __init__(self, motor_input: Physics3D.Signals_LinearVelocityMotorVelocityInput):
179
- super().__init__()
180
- self.motor_input = motor_input
181
- self.cart = PDController(kp=10, kd=5, goal=0)
182
- self.pole = PDController(kp=20, kd=5, goal=0)
183
-
184
- def pre(self, time):
185
- if time == 0.0:
186
- hinge_angle = 0
187
- hinge_angular_velocity = 0
188
- cart_position = Math.Vec3.from_xyz(0,0,0)
189
- cart_velocity = Math.Vec3.from_xyz(0,0,0)
190
- else:
191
- signal_values = {signal.source().getName():signal.value().value() for signal in Signals.getOutputSignals()}
192
- hinge_angle = signal_values["PendulumScene.hinge_angle_output"]
193
- hinge_angular_velocity = signal_values["PendulumScene.hinge_angular_velocity_output"]
194
- cart_position = signal_values["PendulumScene.cart_position_output"]
195
- cart_velocity = signal_values["PendulumScene.cart_velocity_output"]
196
-
197
- u_cart = self.cart.observe(cart_position.x(), cart_velocity.x())
198
- u_pole = self.pole.observe(hinge_angle, hinge_angular_velocity)
199
-
200
- Signals.sendInputSignal(Physics.Signals_RealInputSignal.create(-u_cart - u_pole, self.motor_input))
201
-
202
-
203
- def buildScene():
204
-
205
- result = load_from_file(simulation(), pendulum(), bundle_path(), "")
206
- assembly = result.assembly()
207
- openplx_scene = result.scene()
208
- # Add a signal listener so that signals are picked up from inputs
209
- input_signal_listener = InputSignalListener(assembly)
210
- output_signal_listener = OutputSignalListener(assembly, openplx_scene)
211
- simulation().add(input_signal_listener, InputSignalListener.RECOMMENDED_PRIO)
212
- simulation().add(output_signal_listener, OutputSignalListener.RECOMMENDED_PRIO)
213
- simulation().add(assembly.get())
214
- agxOSG.createVisual(assembly.get(), root())
215
-
216
- motor_input: Physics3D.Signals_LinearVelocityMotorVelocityInput = openplx_scene.getDynamic("motor_input").asObject()
217
- controller = CartController(motor_input)
218
- simulation().add(controller)
219
-
220
-
221
- def handler(_, __):
222
- os._exit(0)
223
-
224
- signal.signal(signal.SIGINT, handler)
225
-
226
- init = init_app(name=__name__,
227
- scenes=[(buildScene, '1')],
228
- autoStepping=True, # Default: False
229
- onInitialized=lambda app: print('App successfully initialized.'),
230
- onShutdown=lambda app: print('App successfully shut down.'))
231
- ```
232
-