Repository navigation
Conversation
Fix fresh-process USD initialization, isolate geometry failures, validate worker outputs, and share bounded seating checks. Preflight native preview names and cache paths while preserving hidden collision bodies. Remove deferred robot preparation and historical runtime material from this layer, retain an immutable archive and current EPA tooling, and extend focused regressions.
han-xudong
left a comment
There was a problem hiding this comment.
Reviewed PR #981 at 39b47ac9f71da99fa93b3f3f2fa1f188edd7f249 against b629c7158b8180934c0990b71e12e1031ddac16e.
Seven P2 findings are attached inline. I recommend addressing them before merging.
Validation included focused benchmark checks (34 passed, 1 skipped), native configuration checks (22 passed; one failure was due to the local older SDK missing ConstraintDesc), API/context checks (12 passed), and targeted reproductions of the reported cases. The current documentation CI failure was also checked. Native rc2 simulation, GPU/rendering, and a fresh wheel build were not rerun.
| related_topics: [simulation-system, data-assets, env-framework, task-engine] | ||
| related_topics: [simulation-system, data-assets, env-framework, task-engine, assembly-assets] | ||
| status: active | ||
| - id: assembly-assets |
There was a problem hiding this comment.
[P2] Add the new topic to the architecture graph
Registering assembly-assets here without a corresponding topic_ids entry in docs/architecture/curated.json breaks the existing architecture coverage contract. docs/architecture/web/tests/data.test.ts requires every registered topic to be represented, and the current Documentation tests job fails specifically because assembly-assets is missing. Add the topic to an appropriate architecture node/view alongside this registration so the documentation checks pass.
| asset_physics_mode="overlay", | ||
| link_attrs={ | ||
| "moving_part": LinkPhysicsOverrideCfg( | ||
| link_names_expr=[body.name], attrs=properties |
There was a problem hiding this comment.
[P2] Escape literal body names before using them as link selectors
link_names_expr is interpreted with re.fullmatch, but body.name is a literal source identifier. A valid world-mounted MJCF hinge named gear[0] imports and passes this exporter's name validation, yet the generated selector ['gear[0]'] cannot match the actual link gear[0]. The subsequent configure_articulation_desc() call therefore raises ValueError: Not all regular expressions are matched, preventing native preparation. Escape the source identifier, e.g. with re.escape(body.name), before constructing the selector.
| # Include standalone solver options for the reference. The adapter discards | ||
| # worldbody decorations and floor, retaining only named physical bodies. | ||
| xml = mjfit.build(cfg, standalone=True) | ||
| asset = import_mjcf(xml, config=cfg, seed=seed, generator_revision=revision) |
There was a problem hiding this comment.
[P2] Resolve generated mesh paths before compiling the MJCF string
The public generator's mesh_dir option is forwarded here, but its external mesh paths are then compiled without their base directory. With the pinned mjfit revision, build_assembly("circle", mesh_dir="/tmp/<temporary-directory>/meshes") writes 35 OBJ files and then fails with Error opening file 'meshes/plate_visual.obj': the generated XML uses a relative compiler meshdir, while import_mjcf() compiles the string relative to the process working directory. Resolve/embed those generated assets while retaining a portable source representation, or explicitly reject this option before generating files.
| return AssemblyAsset( | ||
| generator_revision=generator_revision, | ||
| source_mjcf=xml, | ||
| config=dict(config or {}), |
There was a problem hiding this comment.
[P2] Normalize public shape arrays before storing manifest configuration
Copying the generator configuration verbatim can retain NumPy values that the manifest's JSON encoder cannot serialize. For example, build_assembly({"kind": "peg", "hole": mjfit.shapes.star(5, .012, .006), "clearance": 1e-4}) successfully creates two bodies using the pinned provider, but asset.content_hash then raises TypeError: Object of type ndarray is not JSON serializable. Consequently caching, saving the manifest, USD export, and preparation paths that need the hash fail for this public programmatic configuration. Normalize supported arrays/scalars to stable JSON primitives before retaining the configuration.
| configs.update( | ||
| to_articulation_configs( | ||
| asset, | ||
| Path(directory) / asset.content_hash, |
There was a problem hiding this comment.
[P2] Check the hinge cache subdirectory against the caller's root
If <directory>/<asset.content_hash> already exists as a symlink to another directory, the default prepare_native_configs(..., file_visuals=False) follows it and writes generated hinge files outside the caller's preparation directory. to_articulation_configs() first resolves this derived path and treats the resolved destination as its trusted root, so its later containment checks no longer protect the original root. This is reproducible with an imported hinge and can create or replace same-named URDF/OBJ/MTL files outside the cache; the rigid visual writer rejects the analogous escape. Validate the resolved hash subdirectory against the caller's resolved root before delegation.
| * round(cfg.duration / cfg.timestep) | ||
| * cfg.timestep | ||
| / (2 * math.pi) | ||
| * float(asset.config["pitch"]) |
There was a problem hiding this comment.
[P2] Include asset scale in the thread lead oracle
The pinned mjfit generator multiplies every thread length, including pitch, by scale, but this ideal-advance calculation uses the unscaled configuration pitch. For build_assembly("m10", scale=2) with duration 0.2, timestep 0.001, and spin rate 15, the correct physical advance is 0.001432394487827058 m. Supplying exactly that advance to _score() produces an ideal advance of 0.000716197243913529 m, approximately 100% relative error, and success rate 0. Include asset.config.get("scale", 1.0) in the physical pitch so correctly scaled motion is not rejected.
| run_warp_probe, | ||
| ) | ||
|
|
||
| selected = ConstrainedProbeCfg( |
There was a problem hiding this comment.
[P2] Apply or reject solver overrides for constrained probes
For S0/S2/S3/S4, this conversion drops cfg.solver and cfg.use_mujoco_cpu, even though the CLI accepts both and the original values are later retained in the result's config. In a reproduction requesting solver="pgs", the output records PGS while the prepared reference model still uses mjSOL_NEWTON; similarly, the native constrained runner does not select the requested Newton CPU route. The passive-only guard rejects tolerance/MultiCCD overrides but does not reject these options. Propagate them into the constrained execution path, or reject them explicitly before launching a cell, so recorded experiment settings match actual execution.
Description
Add a reusable precision-assembly toolkit with portable manifests, optional pinned mjfit generation, USD export and native compound/hinge preparation. Source body frames, visual/collision ownership and authored mass/COM/inertia remain explicit. Native preparation belongs to
embodichain.toolkits.assembly.native, withprepare_native_configsas the mixed-body entry point.Demos and benchmarks now select the Newton solver family explicitly.
PrecisionRunCfg.newton_solverand demo--newton-solveracceptmujoco_warpordexuni; omission preserves the existing MuJoCo Warp selection.--solvercontinues to select the MuJoCo algorithm. The same iteration, capacity, integrator, CPU and explicit tolerance/MultiCCD settings are routed into direct MuJoCo Warp options or DexUni's nestedmujoco_options.Prepared results retain the actual Newton family, MuJoCo algorithm and SDK
solver_diagnostics. These assembly profiles require DexUni's measuredpure_mujocodispatch; missing diagnostics, a mismatched family or a VBD/coupled dispatch is reported explicitly. Requested profiles and actual dispatch stay distinct in reports. Failed cells remain attempts of their requested profile, and each cell owns its preparation files. Independent reference cells are not duplicated by a Newton-family sweep. Constrained native S2–S4 probes expose the same family selection; passive-only overrides are rejected on constrained matrix calls.Usage
Programmatic callers use
PrecisionRunCfg(backend="newton", newton_solver="dexuni")orrun_native_probe(..., newton_solver="dexuni"). An explicit family is invalid on non-Newton backends.--use-mujoco-cpuremains an explicit single-world integration profile, never an automatic fallback.Branch relationships
#981 remains the independent asset/toolkit PR on
main. #982 is an independent RL runtime PR onmain; #983 adds the temporal policy on #982. #984 adds assembly tasks on #983 and retains the complete asset implementation through ordinary merges from #981. This update propagates assembly fixes to #981 and #984 while preserving #982/#983's independent scope and concurrent RL fixes.Type of change
S2 accuracy and validation
The former constrained protocol forced one 1 ms integration step. Its stiff contact dynamics produced float32 transient wall errors up to 18.49 µm, exceeding the existing 10 µm budget. Native options below the library's float32 stopping floor do not eliminate the issue, so no SDK source patch or nominal-tolerance workaround is used.
ConstrainedProbeCfg.integration_substepsnow defaults to 4 across CPU reference, raw MuJoCo Warp and native routes. A requested 1 ms interval uses four 0.25 ms physical steps. The physical duration, geometry, mass, force, damping and error budget are unchanged. Every settled physical step is scored; a 0.3 s trial scores 600 samples per direction. Reports expose requested/integration intervals, actual step counts and sample counts.--integration-substeps 1retains the old protocol. This increases integration work and trace storage approximately fourfold.The formal four-profile S2 CLI matrix uses the original circle asset, 100 µm clearance, four directions and 0.3 s:
pure_mujoco)Native overall qualification remains
unverifiedsolely because live contact-overflow accounting is unavailable; the S2 error/finite/drift checks now pass. No metric or acceptance threshold was relaxed. The fixture retains the pinned generator's inline collision geometry/inertias and needs no generator at test time.Isolated Python 3.12 / actual SDK 0.6.0rc2:
black .), API coverage 4948/4948, context/whitespace and strict focused HTML checks passed. Existing cases are extended; the only added fixture closes the real GPU reproducer gap.The previously qualified formal demo recordings (peg, blocked control, many pegs, M10 and CG gears) remain in the guide with provenance. Passive integration defaults are unchanged. No new training, model selection or full robot-policy replay is claimed.
Latest review resolution
All seven P2 review suggestions are covered:
assembly-assetsto the toolkit architecture node, link its guide, refresh source evidence/snapshots.gear[0]receives its own physics while decoygear0is untouched.mesh_dircreates broken external referencesCI failure diagnosis and reproduction
The failing Documentation tests job reported that
assembly-assetswas registered in MAP but absent from architecturetopic_ids. This is fixed in the curated node and generated snapshot. The exact frontend/Sphinx check sequence passes locally: 17 data/state/link tests, 8 browser tests, 10 real Sphinx/browser integration tests, plus format checking. Native USD comparison tests also register SDK schemas before their first stage, removing collection-order dependence while leaving portable export SDK-independent.#982's reported offline/export jobs pass; its SDK jobs were queued, not failing. #983 had no reported checks at inspection. Their independent runtime/policy smoke groups pass 230 and 39 tests respectively. No assembly code is reintroduced into either branch. The self-hosted #981 build/test jobs remain queued.
The new Documentation tests run on
4ad223908completed successfully, including the formerly failing architecture coverage, browser and real Sphinx integration steps. The new lint, documentation-change and test-plan jobs also pass. Queued self-hosted work is not reported as completed.Documentation and research
docs/source/features/toolkits/precision_assembly.mdis linked from Toolkits, API reference and the example README. It covers functional entry points, installation, generation/manifests, native preparation, explicit solver choices, matrix commands and measured limits. Five embedded H.264 clips, posters, before/after S2 measurements and versioned provenance ship underdocs/source/_static/assembly/(about 0.5 MB). The DexUni clip now comes from the formal CLI, with source hashes and SDK dispatch metadata, rather than an experimental host.The guide credits Blind Precise Assembly, links the authors' project and pinned mjfit source, and includes BibTeX. It distinguishes asset/passive-demo integration from the paper's learned-policy and sim-to-real results.
Dependencies and limits
The optional mjfit provider is pinned to
5833f465896e1e06bc4be4f003be847f5183a4db. Prepared manifests load without mjfit or MuJoCo. Normal installation delegates compatible simulation dependencies to SDK/Newton; there is no assembly extra or duplicate project-level MuJoCo constraint. No dependency patch files, overlay scripts, dedicated patch tests or patch-specific packaging hooks are introduced.SDK rc1/rc2 static USD import drops child colliders for disabled compound bodies; the regression remains a strict version-specific expected failure and direct construction preserves the pieces. FastRT is the measured native recording profile; Hybrid recording crashed under the remote Xvfb setup. The failed documentation workflow is reproduced and fixed as detailed above; whole-site rendering can still emit unrelated existing warnings. The new guide passes its strict focused HTML build.
Checklist