app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
Update 11 file(s)
10 files changed
+163−16
diff truncated
CHANGELOG.md+2addin/AdomBridge/commands/mechanical/exploded_render.py+7addin/AdomBridge/commands/mechanical/exploded_stills.py+6addin/AdomBridge/commands/mechanical/glyphs.py+84addin/AdomBridge/commands/mechanical/render_image.py+9−1addin/AdomBridge/commands/mechanical/schema.json+11−3contracts/describe.json+22−6docs/MECHANICAL.md+11−3rust/core/tests/golden/mech_capabilities.json+11−3rust/core/tests/golden/mech_validation.jsonCHANGELOG.md+2@@ -9,6 +9,8 @@ Canonical: https://wiki.adom.inc/adom/fusion-bridge ยท GitHub backup: `adom-inc/ `geometry_query`'s `near` measures bounded faces and edges in root space (PR #38 by Drew Owens): a coplanar face or collinear edge beside the pick read 0 mm and matched too. Add-in 1.0.35: Fusion needs one restart. +Clean captures (PR #39 by Drew Owens): `exploded_view_render` and `exploded_view_stills` hide joint, sketch and construction glyphs while they capture, and put back exactly what they turned off, without marking the document modified. `glyphs: true` keeps them. `render_image` hides them only while fitting a view.+ ## 1.13.75 (2026-11-05) Mechanical writes work on designs that already carry faults (PR #36 by Drew Owens). A write is held to the faults present before it: it may not add one, change one, or leave one on what it edits in place, or it refuses with `unhealthy_model` and rolls back. A healthy design behaves as before. Replies carry `preExistingFaults`. An unsupported sketch relationship only refuses a write that names that sketch. Add-in 1.0.34: Fusion needs one restart.
addin/AdomBridge/commands/mechanical/exploded_render.py+7@@ -11,6 +11,7 @@ import adsk.fusion from .core import SESSION_ID, concrete, current_target, digest, identity, ref, require, resolve, target from .exploded_view import MAX_TRACKED, SPLIT_HINT, compile_steps, matrix, request_arguments, sample_plan, text+from .glyphs import Glyphs MAX_FRAMES = 3600 MOVED_NOTE = ("Fusion marks a document modified when a part moves, even after the move is reverted; the parts are back where "@@ -182,6 +183,7 @@ def exploded_view_render(app, args): original_camera = viewport.camera mover = Mover(snapshot, handles)+ glyphs = Glyphs(design, args.get("glyphs", False)) def pose_at(seconds): poses = sample_plan(snapshot, steps, [seconds], total, playback)[0]["poses"]@@ -190,6 +192,8 @@ def exploded_view_render(app, args): written = 0 try:+ # joint, sketch and construction glyphs off first, so the fit frames the parts and no frame shows them+ glyphs.hide() # frame the most exploded state so every part stays in view, then hold that camera; check there # that every part reached its planned pose held = held_back(snapshot, pose_at(total if playback == "explode" else 0.0), handles)@@ -206,11 +210,13 @@ def exploded_view_render(app, args): require(viewport.saveAsImageFile(str(path), width, height), "render_failed", "Fusion could not save frame %d." % index) written += 1 finally:+ glyphs.restore() if snapshots.hasPendingSnapshot: snapshots.revertPendingSnapshot() original_camera.isSmoothTransition = False viewport.camera = original_camera viewport.refresh()+ require(glyphs.restored, "restore_failed", "Object Visibility switches did not come back after rendering: %s." % ", ".join(glyphs.failed)) after, _ = capture(design, value, scope) drift = home_drift(snapshot, after) require(drift is not None and drift <= POSE_TOLERANCE_MM, "restore_failed",@@ -225,6 +231,7 @@ def exploded_view_render(app, args): "documentModified": modified, **({"documentModifiedNote": MOVED_NOTE} if modified else {}), "durationSeconds": round(len(times) / fps, 3), "playback": playback, "captions": captions(steps, total, playback), "pattern": "frame_%05d.png", "restored": True, "restoredWithinMm": round(drift, 6), "heldBack": held, "moves": mover.moves,+ "glyphs": glyphs.report(), "encode": "ffmpeg -framerate %d -i frame_%%05d.png -c:v libx264 -pix_fmt yuv420p -crf 18 exploded.mp4" % fps}
addin/AdomBridge/commands/mechanical/exploded_stills.py+6@@ -15,6 +15,7 @@ import adsk.core from .core import SESSION_ID, current_target, require, target from .exploded_render import MOVED_NOTE, POSE_TOLERANCE_MM, Mover, capture, home_drift, moved_scope from .exploded_view import compile_steps, request_arguments, sample_plan+from .glyphs import Glyphs MAX_STEPS = 64 MAX_MOVES = 2048@@ -216,6 +217,7 @@ def exploded_view_stills(app, args): viewport = app.activeViewport original_camera = viewport.camera mover = Mover(snapshot, handles)+ glyphs = Glyphs(design, args.get("glyphs", False)) boxes = {key: world_box(handles[key]) for key in owner} # read at home, before anything moves mode = args.get("camera", "auto") require(mode in ("auto", "fit", "current"), "invalid_input", "camera must be auto, fit or current.")@@ -237,6 +239,7 @@ def exploded_view_stills(app, args): images, steps_out, written = {}, [], 0 try:+ glyphs.hide() # no joint, sketch or construction glyphs in the step images camera = viewport.camera camera.isSmoothTransition = False camera.cameraType = adsk.core.CameraTypes.OrthographicCameraType@@ -309,11 +312,13 @@ def exploded_view_stills(app, args): "before": before, "after": after, "context": images["context"] if k == 1 else steps_out[-1]["after"], "instances": instances}) finally:+ glyphs.restore() if snapshots.hasPendingSnapshot: snapshots.revertPendingSnapshot() original_camera.isSmoothTransition = False viewport.camera = original_camera viewport.refresh()+ require(glyphs.restored, "restore_failed", "Object Visibility switches did not come back after rendering: %s." % ", ".join(glyphs.failed)) after_capture, _ = capture(design, value, scope | hidden_paths) drift = home_drift(snapshot, after_capture) require(drift is not None and drift <= POSE_TOLERANCE_MM, "restore_failed",@@ -326,6 +331,7 @@ def exploded_view_stills(app, args): return {"target": current_target(app), "sourceTarget": value, "folder": str(folder), "size": [width, height], "camera": view.describe(), "images": written, "documentModified": modified, **({"documentModifiedNote": MOVED_NOTE} if modified else {}), "steps": steps_out, "moves": mover.moves, "hiddenContext": len(hidden), "restored": True, "restoredWithinMm": round(drift, 6),+ "glyphs": glyphs.report(), "coordinates": "normalized to the image: x right, y down, 0..1"}
addin/AdomBridge/commands/mechanical/glyphs.pyadded+84@@ -0,0 +1,84 @@+"""Clean captures. A viewport image shows what the graphics window shows, so joint and joint origin+glyphs, sketches, origin planes, axes and points, and construction planes, axes and points appear in+every frame. The capture operations hide them through the design's Object Visibility settings (the+Display Settings > Object Visibility menu; Design.objectVisibility in the API) and put back exactly+the switches they turned off. Fusion's local renderer never draws them, but Viewport.fit() counts+them, so render_image hides them only while it fits a standard view.++Measured on Fusion 2705.1.30 against the other routes:+- On a clean new design, all ten switches off and back on left isModified false. One folder light bulb+ (Component.isJointsFolderLightBulbOn and the rest) set it, and turning the bulb back did not clear it;+ the API cannot clear that flag.+- Object Visibility costs one setter per switch (ten in 0.3 ms) whatever the size of the design. The+ folder light bulbs cost a native call per folder per component: 48 toggles at 6.9 ms each on 23+ components, and a light-bulb toggle costs about 26 ms on a 4,579-occurrence assembly.+- Both routes remove the same pixels (0 differ). Entity light bulbs miss the origin geometry, and+ Design.setLightBulbOn refuses joints."""++# The Object Visibility switches a clean capture turns off, with the names replies use.+SWITCHES = (("isJointsVisible", "joints"), ("isJointOriginsVisible", "jointOrigins"),+ ("isJointOriginAxesVisible", "jointOriginAxes"), ("isSketchesVisible", "sketches"),+ ("isOriginPlanesVisible", "originPlanes"), ("isOriginAxesVisible", "originAxes"),+ ("isOriginPointsVisible", "originPoints"), ("isUserWorkPlanesVisible", "constructionPlanes"),+ ("isUserWorkAxesVisible", "constructionAxes"), ("isUserWorkPointsVisible", "constructionPoints"))+UNAVAILABLE = "This Fusion has no Object Visibility API (Design.objectVisibility), so glyphs show as the design shows them."+++class Glyphs:+ """Hide the glyphs a design shows for a capture, then put every switch back as it was.++ show=True (the caller's `glyphs: true`) leaves everything as the design shows it. hide() turns off+ only the switches that are on. restore() never raises and may be called more than once: it runs+ from the capture's `finally` beside the camera and pose restores, and records any switch that did+ not come back so the operation can refuse with restore_failed after its other restores have run."""++ def __init__(self, design, show=False):+ self.design, self.show = design, bool(show)+ self.settings, self.before, self.hidden, self.failed, self.back = None, {}, [], [], False++ def hide(self):+ if self.show:+ return self+ self.settings = getattr(self.design, "objectVisibility", None)+ if self.settings is None:+ return self+ self.before = {attr: bool(getattr(self.settings, attr)) for attr, _ in SWITCHES}+ for attr, name in SWITCHES:+ if self.before[attr]:+ setattr(self.settings, attr, False)+ self.hidden.append((attr, name))+ return self++ def restore(self):+ """Turn back on what hide() turned off, then check every switch against its state before."""+ if not self.hidden or self.back:+ return True+ failed = []+ for attr, name in self.hidden:+ try:+ setattr(self.settings, attr, True)+ except Exception:+ pass+ for attr, name in SWITCHES:+ try:+ if bool(getattr(self.settings, attr)) != self.before[attr]:+ setattr(self.settings, attr, self.before[attr])+ ok = bool(getattr(self.settings, attr)) == self.before[attr]+ except Exception:+ ok = False+ if not ok:+ failed.append(name)+ self.failed, self.back = failed, not failed+ return self.back++ @property+ def restored(self):+ return not self.failed++ def report(self):+ """The reply's `glyphs`: whether they were left showing, which kinds were hidden, and that every+ switch is back."""+ out = {"shown": self.show or self.settings is None, "hidden": [name for _, name in self.hidden], "restored": not self.failed}+ if not self.show and self.settings is None:+ out["note"] = UNAVAILABLE+ return out
addin/AdomBridge/commands/mechanical/render_image.py+9−1@@ -10,6 +10,7 @@ import adsk.core import adsk.fusion from .core import SESSION_ID, require, target+from .glyphs import Glyphs VIEWS = {"iso": "IsoTopRightViewOrientation", "iso-left": "IsoTopLeftViewOrientation", "front": "FrontViewOrientation", "back": "BackViewOrientation", "top": "TopViewOrientation", "bottom": "BottomViewOrientation",@@ -38,6 +39,7 @@ def render_image(app, args): "isBackgroundTransparent": rendering.isBackgroundTransparent} saved_ground = scene.isGroundDisplayed camera = viewport.camera+ glyphs = Glyphs(design) started = time.perf_counter() try: rendering.aspectRatio = adsk.fusion.RenderAspectRatios.CustomRenderAspectRatio@@ -49,11 +51,15 @@ def render_image(app, args): scene.isGroundDisplayed = bool(args["ground"]) view = args.get("view", "iso") if view != "current":+ # the renderer never draws joint, sketch or construction glyphs, but the fit counts them:+ # hidden while fitting, the view frames the parts, and they are back before the render+ glyphs.hide() framed = viewport.camera framed.isSmoothTransition = False framed.viewOrientation = getattr(adsk.core.ViewOrientations, VIEWS[view]) viewport.camera = framed viewport.fit()+ glyphs.restore() viewport.refresh() future = rendering.startLocalRender(str(destination)) require(future is not None, "render_failed", "Fusion did not start the render.")@@ -67,17 +73,19 @@ def render_image(app, args): time.sleep(0.2) require(future.renderState == states.FinishedLocalRenderState, "render_failed", "Fusion's renderer reported a failure.") finally:+ glyphs.restore() for name, setting in saved.items(): setattr(rendering, name, setting) scene.isGroundDisplayed = saved_ground camera.isSmoothTransition = False viewport.camera = camera viewport.refresh()+ require(glyphs.restored, "restore_failed", "Object Visibility switches did not come back after rendering: %s." % ", ".join(glyphs.failed)) require(destination.exists() and destination.stat().st_size > 0, "render_failed", "The rendered image was not written.") return {"target": value, "filename": str(destination), "bytes": destination.stat().st_size, "size": [future.imageWidth, future.imageHeight], "seconds": round(time.perf_counter() - started, 1), "view": args.get("view", "iso"), "quality": int(args.get("quality", 75)),- "settingsRestored": True, "documentFlagsUnchanged": (document.isSaved, document.isModified) == flags}+ "settingsRestored": True, "glyphsHiddenForFit": glyphs.report()["hidden"], "documentFlagsUnchanged": (document.isSaved, document.isModified) == flags} OPERATIONS = {"render_image": render_image}
addin/AdomBridge/commands/mechanical/schema.json+11−3@@ -7468,9 +7468,13 @@ "fit": { "type": "boolean", "description": "Fit the camera to the most exploded state first (default true); false keeps the current view."+ },+ "glyphs": {+ "type": "boolean",+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified)." } },- "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state), then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export)."+ "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state) with joint, sketch and construction glyphs hidden unless glyphs:true, then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export)." }, "exploded_view_stills": { "type": "object",@@ -7613,13 +7617,17 @@ "keep" ], "description": "auto (default): when every moved part sits in one subassembly, everything outside it is moved out of view in every image (and back afterwards); keep: show the whole design."+ },+ "glyphs": {+ "type": "boolean",+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified)." } },- "description": "Step images for assembly instructions, from one fixed orthographic camera: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status."+ "description": "Step images for assembly instructions, from one fixed orthographic camera, with joint, sketch and construction glyphs hidden unless glyphs:true: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status." }, "render_image": { "type": "object",- "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.",+ "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. The renderer never draws joint, sketch or construction glyphs; a standard view is fitted with them hidden, so it frames the parts. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.", "properties": { "operationId": { "type": "string"
contracts/describe.json+22−6@@ -5308,7 +5308,7 @@ "type": "object" }, "exploded_view_render": {- "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state), then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export).",+ "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state) with joint, sketch and construction glyphs hidden unless glyphs:true, then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export).", "properties": { "fit": { "description": "Fit the camera to the most exploded state first (default true); false keeps the current view.",@@ -5324,6 +5324,10 @@ "minimum": 1, "type": "integer" },+ "glyphs": {+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified).",+ "type": "boolean"+ }, "height": { "description": "Frame height in pixels (default 720).", "maximum": 2160,@@ -5464,7 +5468,7 @@ "type": "object" }, "exploded_view_stills": {- "description": "Step images for assembly instructions, from one fixed orthographic camera: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status.",+ "description": "Step images for assembly instructions, from one fixed orthographic camera, with joint, sketch and construction glyphs hidden unless glyphs:true: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status.", "properties": { "camera": { "description": "auto (default): the isometric view that best shows the parts coming in, framing the moved parts seated and exploded; fit: the current view direction, framing them; current: the view as it is. Always orthographic.",@@ -5487,6 +5491,10 @@ "description": "Absolute folder on the PC for step_000_after.png (nothing assembled) and step_NNN_before.png / step_NNN_after.png per step (created if missing).", "type": "string" },+ "glyphs": {+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified).",+ "type": "boolean"+ }, "height": { "description": "Frame height in pixels (default 720).", "maximum": 2160,@@ -10925,7 +10933,7 @@ "type": "object" }, "render_image": {- "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.",+ "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. The renderer never draws joint, sketch or construction glyphs; a standard view is fitted with them hidden, so it frames the parts. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.", "properties": { "filename": { "description": "Absolute .png or .jpg path on the PC.",@@ -13168,7 +13176,7 @@ "type": "object" }, "exploded_view_render": {- "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state), then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export).",+ "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state) with joint, sketch and construction glyphs hidden unless glyphs:true, then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export).", "properties": { "fit": { "description": "Fit the camera to the most exploded state first (default true); false keeps the current view.",@@ -13184,6 +13192,10 @@ "minimum": 1, "type": "integer" },+ "glyphs": {+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified).",+ "type": "boolean"+ }, "height": { "description": "Frame height in pixels (default 720).", "maximum": 2160,@@ -13324,7 +13336,7 @@ "type": "object" }, "exploded_view_stills": {- "description": "Step images for assembly instructions, from one fixed orthographic camera: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status.",+ "description": "Step images for assembly instructions, from one fixed orthographic camera, with joint, sketch and construction glyphs hidden unless glyphs:true: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status.", "properties": { "camera": { "description": "auto (default): the isometric view that best shows the parts coming in, framing the moved parts seated and exploded; fit: the current view direction, framing them; current: the view as it is. Always orthographic.",@@ -13347,6 +13359,10 @@ "description": "Absolute folder on the PC for step_000_after.png (nothing assembled) and step_NNN_before.png / step_NNN_after.png per step (created if missing).", "type": "string" },+ "glyphs": {+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified).",+ "type": "boolean"+ }, "height": { "description": "Frame height in pixels (default 720).", "maximum": 2160,@@ -18785,7 +18801,7 @@ "type": "object" }, "render_image": {- "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.",+ "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. The renderer never draws joint, sketch or construction glyphs; a standard view is fitted with them hidden, so it frames the parts. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.", "properties": { "filename": { "description": "Absolute .png or .jpg path on the PC.",
docs/MECHANICAL.md+11−3@@ -105,7 +105,7 @@ A [finite exploded-preview controller candidate](EXPLODED-PREVIEWS.md) now exist ## Render an exploded-view animation -`exploded_view_render` takes the same `unit`, `steps` and `playback` as `exploded_view_plan`, plus an absolute `folder`, `fps` (default 24), `width` and `height` (default 1280 x 720) and `holdSeconds` (default 1). It plays the steps on the real occurrences and saves one viewport image per frame, moving only the parts whose pose changes from one frame to the next (`moves` in the reply; each move costs about 44 ms on a 4,579-occurrence assembly). The camera is fitted to the most exploded state unless `fit:false` keeps the current view. Afterwards it reverts every move and verifies that each instance is home and that the document's saved flag is unchanged. Fusion marks a document modified whenever a part moves, even after the move is reverted, and its API cannot clear that flag (measured on a saved design), so the reply's `documentModified` says when the render set it; the parts are back where they were and there is nothing to save. It refuses a design that already has an uncaptured move, since the revert would discard it. It is journaled. The reply lists the frames, timed `captions` from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4; Fusion's API has no video export. A three-step check (screws out, lid off, housing slid off the base) rendered 133 frames, in both explode and assemble order, and was joined into captioned MP4. At the most exploded pose it checks that every part reached its planned pose and names any that did not in `heldBack` (with how far off), so a part a constraint holds never just stands still in the video; live, a rigidly jointed post, component-pattern copies, a nested child moved in its parent's frame and a rotated part moved along its own axis all reached their poses and came back home. Frames are the viewport, so joint glyphs and other visible decorations appear in them; hide them first for clean frames.+`exploded_view_render` takes the same `unit`, `steps` and `playback` as `exploded_view_plan`, plus an absolute `folder`, `fps` (default 24), `width` and `height` (default 1280 x 720) and `holdSeconds` (default 1). It plays the steps on the real occurrences and saves one viewport image per frame, moving only the parts whose pose changes from one frame to the next (`moves` in the reply; each move costs about 44 ms on a 4,579-occurrence assembly). The camera is fitted to the most exploded state unless `fit:false` keeps the current view. Afterwards it reverts every move and verifies that each instance is home and that the document's saved flag is unchanged. Fusion marks a document modified whenever a part moves, even after the move is reverted, and its API cannot clear that flag (measured on a saved design), so the reply's `documentModified` says when the render set it; the parts are back where they were and there is nothing to save. It refuses a design that already has an uncaptured move, since the revert would discard it. It is journaled. The reply lists the frames, timed `captions` from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4; Fusion's API has no video export. A three-step check (screws out, lid off, housing slid off the base) rendered 133 frames, in both explode and assemble order, and was joined into captioned MP4. At the most exploded pose it checks that every part reached its planned pose and names any that did not in `heldBack` (with how far off), so a part a constraint holds never just stands still in the video; live, a rigidly jointed post, component-pattern copies, a nested child moved in its parent's frame and a rotated part moved along its own axis all reached their poses and came back home. Frames show the parts without joint, sketch or construction glyphs unless `glyphs:true` (see [Clean captures](#clean-captures)). ## Render step images for assembly instructions @@ -124,11 +124,19 @@ A [finite exploded-preview controller candidate](EXPLODED-PREVIEWS.md) now exist `context` chooses what else shows. `auto` (the default): when every moved part sits in one subassembly, the occurrences outside it that are visible move out of view in every image, so a panel inside a big assembly appears on its own; parts at the root hide nothing, so a base part always stays. `keep` shows the whole design. -Fusion frames an orthographic image so that its shorter side spans the camera's `viewExtents`, centred on the target (measured at 1280 x 720, 960 x 960 and 720 x 1280, every point within a pixel); the projections in the reply follow that rule. It moves only the parts whose pose changes between images, about four moves per moved part in all. It takes up to 64 steps and 2048 moves (an animation takes 32 and 128) and tracks up to 4,096 moved parts with their parents. Afterwards it reverts every move, restores the camera, verifies every part is home and reports `documentModified` like the animation. It is journaled. Measured on a 4,579-occurrence assembly: an electronics panel of 4 steps and 92 parts gave 9 images in 40 s with 368 moves.+Fusion frames an orthographic image so that its shorter side spans the camera's `viewExtents`, centred on the target (measured at 1280 x 720, 960 x 960 and 720 x 1280, every point within a pixel); the projections in the reply follow that rule. It moves only the parts whose pose changes between images, about four moves per moved part in all. It takes up to 64 steps and 2048 moves (an animation takes 32 and 128) and tracks up to 4,096 moved parts with their parents. Like the animation, the images leave out joint, sketch and construction glyphs unless `glyphs:true` (see [Clean captures](#clean-captures)). Afterwards it reverts every move, restores the camera, verifies every part is home and reports `documentModified` like the animation. It is journaled. Measured on a 4,579-occurrence assembly: an electronics panel of 4 steps and 92 parts gave 9 images in 40 s with 368 moves. ## Render a picture -`render_image` renders a photoreal image with Fusion's local renderer to an absolute `.png` or `.jpg` `filename`. The `view` is `iso` by default, or `iso-left`, `front`, `back`, `top`, `bottom`, `left` or `right`, each fitted to the design; `current` keeps the view as it is. It takes `width` and `height` (1920 x 1080 by default), `quality` from 0 to 100 (75 by default), `transparent` and `ground`. It waits for the render, then restores the camera and every render setting it changed, and reports whether the document's saved and modified flags are unchanged. It is journaled, since a large high-quality render runs for minutes. Fusion's API lists render environments but cannot choose one, so the document's own is used. A 1280 x 720 iso render at quality 60 took 28 s. A transparent background needs a `.png`. Fusion's API cannot cancel a render, so after `render_timeout` the render keeps running in the background and may still write the file (seen live: a 1920 x 1080 quality 95 render timed out at 10 s and delivered the file later), while the settings and camera are already back.+`render_image` renders a photoreal image with Fusion's local renderer to an absolute `.png` or `.jpg` `filename`. The `view` is `iso` by default, or `iso-left`, `front`, `back`, `top`, `bottom`, `left` or `right`, each fitted to the design; `current` keeps the view as it is. It takes `width` and `height` (1920 x 1080 by default), `quality` from 0 to 100 (75 by default), `transparent` and `ground`. Fusion's renderer never draws joint, sketch or construction glyphs, but fitting a standard view counts them, so they are hidden while the view is fitted and back before the render starts (`glyphsHiddenForFit` in the reply; see [Clean captures](#clean-captures)). It waits for the render, then restores the camera and every render setting it changed, and reports whether the document's saved and modified flags are unchanged. It is journaled, since a large high-quality render runs for minutes. Fusion's API lists render environments but cannot choose one, so the document's own is used. A 1280 x 720 iso render at quality 60 took 28 s. A transparent background needs a `.png`. Fusion's API cannot cancel a render, so after `render_timeout` the render keeps running in the background and may still write the file (seen live: a 1920 x 1080 quality 95 render timed out at 10 s and delivered the file later), while the settings and camera are already back.++## Clean captures++A viewport image shows what the graphics window shows. So `exploded_view_render` and `exploded_view_stills` hide joint and joint origin glyphs, sketches, origin planes, axes and points, and construction planes, axes and points for every image. They turn these off in the design's Object Visibility (Display Settings > Object Visibility in the navigation bar; `Design.objectVisibility` in the API), then turn back on exactly the switches they turned off, after a finished capture, a failed image or a timeout alike. The animation's `fit` happens after the glyphs hide, so it frames the parts: on a test design with a sketch line and planes beside the parts, the fitted view spanned 12.2 cm instead of 23.3 cm. Pass `glyphs: true` to keep everything as the design shows it. The reply's `glyphs` lists the kinds that were hidden (`joints`, `jointOrigins`, `jointOriginAxes`, `sketches`, `originPlanes`, `originAxes`, `originPoints`, `constructionPlanes`, `constructionAxes`, `constructionPoints`) and says every switch is back; a switch that does not come back fails the operation with `restore_failed`, after the parts and the camera are home.++Fusion's local renderer never draws these glyphs (a render with every glyph showing matched one with them hidden to within a pixel), so `render_image` hides them only while it fits a standard view, and they are back before the render starts.++Object Visibility is a view setting. On a clean new design, all ten switches off and back on left the document unmodified, so hiding glyphs adds nothing to `documentModified`. It costs ten setters, about 0.3 ms, whatever the size of the design. The folder light bulbs on each component (`isJointsFolderLightBulbOn`, `isSketchFolderLightBulbOn` and the rest) hide the same pixels, but one toggle marks the document modified, turning it back does not clear that, and the API cannot clear it. They also cost a native call per folder per component: 48 toggles at 6.9 ms each on a 23-component design, and a light-bulb toggle costs about 26 ms on a 4,579-occurrence assembly. `Design.setLightBulbOn` refuses joints. Object Visibility has no switch for canvases, decals or section analyses, so those stay as the design shows them. ## Inspect render settings and prepare a detached camera
rust/core/tests/golden/mech_capabilities.json+11−3@@ -7483,9 +7483,13 @@ "fit": { "type": "boolean", "description": "Fit the camera to the most exploded state first (default true); false keeps the current view."+ },+ "glyphs": {+ "type": "boolean",+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified)." } },- "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state), then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export)."+ "description": "Render an exploded-view animation for operators: play the same steps as exploded_view_plan on the real occurrences, save one viewport image per frame from a fixed camera (fitted to the most exploded state) with joint, sketch and construction glyphs hidden unless glyphs:true, then revert every move and verify each instance is back home and the document state unchanged. Refuses a design with an uncaptured move. Journaled; follow a long render with fusion_mech_operation_status. Returns the frame folder, timed captions from the steps' titles and instructions, and the ffmpeg line that joins the frames into MP4 (Fusion's API has no video export)." }, "exploded_view_stills": { "type": "object",@@ -7628,13 +7632,17 @@ "keep" ], "description": "auto (default): when every moved part sits in one subassembly, everything outside it is moved out of view in every image (and back afterwards); keep: show the whole design."+ },+ "glyphs": {+ "type": "boolean",+ "description": "Keep joint, joint origin, sketch, origin and construction glyphs in the images, as the design shows them (default false: they are hidden through Object Visibility for the capture and every switch is put back; the document is not marked modified)." } },- "description": "Step images for assembly instructions, from one fixed orthographic camera: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status."+ "description": "Step images for assembly instructions, from one fixed orthographic camera, with joint, sketch and construction glyphs hidden unless glyphs:true: per step, in assembly order, the step's parts hovering where they go with earlier steps seated and later steps out of view (before), then seated (after); step_000_after.png shows nothing assembled, so consecutive images differ by exactly the parts that came in. Returns each moved part's box and centre in both images, normalized to the image, and the camera. Moves only the parts whose pose changes, reverts every move, restores the camera and verifies each instance is back home. Up to 64 steps and 2048 moves. Journaled; follow a long run with fusion_mech_operation_status." }, "render_image": { "type": "object",- "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.",+ "description": "Render a photoreal image with Fusion's local renderer: from a standard view fitted to the design (iso by default) or the current view, at a size and quality (0 to 100; 75 by default), optionally on a transparent background or without the ground. The renderer never draws joint, sketch or construction glyphs; a standard view is fitted with them hidden, so it frames the parts. Waits for the render, then restores the camera and every render setting it changed. Journaled: a large, high-quality render runs for minutes, so follow it with fusion_mech_operation_status. Fusion's API cannot choose the render environment; the document's own is used.", "properties": { "operationId": { "type": "string"
rust/core/tests/golden/mech_validation.json@@ -1 +1 @@