16 KiB
Testing
Import from top-level sdk:
from sdk import AllowedOverlap, TestContext, TestFailure, TestReport
TestContext is the SDK test harness for authored models. It records blocking
failures, non-blocking warnings, and explicit allowances, then returns a
TestReport from run_tests().
Public Types
TestFailure
TestFailure(name: str, details: str)
name: recorded check name.details: failure detail string stored in the report.
AllowedOverlap
AllowedOverlap(
link_a: str,
link_b: str,
reason: str,
elem_a: str | None = None,
elem_b: str | None = None,
)
- Structured overlap allowance recorded by
allow_overlap(...).
TestReport
TestReport(
passed: bool,
checks_run: int,
checks: tuple[str, ...],
failures: tuple[TestFailure, ...],
warnings: tuple[str, ...] = (),
allowances: tuple[str, ...] = (),
allowed_isolated_parts: tuple[str, ...] = (),
allowed_overlaps: tuple[AllowedOverlap, ...] = (),
)
passed:Truewhen no blocking failures were recorded.checks_run: number of recorded checks.checks: ordered check names.failures: blocking failures.warnings: warning messages and failed warning-tier checks.allowances: human-readable entries recorded byallow_*.allowed_isolated_parts: names of parts explicitly allowed byallow_isolated_part(...).allowed_overlaps: structured overlap allowances recorded byallow_overlap(...).
Construction
ctx = TestContext(model, seed=0)
model: theArticulatedObjectunder test.seed: deterministic sampling seed for pose-sampled checks.
Generated models should end run_tests() with:
return ctx.report()
compile_model automatically runs the baseline sanity/QC pass:
- model validity
- exactly one root part
- mesh asset readiness
- floating disconnected-part-group detection
- disconnected geometry-island detection within a part
- current-pose real 3D overlap detection
Use run_tests() for prompt-specific exact assertions such as expect_gap(...),
expect_overlap(...), expect_contact(...), and expect_within(...).
Keep pose-specific checks lean. Add articulated-pose assertions only when a prompt-critical mechanism remains ambiguous after exact rest-pose checks.
High-Signal Testing Habits
- Prefer exact part or element relationship checks over broad heuristics when the mounting path, retained insertion, or clearance is what matters.
- If a part has multiple visual regions, use exact contact/support checks for the critical mounts instead of broad whole-part approximations.
- Delay brittle numeric thresholds and exact
elem_*checks until the geometry representation is stable. - Once
run_tests()references a visual by exactelem_*name, treat that name as a contract. Preserve it or update every dependent check in the same edit. - Do not add broad lower/upper pose sweeps by default. Use one or two decisive articulated poses when a prompt-critical mechanism still needs proof.
- When support, floating status, or overlap meaning is ambiguous, probe first,
then encode the lasting invariant in
run_tests().
Parameter Conventions
These rules apply across most of the API. Method signatures below are authoritative when a helper differs.
link_*,part,positive_link,negative_link,inner_link, andouter_linkaccept either aPartobject or a part name string. The API uses bothlinkandpartterminology; both refer to authored parts.elem_*,positive_elem,negative_elem,inner_elem, andouter_elemaccept either a namedVisualobject or a visual name string belonging to the referenced part.axismust be"x","y", or"z"and always refers to the positive world axis direction.axesaccepts"x","y","z", combinations such as"xy", or a sequence such as("x", "y").- Distances and tolerances are meters.
nameoverrides the recorded check name in the final report.max_pose_samplescontrols how many sampled articulation poses are checked.contact_tol=None,overlap_tol=None, andoverlap_volume_tol=Nonemean “use the SDK default”.
Reporting Helpers
report() -> TestReport
Returns the final report for the checks recorded so far.
check(name: str, ok: bool, details: str = "") -> bool
Records a custom blocking check.
name: report name.ok: pass/fail result.details: stored whenokisFalse.
fail(name: str, details: str) -> bool
Convenience wrapper for check(name, False, details).
warn(text: str) -> None
Appends a non-blocking warning string to the report.
Allowances
allow_overlap(link_a, link_b, *, reason, elem_a=None, elem_b=None) -> None
Records an intentional real 3D interpenetration so overlap checks do not fail on that case. Do not use this just because one part sits inside another part's footprint or cavity.
reason: required justification string.elem_a,elem_b: optional element-level scope. If omitted, the allowance applies to the full part pair.
Acceptable Overlap Patterns
Use allow_overlap(...) for small intentional overlap that is local, mostly
hidden, and mechanically explanatory. Broad whole-part allowances are the wrong
default.
- Nested proxy fits: a slider, mast, or drawer member represented as moving
inside a simplified solid sleeve. Pair the allowance with
expect_within(...)andexpect_overlap(...)on the retained-insertion axis. - Captured pins or shafts: a hinge pin, knob stem, or axle intentionally nested
inside a barrel, bushing, or hub proxy. Pair the allowance with
expect_contact(...),expect_gap(..., max_penetration=...), or a decisive pose check showing the mechanism moves correctly. - Seated trim or compliant compression: a bezel, cap, plug, seal, or bumper
that needs a tiny local embed to read as seated or compressed. Pair the
allowance with
expect_contact(...)orexpect_gap(..., max_penetration=...)on the seating interface.
allow_isolated_part(part, *, reason) -> None
Records that a named part is allowed to remain isolated in the compiler-owned floating/disconnected-part-group pass.
If the intentional floating assembly is a multi-part group, allow each authored part in that group.
allow_coplanar_surfaces(link_a, link_b, *, reason, elem_a=None, elem_b=None) -> None
Records an intentional coplanar-surface relationship for
warn_if_coplanar_surfaces(...).
Nested Sliders And Telescoping Fits
The compiler-owned overlap pass treats real 3D interpenetration as a failure unless you explicitly allow it.
Use this decision rule for nested prismatic fits:
- if the outer member is modeled as a true hollow or clearanced sleeve, prove
the fit with
expect_within(...),expect_gap(...), and retained-insertion checks; do not useallow_overlap(...) - if the outer member is a simplified solid proxy and the authored mechanism is
still intended to represent one member sliding inside another, scoped
allow_overlap(...)calls are acceptable, but only for the specific named elements that stand in for the sleeve/member fit
For telescoping poles, rails, and sleeves, the usual exact-check pattern is:
expect_within(...)on the non-motion axes to prove centeringexpect_overlap(..., axes="<slide axis>")at rest and at max extension to prove retained insertion- a
with ctx.pose(...)check showing the child actually moves in the intended direction
expect_overlap(...) is a projected overlap check, not a collision waiver. It
proves retained length along an axis; it does not suppress the compiler-owned
overlap pass.
ctx.allow_overlap(
outer_stage,
inner_stage,
elem_a="outer_sleeve",
elem_b="inner_member",
reason="The inner member is intentionally represented as sliding inside the sleeve proxy.",
)
ctx.expect_within(
inner_stage,
outer_stage,
axes="xy",
inner_elem="inner_member",
outer_elem="outer_sleeve",
margin=0.002,
name="inner member stays centered in the sleeve",
)
ctx.expect_overlap(
inner_stage,
outer_stage,
axes="z",
elem_a="inner_member",
elem_b="outer_sleeve",
min_overlap=0.080,
name="collapsed stage remains inserted in the sleeve",
)
rest_pos = ctx.part_world_position(inner_stage)
with ctx.pose({slide_joint: slide_upper}):
ctx.expect_within(
inner_stage,
outer_stage,
axes="xy",
inner_elem="inner_member",
outer_elem="outer_sleeve",
margin=0.002,
name="extended stage stays centered in the sleeve",
)
ctx.expect_overlap(
inner_stage,
outer_stage,
axes="z",
elem_a="inner_member",
elem_b="outer_sleeve",
min_overlap=0.030,
name="extended stage retains insertion in the sleeve",
)
extended_pos = ctx.part_world_position(inner_stage)
ctx.check(
"stage extends upward",
rest_pos is not None and extended_pos is not None and extended_pos[2] > rest_pos[2] + 0.02,
details=f"rest={rest_pos}, extended={extended_pos}",
)
The wrong default is a broad whole-part allowance such as
ctx.allow_overlap("cabinet", "drawer", ...) when only one named sleeve/member
interface is intentionally overlapping. Scope the allowance to the specific
elements whenever the representation gives you stable names to target.
Pose and World-Space Queries
pose(joint_positions: dict[object, float | Origin] | None = None, **kwargs: float | Origin) -> Iterator[None]
Temporary pose override context manager.
with ctx.pose({hinge: 0.5}):
...
with ctx.pose(hinge=0.5):
...
joint_positions: mapping of articulation object or joint name to position. Scalar joints usefloat; floating joints useOrigin(...).**kwargs: joint-name shorthand.- Revolute and continuous positions are radians; prismatic positions are meters. Floating poses use
Origin.xyzin meters andOrigin.rpyin radians. - Positive scalar values follow the configured joint convention: right-hand rule for revolute/continuous, translation along
+axisfor prismatic. Floating articulations do not useaxis. - Mimic followers are derived automatically from their source articulation. Pose the source joint, not the mimic follower.
- Restores the previous pose on exit.
When debugging a reversed hinge or slider, compare the closed pose and an
opened/extended pose with part_world_position(...), part_world_aabb(...),
or prompt-specific expect_* checks. A good default is to confirm that the
upper-limit pose moves outward/upward in the intended direction, not just that
it avoids overlap.
part_world_position(part) -> tuple[float, float, float] | None
Returns the part origin position in world coordinates for the current pose.
link_world_position(link) -> tuple[float, float, float] | None
Alias for part_world_position(...).
part_world_aabb(part) -> tuple[Vec3, Vec3] | None
Returns the world-space AABB of the full part in the current pose.
link_world_aabb(link) -> tuple[Vec3, Vec3] | None
Alias for part_world_aabb(...).
part_element_world_aabb(part, *, elem) -> tuple[Vec3, Vec3] | None
Returns the world-space AABB of one named visual element on a part.
Structural Checks
All methods in this section record a named check and return True on pass,
False on fail.
fail_if_articulation_origin_far_from_geometry(*, tol=0.015, reason=None, name=None) -> bool
Fails when an articulation origin is farther than tol from nearby geometry.
tol: non-negative absolute tolerance in meters.reason: optional note recorded as a warning when using a relaxed tolerance.
Available, but not part of the recommended default stack because the tolerance is absolute rather than scale-aware.
warn_if_articulation_origin_far_from_geometry(*, tol=0.015, reason=None, name=None) -> bool
Warning-tier version of the same check.
warn_if_coplanar_surfaces(*, max_pose_samples=32, plane_tol=0.001, min_overlap=0.02, min_overlap_ratio=0.35, ignore_adjacent=True, ignore_fixed=True, name=None) -> bool
Warning-tier heuristic for suspicious coplanar or nearly coplanar surfaces.
plane_tol: maximum plane separation.min_overlap: minimum in-plane overlap distance on both in-plane axes.min_overlap_ratio: minimum overlap area ratio, in[0, 1].ignore_adjacent,ignore_fixed: same meaning as the sampled overlap checks.
Use only when this specific heuristic answers a real uncertainty.
Exact Assertions
All methods in this section record a named check and return True on pass,
False on fail.
expect_origin_distance(link_a, link_b, *, axes="xy", min_dist=0.0, max_dist=None, name=None) -> bool
Checks the distance between part origins along the requested axes.
min_dist: inclusive lower bound.max_dist: optional inclusive upper bound.
expect_origin_gap(positive_link, negative_link, *, axis, min_gap=0.0, max_gap=None, name=None) -> bool
Checks the signed origin-to-origin gap along one positive world axis.
positive_link: object expected on the positive side of the axis.negative_link: object expected on the negative side of the axis.min_gap: inclusive lower bound.max_gap: optional inclusive upper bound.
expect_contact(link_a, link_b, *, contact_tol=1e-6, elem_a=None, elem_b=None, name=None) -> bool
Checks exact minimum distance between two parts or two named elements.
contact_tol: maximum allowed separation to still count as contact.elem_a,elem_b: optional named element scope.
expect_gap(positive_link, negative_link, *, axis, min_gap=None, max_gap=None, max_penetration=None, positive_elem=None, negative_elem=None, elem_a=None, elem_b=None, name=None) -> bool
Checks signed exact-geometry gap along a positive world axis.
- The measured gap is
positive.min[axis] - negative.max[axis]. min_gap: inclusive lower bound. If omitted, it is derived frommax_penetration.max_gap: optional inclusive upper bound.max_penetration: convenience way to express the allowed overlap depth; equivalent to a lower bound of-max_penetration.positive_elem,negative_elem: optional named element scope.elem_a,elem_b: accepted as compatibility aliases forpositive_elem/negative_elem.
This is the main exact clearance and seating helper.
expect_overlap(link_a, link_b, *, axes="xy", min_overlap=0.0, elem_a=None, elem_b=None, name=None) -> bool
Checks exact projected overlap between two parts or named elements. This is a footprint/projection check, not a collision/contact check.
min_overlap: required overlap on every requested axis.elem_a,elem_b: optional named element scope.
expect_within(inner_link, outer_link, *, axes="xy", margin=0.0, inner_elem=None, outer_elem=None, elem_a=None, elem_b=None, name=None) -> bool
Checks that one part or named element stays within another on the requested axes.
margin: allowed slack outside the outer bounds.inner_elem,outer_elem: optional named element scope.elem_a,elem_b: accepted as compatibility aliases forinner_elem/outer_elem.
For nested sliders, use expect_within(...) on the non-motion axes and pair it
with expect_overlap(...) or expect_gap(...) on the slide axis. Do not use
expect_within(...) by itself as proof that the moving member still remains
inserted at full extension.
Floating pose notes
ctx.pose(...)accepts mixed values: scalarfloatfor scalar articulations andOrigin(...)forFLOATING. Passing the wrong value kind is a validation error.- For floating articulations,
Origin.xyzis relative translation in the articulation frame andOrigin.rpyis relative rotation in that same frame. Zero floating pose isOrigin(). - Mimic followers cannot be overridden directly in
ctx.pose(...); drive the source articulation instead. sample_poses(...)and pose-sampled QC use onlyOrigin()for floating joints by default. To test additional floating poses, providejoint.meta["qc_samples"] = [Origin(...), ...].