-
Notifications
You must be signed in to change notification settings - Fork 623
Selection API #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Selection API #119
Changes from all commits
Commits
Show all changes
61 commits
Select commit
Hold shift + click to select a range
3afda12
Add utility to replicate Isaac Lab environments with example
nvlukasz 6ea6962
Updated Isaac Lab cartpole example
nvlukasz b1a42f3
Draft of ArticulationView
nvlukasz d28a9ce
Add include_root_joint flag to ArticulationView constructor
nvlukasz 2db8e25
Added properties for counts and attribute shapes in ArticulationView
nvlukasz 58fab77
Use correct up-axis in replicate_environment()
nvlukasz 0c5b452
Use up_axis instead of up_vector
nvlukasz 4ea4e4d
Added missing USD file
nvlukasz 616258d
Removed redundant cloner example, renamed carpole example
nvlukasz 388e5b7
Example tweaks
nvlukasz 1841df6
Add API for articulation root transforms and velocities
nvlukasz 93a79d6
Implementation tweaks
nvlukasz 82cefa9
Code formatting
nvlukasz 0a298f2
Changed include_root_joint to include_free_joint in ArticulationView
nvlukasz 12ca4e3
Updated cartpole example to use env_offsets
nvlukasz b7c2f4c
Restore loading global data from prototype env
nvlukasz f53e8ad
Code formatting
nvlukasz b899cce
Periodic ant reset
nvlukasz a2f0ea3
Set default env_offsets to zero
nvlukasz 671a58f
Fixed a bug with computing the end of articulation joint axes
nvlukasz 17ed45a
Exposed joint limits in ArticulationView
nvlukasz 687f55e
Cleanup
nvlukasz 148b795
Demonstrate two ways of setting articulation states
nvlukasz 5eee5e7
Example tweaks
nvlukasz 5111130
Updated docstrings
nvlukasz 61432ad
Use eval_fk() consistently
nvlukasz 5f9a6a6
Updated usage of eval_fk()
nvlukasz 5064ded
Allow passing indices to setters for selective updates
nvlukasz 228e545
Fix silly things
nvlukasz 53de9cf
Code formatting
nvlukasz 2eecfb9
Update to latest
nvlukasz 6b0de17
Use DFS joint ordering
nvlukasz a5c828f
Simplify up axis code
nvlukasz 98dfe8b
Use ant prototype env with ground plane
nvlukasz f4fe573
Use ground plane from USD in ant example
nvlukasz a952a61
Support for more builtin attributes
nvlukasz e33b268
Refactored indexing mode
nvlukasz 7d8a3c3
Code formatting
nvlukasz ecbab9b
Renamed examples
nvlukasz 355622a
Clean up example assets
nvlukasz 91a4dba
Switch from joint_target to joint_f for control
nvlukasz d85b957
Remove ArticulationView.get_attribute_shape()
nvlukasz 740dde0
Removed env_offsets from ArticulationView
nvlukasz 01ea531
Add verbose flag to ArticulationView constructor
nvlukasz 0d2ba8a
Get rid of copy arg in get_attribute(), use lru_cache for caching str…
nvlukasz 02f5da8
Use joint_f instead of joint_target in cartpole example
nvlukasz e9aeb66
Formatting
nvlukasz 67a89f6
Added humanoid env
nvlukasz 37d64d0
Renaming things
nvlukasz 952dc0c
Ruff ruff
nvlukasz f9eaaef
Updated replicate_environment()
nvlukasz 22c7466
Merge branch 'main' into selection-api
nvlukasz 85aff6d
Update eval_fk() signature
nvlukasz ff7c78d
Merge branch 'main' into selection-api
nvlukasz 87d56dd
Added anymal
nvlukasz 855f8ef
Fixed articulation mask device
nvlukasz f1ff063
Merge branch 'main' into selection-api
nvlukasz 8d25c26
Parsing names from keys
nvlukasz 95563d6
Tweak debug output
nvlukasz 6d08222
Merge branch 'main' into selection-api
nvlukasz f57e716
Update to latest
nvlukasz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import math | ||
|
|
||
| import torch | ||
| import warp as wp | ||
|
|
||
| import newton | ||
| import newton.examples | ||
| import newton.utils | ||
| from newton.utils.isaaclab import replicate_environment | ||
| from newton.utils.selection import ArticulationView | ||
|
|
||
|
|
||
| class Example: | ||
| def __init__(self, stage_path=None, num_envs=8): | ||
| self.num_envs = num_envs | ||
|
|
||
| builder, stage_info = replicate_environment( | ||
| newton.examples.get_asset("envs/ant_env.usda"), | ||
| "/World/envs/env_0", | ||
| "/World/envs/env_{}", | ||
| num_envs, | ||
| (5.0, 5.0, 0.0), | ||
| # USD importer args | ||
| collapse_fixed_joints=True, | ||
| joint_ordering="dfs", | ||
| ) | ||
|
|
||
| up_axis = stage_info.get("up_axis") or newton.Axis.Z | ||
|
|
||
| # finalize model | ||
| self.model = builder.finalize() | ||
|
|
||
| self.solver = newton.solvers.MuJoCoSolver(self.model) | ||
|
|
||
| self.renderer = None | ||
| if stage_path: | ||
| self.renderer = newton.utils.SimRendererOpenGL( | ||
| path=stage_path, | ||
| model=self.model, | ||
| scaling=2.0, | ||
| up_axis=str(up_axis), | ||
| screen_width=1280, | ||
| screen_height=720, | ||
| camera_pos=(0, 4, 30), | ||
| ) | ||
|
|
||
| self.state_0 = self.model.state() | ||
| self.state_1 = self.model.state() | ||
| self.control = self.model.control() | ||
|
|
||
| self.sim_time = 0.0 | ||
| fps = 60 | ||
| self.frame_dt = 1.0 / fps | ||
|
|
||
| self.sim_substeps = 10 | ||
| self.sim_dt = self.frame_dt / self.sim_substeps | ||
|
|
||
| self.next_reset = 0.0 | ||
|
|
||
| # =========================================================== | ||
| # create articulation view | ||
| # =========================================================== | ||
| self.ants = ArticulationView(self.model, "/World/envs/*/Robot/torso", include_free_joint=True) | ||
|
|
||
| print(f"articulation count: {self.ants.count}") | ||
| print(f"link_count: {self.ants.link_count}") | ||
| print(f"joint_count: {self.ants.joint_count}") | ||
| print(f"joint_axis_count: {self.ants.joint_axis_count}") | ||
|
|
||
| print(f"joint_q shape: {self.ants.get_attribute('joint_q', self.model).shape}") | ||
| print(f"joint_qd shape: {self.ants.get_attribute('joint_qd', self.model).shape}") | ||
| print(f"joint_f shape: {self.ants.get_attribute('joint_f', self.model).shape}") | ||
| print(f"joint_target shape: {self.ants.get_attribute('joint_target', self.model).shape}") | ||
| print(f"body_q shape: {self.ants.get_attribute('body_q', self.model).shape}") | ||
| print(f"body_qd shape: {self.ants.get_attribute('body_qd', self.model).shape}") | ||
|
|
||
| # set all dofs to the middle of their range by default | ||
| dof_limit_lower = wp.to_torch(self.ants.get_attribute("joint_limit_lower", self.model)) | ||
| dof_limit_upper = wp.to_torch(self.ants.get_attribute("joint_limit_upper", self.model)) | ||
| default_dof_transforms = 0.5 * (dof_limit_lower + dof_limit_upper) | ||
|
|
||
| if self.ants.include_free_joint: | ||
| # combined root and dof transforms | ||
| self.default_transforms = wp.to_torch(self.ants.get_attribute("joint_q", self.model)).clone() | ||
| self.default_transforms[:, 2] = 0.8 # z-coordinate of articulation root | ||
| self.default_transforms[:, 7:] = default_dof_transforms | ||
| # combined root and dof velocities | ||
| self.default_velocities = wp.to_torch(self.ants.get_attribute("joint_qd", self.model)).clone() | ||
| self.default_velocities[:, 2] = 0.5 * math.pi # rotate about z-axis | ||
| self.default_velocities[:, 5] = 5.0 # move up z-axis | ||
| else: | ||
| # root transforms | ||
| self.default_root_transforms = wp.to_torch(self.ants.get_root_transforms(self.model)).clone() | ||
| self.default_root_transforms[:, 2] = 0.8 | ||
| # dof transforms | ||
| self.default_dof_transforms = default_dof_transforms | ||
| # root velocities | ||
| self.default_root_velocities = wp.to_torch(self.ants.get_root_velocities(self.model)).clone() | ||
| self.default_root_velocities[:, 2] = 0.5 * math.pi # rotate about z-axis | ||
| self.default_root_velocities[:, 5] = 5.0 # move up z-axis | ||
| # dof velocities | ||
| self.default_dof_velocities = wp.to_torch(self.ants.get_attribute("joint_qd", self.model)).clone() | ||
|
|
||
| # create disjoint index groups to alternate between | ||
| all_indices = torch.arange(num_envs, dtype=torch.int32) | ||
| self.indices_0 = all_indices[::2] | ||
| self.indices_1 = all_indices[1::2] | ||
|
|
||
| # reset all | ||
| self.reset() | ||
| self.next_reset = self.sim_time + 2.0 | ||
|
|
||
| self.use_cuda_graph = wp.get_device().is_cuda | ||
| if self.use_cuda_graph: | ||
| with wp.ScopedCapture() as capture: | ||
| self.simulate() | ||
| self.graph = capture.graph | ||
|
|
||
| def simulate(self): | ||
| for _ in range(self.sim_substeps): | ||
| self.state_0.clear_forces() | ||
|
|
||
| # explicit collisions needed without MuJoCo solver | ||
| if not isinstance(self.solver, newton.solvers.MuJoCoSolver): | ||
| newton.collision.collide(self.model, self.state_0) | ||
|
|
||
| self.solver.step(self.model, self.state_0, self.state_1, self.control, None, self.sim_dt) | ||
| self.state_0, self.state_1 = self.state_1, self.state_0 | ||
|
|
||
| def step(self): | ||
| if self.sim_time >= self.next_reset: | ||
| self.reset(self.indices_0) | ||
| self.next_reset = self.sim_time + 2.0 | ||
| self.indices_0, self.indices_1 = self.indices_1, self.indices_0 | ||
|
|
||
| # ========================= | ||
| # apply random controls | ||
| # ========================= | ||
| joint_forces = 300.0 - 600.0 * torch.rand((self.num_envs, 8)) | ||
| if self.ants.include_free_joint: | ||
| # include the leading root joint (pad with zeros) | ||
| joint_forces = torch.cat([torch.zeros((self.num_envs, 6)), joint_forces], axis=1) | ||
| self.ants.set_attribute("joint_f", self.control, joint_forces) | ||
|
|
||
| with wp.ScopedTimer("step", active=False): | ||
| if self.use_cuda_graph: | ||
| wp.capture_launch(self.graph) | ||
| else: | ||
| self.simulate() | ||
| self.sim_time += self.frame_dt | ||
|
|
||
| def reset(self, indices=None): | ||
| # ============================== | ||
| # set transforms and velocities | ||
| # ============================== | ||
| if self.ants.include_free_joint: | ||
| # set root and dof transforms together | ||
| self.ants.set_attribute("joint_q", self.state_0, self.default_transforms, indices=indices) | ||
| # set root and dof velocities together | ||
| self.ants.set_attribute("joint_qd", self.state_0, self.default_velocities, indices=indices) | ||
| else: | ||
| # set root and dof transforms separately | ||
| self.ants.set_root_transforms(self.state_0, self.default_root_transforms, indices=indices) | ||
| self.ants.set_attribute("joint_q", self.state_0, self.default_dof_transforms, indices=indices) | ||
| # set root and dof velocities separately | ||
| self.ants.set_root_velocities(self.state_0, self.default_root_velocities, indices=indices) | ||
| self.ants.set_attribute("joint_qd", self.state_0, self.default_dof_velocities, indices=indices) | ||
|
|
||
| if not isinstance(self.solver, newton.solvers.MuJoCoSolver): | ||
| self.ants.eval_fk(self.state_0, indices=indices) | ||
|
|
||
| def render(self): | ||
| if self.renderer is None: | ||
| return | ||
|
|
||
| with wp.ScopedTimer("render", active=False): | ||
| self.renderer.begin_frame(self.sim_time) | ||
| self.renderer.render(self.state_0) | ||
| self.renderer.end_frame() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| import argparse | ||
|
|
||
| parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) | ||
| parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.") | ||
| parser.add_argument( | ||
| "--stage_path", | ||
| type=lambda x: None if x == "None" else str(x), | ||
| default="example_selection_ant.usd", | ||
| help="Path to the output USD file.", | ||
| ) | ||
| parser.add_argument("--num_frames", type=int, default=1200, help="Total number of frames.") | ||
| parser.add_argument("--num_envs", type=int, default=16, help="Total number of simulated environments.") | ||
|
|
||
| args = parser.parse_known_args()[0] | ||
|
|
||
| with wp.ScopedDevice(args.device): | ||
| example = Example(stage_path=args.stage_path, num_envs=args.num_envs) | ||
|
|
||
| for _ in range(args.num_frames): | ||
| example.step() | ||
| example.render() | ||
|
|
||
| # import time | ||
| # time.sleep(0.2) | ||
|
|
||
| if example.renderer: | ||
| example.renderer.save() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would've been great if we didn't have to put this burden on the user side and could resolve this if/else internally;
Does this also mean that these if/else statements would be propagated all the way to IL to support various solvers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These if-statements are in the examples only to show two different ways of doing things. Normally, as a user, you would pick one and stick to it - no need to provide both code paths and if-statements.
Some background info...
There are two ways of dealing with articulation root transforms:
joint_qandjoint_qd. This allows you to set the root and dof states in a single call. I think this is the preferred way of doing it in Newton, but it deviates from the legacy tensor API.I'd be happy to collapse this into a single option, but I think that would mean option 1 (include free joint).
Another thing you might find weird is that the
joint_farray used to set joint forces/torques includes the free joint in Newton. So it can be used to apply joint forces and forces on the articulation root in one call, but that again deviates from the legacy tensor API.So the
include_free_jointoption is there to ease porting environments from the legacy tensor API to Newton. I'm open to revisiting this. For example, we could always include the free joint. It's the right thing to do for Newton, but it would mean a more complicated rewrite for Isaac Lab.Or maybe we can keep the
include_free_jointflag but make it more granular, so that you can set it on every attribute getter/setter. E.g.,The flexibility is nice, but it adds complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about it, the more I want to get rid of this
include_free_jointlogic. And I would also like to get rid of the special methods for dealing with articulation roots (get/set_root_transforms()andget/set_root_velocities().I think the selection API should always include the root joint. That is the Newton Way.
This would make it slightly more difficult to port envs from the legacy tensor API, but hear me out. I don't think it'll be that bad.
If the root joint is a free joint:
joint_qattribute slice[:, :7].joint_qattribute slice[:, 7:].joint_qdattribute slice[:, :6].joint_qdattribute slice[:, 6:].joint_fattribute slice[:, 6:].So it's essentially up to the user (or Isaac Lab) to read/write the correct slice of the joint arrays. This would simplify the selection API and get rid of the sketchy
include_free_jointflag that could be misunderstood or misused. That flag is only there to emulate compatibility with how we did things in PhysX.@Milad-Rakhsha-NV @mmacklin what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also make it easier to accommodate root joints that are not free joints. We'd just need to tweak the slice offsets based on how many coords/dofs/axes the root joint has.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an aside, fixed root joints are a bit of a conundrum right now, because they are stripped by the builder before the selection API sees them. So we might need some placeholder joint, something that tells us that there was a fixed joint. Especially if we want to allow getting/setting the transform of the root joint.
Cartpole is a good existing example. According to the joints in the Model, the cart joint is the root joint and the cart is the root link. But that's obviously not correct. We can't change the root transform of the rail, because its shapes are part of the world geometry and there's no easy way to make the descendant links depend on the transform of those static shapes (as far as I can tell).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nvlukasz how about we have a 2D mask for environments and joints? Anyway we wanted a way to set a subset of joints based on joint indices. If we can have the mask when getting and setting the attributes. Wouldn’t that satisfy both use cases and avoid duplication?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, maybe. I don't recall discussing a mask for subsets of joints, just remapping indices based on some orderings.
I think we can do masks, yes, but that doesn't really solve all root transform/velocity issues. For example, if the root joint is a free joint, you can get the root link transform from
joint_q. But if it's a fixed joint, you can't get it fromjoint_q, but have to usejoint_X_p. So the masks and attributes you use will need to change depending on the root joint type, which I think will be pretty annoying. That's why I'd rather provide special methods for that, likeget_root_transforms()andget_root_velocities().We can still use masks for joints, but I don't think it fixes everything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to clarify a few things about root transforms / free joints / maximal coordinates since there's a lot of issues flying around this atm:
@gyeomannvidia
joint_qarrayjoint_qDOFs are initialized to zero instead of inheriting the body xform, which can be unintuitive Bug in setting start pose of a rigid body. #93body_qis a function ofjoint_q, andeval_fk()is the way that we sync between them, and must be called before simulation, also related to issue Bug in setting start pose of a rigid body. #93joint_qarray (e.g.: revolute base) but are stored in thebody_qstate and computed using theparent_xformof the joint.My suggestion is that for Newton we should always add FREE joints for floating base articulations and rigid bodies. This is necessary to allow users to automatically switch between different solvers. (#43). TBD where to do this,
finalize()maybe a bit late, so I would suggest importers should handle this for now and that we document clearly this requirement for users building models by hand.In addition, we should initialize
joint_qfor FREE joints to the child body xform when callingadd_joint_free()so that the previously created body xform is respected.@nvlukasz
Assuming that the
ArticulationViewknows what the root body is, then I think these root quantities can be retrieved directly frombody_q[root_body],body_qd[root_body], in a unified way that doesn't depend on variations injointlayout (e.g.: revolute versus free joint at the root).Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I do currently. I use
body_qandbody_qdfor getting the root link states, but I set them throughjoint_qandjoint_qdif there's a free joint (I had assumed thatbody_qandbody_qdare essentially read-only attributes for articulations).But I think that using
body_qandbody_qdis actually wrong. We wanted this API to support reading and writing directly to the Newton buffers without copying. The getters return arrays that alias the Newton buffers with appropriate offsets and strides. Example:The kernel writes directly to
State.joint_qusing the strided aliasmy_joint_q. There are no copies or staging buffers, which is what we wanted. Technically there's no need for a setter, because the data are already in the right place, but there's a plot twist coming and I'll come back to it shortly (*).Now. What should
get_root_transforms()andget_joint_transforms()do? I think they should do something equivalent. Example:The kernel writes directly to
State.joint_q, this time using two strided aliases, one for the roots and one for the rest of the joints.get_root_transforms() == get_attribute("joint_q")[:, :7]get_joint_transforms() == get_attribute("joint_q")[:, 7:]I think this is nice and it fulfills what Isaac Lab is asking for.
If
get_root_transforms()returned an alias of thebody_qarray, I don't think the zero-copy approach will work. I assume that we would still need to copy the root transforms tojoint_q, otherwise they'll get overwritten on the next simulation step oreval_fk()and the changes will be lost. Please correct me if I'm wrong.Things get interesting when the root joint is not a free joint. I would still like to support a zero-copy approach to setting all the transforms. For example, if the root joint is fixed:
get_root_transforms() == get_attribute("joint_X_p")[:, 0]# root joint frame in parentget_joint_transforms() == get_attribute("joint_q")# all internal joint coordsIs this correct? I'm basing it on some sample code from @eric-heiden that used
joint_qfor free joints andjoint_X_pfor fixed joints.I would like to keep this zero-copy mode as much as possible, though it may not be possible in all scenarios.
Zero-copy may not be possible if we want to re-order the joints or use masks like @oahmednv requested. We may need staging buffers to swizzle the joint data around.
If the user requested a different joint order when constructing the ArticulationView, then the ordering of joints in
my_joint_qis different than inState.joint_q. We have a couple of possibilities here.joint_qinto it.wp.indexedarraythat remaps the joint axes as needed.Creating a staging buffer is a con, but the pro is that it's compatible with PyTorch and other array-based frameworks. That's basically what we did in the previous tensor APIs. Creating a
wp.indexedarrayoverState.joint_qavoids a staging buffer. The indexed array can be used in zero-copy mode from Warp, but for PyTorch we still need to create a contiguous copy. And even with Warp, introducingwp.indexedarrayinto the mix can be a curve ball. If in some cases we return a stridedwp.array(no reordering) and in others we return awp.indexedarray, then the user must be prepared for it. That means writing kernels that support both, which brings new caveats.(*) Getting back to setters. With zero-copy mode, setters are not needed, because we just write to the Newton arrays directly. But as described above, zero-copy is not always possible, so a setter is needed to put the data in the right place. The API guidance I recommend is that the setter should always be called. Under the hood, we can early-out if the values are already in the right place, so it will be essentially free. But for API correctness, a setter should be called. Furthermore, if we ever need to notify the solver that something has changed, the setter is the place to do it.
So yeah, designing this API is quite challenging with all the different objectives that sometimes contradict each other.
get_root_transforms()should accomplish.So we offer two API flavours, one for working with the raw attributes and one that provides an interface that resembles the previous tensor APIs. With raw attributes, you can:
And equivalently, with the other methods:
With the notable difference that if we change the root free joint to a fixed joint, the code that uses raw attributes will need to change, but the code that uses the other methods can stay the same. So the raw attributes are a more "low-level" way to work with Newton, and the other methods create a light abstraction layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I think I found good solutions. There are some breaking changes, so I created a separate PR for review: #186