| run_topoptA | Start a topology optimization run in the background. Returns at once. Use after check_problem and after you state your assumptions to the user. If the user gave no load (no object, no mass and no force), ask first. Then run_status to watch it, and run_result when it is done.
Nothing calls you back when the run ends: call run_status in this turn, or end the turn and tell the user what to ask.
It also returns assumptions (the strings of the YAML) and derived (forces in N, voxel size, minimum feature size, volume fraction), so you can show them.
Reply keys: resource topopt://replies/run_topopt (the meaning of each key, and the fine pass: fine_from, fine_pass). yaml_path: the problem YAML (relative paths start at the work folder; environment shows it).
out_dir: the run folder. Default: runs/live in the work folder (the dashboard shows it). Without resume, a folder that holds the checkpoint of a run that did not finish loses it: the reply then has earlier_checkpoint (the pass and a note), and the run goes on.
override_lock: a live process can hold the job lock of the run folder (the reply names its pid and start time). Give true to run anyway. A lock of a stopped process is replaced without it.
proceed_without_brief: a YAML with a case file (made by record_brief) can have no brief, or a stale brief (a changed STL file or requirement key). Then the reply is {"state": "awaiting_user", "facts", "options", "override"} and nothing starts. Give true to run anyway: the case log records it.
resume: continue the run of out_dir from its last checkpoint (run_status shows checkpoint). The history and the snapshots after it are dropped. The YAML and the region files are read again: if they or the settings changed, the reply is an error with the changes in resume.facts, and nothing is written. A run that stop_run stopped is continued the same way, with no allow_changed_inputs.
continue_from: add passes to a run in a NEW run folder (out_dir); this folder is the parent and is only read. more_iterations (with continue_from): the number of passes to add (a whole number of 1 or more, no upper limit). The new run starts from the last checkpoint of the parent (pass K) and has K + more_iterations passes (settings.iterations is not used). The reply has continue. The card run_topopt:continue_from has the rest.
allow_changed_inputs: with resume or continue_from, go on with the changed inputs. The run log and the verdict name the changes. With resume=true the stop of proceed_without_brief names both overrides in one option (this one and allow_changed_inputs=true). On a finished run a resume runs the post step only: a key that only it reads (for example settings.mesh_method) takes effect in seconds; a key of the passes is accepted, the part stays, and the reply has resume (with pass_keys).
stop_rules: true turns the stop rules on, false turns them off. None: as settings.stop_rules of the YAML says (off by default). A rule ends the run at a pass that only measures. The verdict names the rule and its numbers.
With resume, a run that was killed gives the same result as one that was not (bit-equal with one thread and a direct solver).
The lost force is in check_problem (before) and in run_result or run.json (after). A moment, a remote force, a pressure or a per-axis support gives extended in check_problem; the stiffness of such a case is null with a reason. Who decides a change of the YAML: a method setting (penal, beta schedule, solver, voxel size, iterations, a voxel guard: the memory estimate against the RAM first, above it the machine can freeze) you change without asking, and report what, why and the price. A user requirement (loads, supports, keep-outs, material, stress limits and safety factors, a mass target, print limits, symmetry) you may tighten without asking, with a report and its measured price; you loosen it only with the user's consent, and you bring the facts. A key that can be either follows its origin in record_brief. Cards: topopt://levers. |
| run_statusA | Show the state of a run: status, last iteration, and a one-line summary. Call it again to watch a run. Each call returns at once. Nothing calls you back when the run ends: call it again in this turn until the state is done, error or stopped, or process_alive is false, or end the turn and tell the user what to ask for the result. A promise of a later report cannot be kept. The numbers are facts; you decide what to do next.
run_dir: the run folder. Default: the last run that this server started.
job has the facts of the run process: state (running, done, stopped, failed with its exit code, or stale_lock when the process stopped without a marker), wall time and peak memory. history_facts names lines that were skipped.
state is stopped for a run that a request to stop (stop_run, topopt stop) ended at the end of a pass: the checkpoint is saved, there is no result/, and run_topopt with resume=true continues it. A process of this server can still be alive after that: it only serves the dashboard (process_alive true, job.state stopped). stop_request is the request in the run folder (null if none): while the job is running it means the run stops at the end of its current pass.
checkpoint (null if the folder has none, or the state is starting, error before any data, or none) gives the pass of the last checkpoint, complete (the run reached its last pass or a stop rule) and yaml_path: run_topopt with resume=true continues a run that stopped before its last pass.
verdict (null until the run ends) has the state (converged, iteration_limit, stopped_by_rule, stopped_by_request, unfinished), whether the constraint rows are met (feasible), the measures with their thresholds, the stop rule that fired, and the resume points. The same verdict is in metrics.json (run_result).
verdict.facts has the facts of the last pass, and the verdict text ends with "Facts: ..." for each fact that the run has: the volume used (the projected design, and the thresholded part at density 0.5; only with a volume row); the load path (which load regions the thresholded part does not join to a fixed, spring or joint region; in the text only when a region is not joined); for a run with objective mass, the material (volume and mass of the thresholded part; only with no volume row) and, when the grayness is above grayness_max, the grey design (volume and body count of the thresholded part at the cuts 0.4, 0.5 and 0.6, and the volume spread between 0.4 and 0.6 as a share of the volume at 0.5). They are facts: no test uses them. The run log has them at the end of each beta stage. The body count of final.stl comes after the post step: metrics.grey_design (see run_result).
A run that ended with no result also has log_tail, failure (facts: pid, exit code, signal, run data, whether this server stopped it,
peak memory, estimate, RAM), options and, after a signal or an unseen end with no message, possible_causes (not a verdict).
fine_run (the first folder of run_topopt with fine_pass=true): the folder and the state of the fine pass.
time_left (while the run is in its passes, from pass 2 of the run process) has left_s and up_to_s (seconds left if no stop rule or request to stop ends the run earlier) and basis. Else it is null, with the reason in time_left_note. |
| run_resultA | Get the final metrics, the path of final.stl and a text preview when the run is done. Tell the user the result: mass, stiffness, run time. Units: mm, N, MPa, g.
A run that a request to stop ended has no result: the reply is done: false and stopped: true with the pass. run_dir: the run folder. Default: the last run that this server started.
preview: add mesh facts and ASCII silhouettes of final.stl (for clients that cannot open files). render_part gives real images of the part.
Reply keys: resource topopt://replies/run_result (the meaning of each key).
A note names a wall thinner than the voxel size. The thinnest wall (thinnest_wall_mm) is the lower end of the film, not a value to use.
The setting settings.part_only_min_film_mm (the wall thickness to cut off, in mm; default off) cuts off that thin film; then part_only also has min_film_mm, film_removed_mm3 and, for each body, thin_area_mm2, cut_area_mm2, thin_area_after_mm2 and the thinnest wall that stays. To apply it to a run that has finished: write the YAML with the value as a NEW file in the folder of the old YAML (region paths start there), then run_topopt with that YAML, the run folder as out_dir, resume=true and allow_changed_inputs=true (the post step repeats; copy the run folder first to keep the old result). Read the three areas and change the value if the film is still there.
For a run with a stress limit, "met" of metrics.stress.cases.<case>.relaxed is the relaxed measure of the optimizer, not the stress of the part. summary and independent_check of the case give the P2 p99.9 of verify_run and its ratio to the limit when verify has run on the folder (read again at each call), else the option to run it. |
| stop_runA | Ask a running run to stop at the end of its current pass. Returns at once; sends no signal; waits for nothing. The tool writes the file job.stop in the run folder. The run then ends with exit code 5. There is no post step and no result/. The history, the snapshots and the checkpoint are those of a run that goes on, so run_topopt with resume=true (no allow_changed_inputs) gives the same result as a run that was not stopped.
run_dir: the run folder. Default: the run that this server started and that still works; else the one run in the work folder (or its runs/ folder) that a live process holds the job lock of (this finds a run that the CLI started). If several runs are live, the reply lists them and writes nothing: give run_dir.
The reply has state:
stop_requested: the request is written. pid, iteration (the pass at the last status), message, options.
already_requested: a request is in the folder already (stop_request has its time); it is not written again.
postprocess: the run is in the post step or the refine step: all passes are done, nothing is written. Options: wait, or end the process yourself (resume=true then repeats the post step only).
nothing_to_stop: the run is done, stopped or failed already: nothing is written. process_alive says whether a process of this server is alive and only serves the dashboard. options has the resume route where a checkpoint is there.
gone: a lock whose process is not running, or a record that says running and has no lock: nothing is written. checkpoint and the option resume=true (it replaces the stale lock, with a fact). Also the run process of this server that ended before it took the job lock: the message has its exit and the log file.
none: no run in this folder: no job files and no live process, or the folder does not exist, or a live job lock of a job that is not a topopt run (the message names the marker, for example ratchet.json). Nothing is written.
several: several live runs: the list is in runs.
Every reply has note: a topopt build from before this change does not read job.stop, and a run of such a build ignores the request. found_by says how the run was found (run_dir, this server, the lock scan).
The run reads job.stop after the checkpoint of each pass (one file read per pass, no polling). It then saves its verdict (state stopped_by_request), writes the status stopped and the job marker job.STOPPED, and releases the job lock. A pass that is long (a big grid, a buckling row) delays the stop by up to one pass. A solve that hangs never reads the request: the reply has the pid.
With run_topopt fine_pass=true and no run_dir, the fine pass is stopped when it runs; while the first run works, that run is stopped and the fine pass does not start. From the post step of the first run until the fine pass has started, the request is written in the fine folder (state stop_requested, run_dir the fine folder): the process reads it before the fine pass starts, ends without starting it (exit code 5) and says so in the log.
Only run folders count for run_dir: a folder with status.json or run.json, as in list_runs. The folder of a ratchet job holds a job lock too, and it is not a run. |
| render_partA | Render a part to PNG images so you can look at it. No display or GL is needed (CONTRACTS.md section 15). A look before the first run and after each run finds a wrong region or load fast. Input: stl_paths (STL files; roles: one role for all or one per file, from part, design, fixed, load, keep-out; default part; an item may be a comma list), or run_dir (renders result/final.stl and the region STLs that run.json names; missing region files are listed in the facts), or yaml_path (a problem YAML, before a run: the region STLs and the interface shapes. Fixed, load and union shapes are solid. The design space, keep-out regions and cut shapes are ghosts. A face is its cut zone, clipped to the grid box. Look at it before a run: it shows what a cut takes). With none of them, the last run is used. A YAML render also draws the force arrows (one for each force of each load case, a color for each case, along the force, ending where it acts): you can show it to the user to check the directions before the run.
The answer is a JSON text and, when inline is true, the montage as one image. With inline false the answer is the text only; the PNG files are on disk and a client that can open files reads them.
A 4-view render of an 85,000-triangle part takes about 2 to 4 s; the first call with an empty numba cache adds under 1 s. Later renders overwrite PNGs with the same names; out_dir picks another folder.
out_dir: default /renders, or renders beside the first STL.
max_triangles: refuse meshes with more triangles than this (default: no limit; the facts give a memory estimate, about 450 MB plus 450 MB per million triangles).
Reply keys: resource topopt://replies/render_part (the meaning of each key). The facts of each mesh have watertight and winding_consistent.
loads: false draws no force arrow in a YAML render (default: arrows). A moment and a pressure get no arrow, and the facts say so. The arrow length is a share of the part size: it does not show the force size.
views: names from iso, front, back, right, left, top, bottom (default iso, front, right, top); NAME:dx,dy,dz gives your own direction from the part to the camera, z up (name: letters, digits, _, -).
section: "x=30" (or "x>=30") keeps x >= 30; "x<=30" keeps x <= 30; the axis is x, y or z, in mm.
regions: how the non-part meshes are drawn. ghost (default with a part): design and keep-out as faint ghosts, fixed and load as strong ghosts, so the part stays visible. solid (default without a part). off: the part only.
size: one tile in pixels, WxH (default 640x480). The montage is at most 1568 px on the long edge. All tiles have one scale; a scale bar shows it.
In views, one item may be "fl:-0.5,-1,0.25". A text like "iso,fl:-0.5,-1,0.25,top" in one item is read as three views.
Cut faces are flat orange. With a section and no views, the views are: cut (straight at the cut face from the removed side), iso-cut, and two side views of the kept part. The facts give cut_face_visible_px for each view; they say if a mesh could not be cut (for example, it is not a closed solid), and the render is made anyway. Colors: part grey-blue, design space light blue, fixed dark blue, load red, keep-out brown, cut face orange; for a YAML also union shape green and cut shape (a ghost) pink-red. Back faces are magenta: they show a flipped or open surface. The same input gives the same PNG bytes. |
| list_runsA | List the run folders in the work folder (newest first), with state and iteration. A run folder is any folder in the work folder, or in its runs/ folder, that holds
status.json or run.json. Paths are relative to the work folder. |
| run_historyA | Facts of the earlier runs under a folder, newest first, read from the files on disk. Writes nothing. Use it to see what was tried: state, the YAML of each run, key settings, main metrics. A missing or partial file
(no metrics.json, a cut last history line, a broken status.json) is a fact in problems, not an error.
CONTRACTS.md section 21. root: the folder to search (relative paths start at the work folder). Default: the work folder.
depth: how many levels of folders to search (default 2: runs/ under the work folder; at most 5). A run
folder holds run.json or status.json. A root that is a run folder gives that run. The scan stops after 5000
folders and says so.
limit: the newest runs only (default 20). count is the number of runs found, and a note says when older runs
are left out. 0 gives all runs.
detail: the full facts of each run (all settings, all metrics, which files exist).
Each run: name, run_dir, path (relative), state, iteration, iterations, process_alive, modified, yaml_path,
yaml_exists, yaml_newer_than_run (the YAML file is newer than the run: it may not be the one that ran), settings
(voxel_size_mm, volume_fraction, iterations, solver, filter_radius_mm, n_active, load case names), metrics
(compliance_initial and compliance_final, mass_final_g, volume_final_mm3, volume_fraction_final, run_time_s),
metrics_source and problems. list_runs is the cheaper list (it reads no metrics). |
| compare_runsA | Runs side by side: two runs (setup diff and metrics diff), or a base with its variants (one table). Reads files, writes nothing. Give one form. (run_a, run_b): A is the base and B the change; delta is B - A. Every value names its source: run.json (what each
run recorded at its start: the YAML path and the region file names, no hash), labels.bin, the YAML and STL files on disk (read now),
and result/metrics.json. So the files on disk can be newer than the run or can differ from it; inputs says so as facts (a newer
YAML or STL, YAML values that differ from run.json, labels from the files on disk that differ from labels.bin). The facts are evidence, not proof.
(run_a, variants): run_a is the base; variants is a list of run folders, any number. One row per run, the base first. run_a: the base run folder (relative paths start at the work folder). run_b: the changed run folder (two-run form).
variants: the run folders of the table (table form); not together with run_b.
check_labels: voxelize the YAML on disk and compare it with labels.bin (a few seconds on a big grid).
null: do it when the grid has at most 10 million voxels. true: any grid size. false: skip it.
Two-run keys: a, b, setup (run_json_diff, yaml_diff, region_files with the STL hashes, labels_diff: the voxel classes that changed,
with the box in mm), metrics (rows with a, b, delta, delta_pct; stiffness per case; filter facts), inputs, notes (for example: the
load cases differ, so compliance is not comparable), summary and problems.
Table keys: base; runs (state, mass_final_g, mass_saved_pct, stiffness per load case from the optimizer, verdict, verify: done,
not run or no result, print: a few print facts, render: the path of the newest montage PNG on disk, or null); vs_base (the setup
diff of each variant against the base); notes; summary; problems. A fact that no file holds is null and an entry in problems.
The table makes no image: render_part does.
On a wrong call or folder: {"ok": false, "error": ...}. |
| marks_to_hintsA | Turn the marks of the Refine page into facts, options and a proposed YAML in a NEW folder. Edits no mesh. A mark is a point of emphasis, not an absolute. The report gives, for each mark, the facts (position, radius, distance to the surface of final.stl, voxels touched, overlap with the design, fixed, load and keep-out regions, whether the region touches the material of final.stl) and the options for its type, and says which option the proposed YAML uses. The agent decides: read the report, change options and call again, edit the YAML, or run it with run_topopt.
The tool writes only into the new folder. It refuses an out_dir that is a run folder or inside one, one that holds an input file of the run (the folder of the YAML or of a region STL), a symbolic link, and a folder with files that is not an earlier hints folder; the reply names the fact and the options.
After it: check the report, then run_topopt with the proposed YAML and a new out_dir, then compare_runs.
Reply keys: resource topopt://replies/marks_to_hints (the meaning of each key). marks: the path of marks.json (the Refine page exports it: version 1, mm, the frame of final.stl), its JSON text ("Copy JSON" on the page) or the marks object.
run_dir: the run the marks belong to. Default: the last run of this server. Needs run.json and the YAML it names.
out_dir: the new folder. Default: -hints beside the run folder. It holds the proposed YAML (the YAML of the run, with the changes), the region STLs (the originals are copied, the hints are new) and hints.json (the full report; its paths are relative to the folder).
detail: the full report in the reply (about 1.5 KB for each mark). Default: a short reply; hints.json has the full report.
options: remove ("keep_out" or "skip"), add ("solid" or "skip"), radius_scale (default 1.0; 0.5 gives smaller regions), protect_regions (default true: fixed and load regions are cut out of a keep-out hint), clip_to_design (default true: a solid hint is cut to the design space), per_mark ({mark id: option}).
overwrite: write again into a folder that holds an earlier hints.json.
yaml_path: the YAML of the run, when run.json names a file that is not there (a moved run). The labels check in the report says if it fits the run.
The input YAML, the run folder and the region STLs of the run stay as they are; the new region STLs touch the grid only inside the box of the input regions.
Mapping in this version: remove -> a keep-out STL (sphere, or capsule chain for a stroke) of radius radius_mm x radius_scale; add -> a forced-solid region (a load region that no load case uses); smooth -> facts and options only, because the YAML has no key that smooths one place. mirror repeats the mark at y -> -y. normals, amount_mm, strength and note are not used. The marks are read in file order and the first mark wins an overlap: a later hint region is cut by the earlier ones (a mark inside an earlier one has status no_effect, with the reason). |
| apply_marksA | Edit the finished part at the marks of the Refine page, in a NEW folder: facts and options, no verdict. The run folder and final.stl stay as they are. No FE run is made: the stiffness and stress of the edited part are not known. Order: remove, then smooth, then add. A step that gives a part that is not closed, or that splits it, is rolled back, and the reply says why. The fixed, load and keep-out regions are frozen: an edit within frozen_keep_mm of them is clipped, and the clip is a fact.
Reply keys: resource topopt://replies/apply_marks (the meaning of each key).
marks: the path of marks.json (the Refine page exports it: version 1, mm, the frame of final.stl), its JSON text, or the marks object. radius_mm, amount_mm (the neck offset of add), strength (smooth) and mirror are used; a value that the page does not record is the pipeline value, named in defaults_used. A mark may carry smooth_mode or remove_mode (cleanup_list writes them); the order is per_mark, the mark, then the call option.
run_dir: the run the marks belong to. Default: the last run of this server. Needs run.json, labels.bin and result/final.stl.
out_dir: the new folder. Default: -marks beside the run folder. It gets edited.stl and apply_marks.json (the full report). Refused: a run folder or a folder inside one, a folder that holds an input file of the run, a symbolic link, a folder with files that is not an earlier apply_marks folder.
allow_stale: apply the marks when source_sha256 of the marks file is not the hash of final.stl. Default: the reply states the fact (marks_match_final_stl false) and nothing changes.
options: radius_scale (1.0), pitch_mm (node spacing of the crop box; default voxel size / 4, between 0.2 and 0.5), max_crop_voxels (8 million, a guard: above it the reply gives the facts and the options, e.g. pitch_mm).
overwrite: write again into an earlier apply_marks folder.
detail: the full report in the reply. Default: a short reply; apply_marks.json has the full report.
yaml_path: the YAML of the run when run.json names a file that is not there; used for the exact frozen-region check.
CONTRACTS.md section 29. Each mark is one local edit of result/final.stl on a signed distance field in a crop box around the mark, with the method of the pipeline stage s7_cleanup: remove = a cut that protects the parent (then the parent check), add = a closing with a ball (a neck offset when the closing adds little), smooth = an isotropic fairing for a point and a web fairing for a stroke (smooth_mode tube rebuilds a whole strut: it does not use radius_mm, and the reply gives its planned volume and reach). The new surface is spliced into the mesh in a zone around the edit; the triangles outside the zones stay bit for bit.
More keys of options: max_total_voxels, frozen_keep_mm (3), frozen_full_mm (6), close_mm (3), neck_min_mm3, parent_tol_mm (0.2), parent_check ("report" or "enforce"), smooth_mode ("auto", "tube", "iso", "fair"), remove_mode ("cut" or "shave"), mirror_plane ({axis, at_mm}; else the plane of the mark or of run.json, else y = 0), skip ([mark ids]), per_mark ({id: {radius_mm, amount_mm, strength, mirror, smooth_mode, remove_mode, skip}}), check_frozen_exact, loose_body_mm3, max_odd_rays. |
| check_problemA | Load and voxelize a problem YAML (no optimizer run; with settings.solver auto it runs the short self-test of gpu-mg in a child process, at most 60 s: seconds the first time on a device and driver, then the cached pass). Returns facts, estimates and warnings. Use it before run_topopt, also for your own YAML and STLs. Read the facts and tell the user the numbers that matter.
Resource topopt://levers: the key for a symptom of these facts, and the price of a try.
yaml_path: the problem YAML (relative paths start at the work folder).
Another solver: settings.solver (auto, cholesky, pardiso, amg, gpu-mg) or --solver of topopt run and check, not of run_topopt. For gpu-mg, settings.gpu_device (auto, a number from topopt check, or a part of the name).
Warnings are facts; a warning that starts with "settings.solver is" (the solver cannot run here) means run_topopt will stop with the same message. A warning that starts with "fact: settings.iterations" says that the passes end before the last stage of settings.beta_schedule (a grey design): it gives the options (iterations, or the schedule scaled to the passes) and the measured price. It does not block the run. On a wrong input: {"ok": false, "error": ...}.
Reply keys: resource topopt://replies/check_problem (the meaning of each key).
A settings.perimeter_max_mm2 below the smallest possible perimeter gives {"ok": false, "error": ...} with the options.
The reply key lost_force has, for each load case, the total force, the force on nodes that are also fixed (the solver drops it) and the lost percent.
Key fine_pass: the voxel that the part needs (voxel_mm, one value; recommend; limiting: the feature that sets it, its thickness and the voxels across now and needed; and with recommend the fact line). run_topopt with fine_pass=true or fine_from runs a fine pass. |
| verify_runA | Check the final STL of a finished run with quadratic tets (P2): von Mises stress per load case. It is independent of the voxel optimizer: it meshes result/final.stl (part_only.stl with part_only) with tets (gmsh), clamps each fixed region (a joint or spring holds the part too), loads each load region with a traction (uniform per area, the resultant is the YAML force), and solves each load case alone (no case weights). Units: mm, N, MPa. estimate_only: default True. It returns the estimates for each mesh size (memory in bytes, time in s per measured solver), the solver that would run, the extras and the RAM, and meshes nothing. Tell the user the numbers, then call again with estimate_only=false.
A real run starts in the background and returns at once; verify_status shows it and gives the result. One verify job or optimizer run at a time: a second start is refused.
allowable_mpa, percentile: your allowable stress, no default (ranges: topopt://materials/reference). It adds a limit block. exit_code is 3 if the percentile value (by volume, default 99.9) of a case is above it. With gate it replaces the YAML stress limits (a larger value loosens them: the user's consent).
run_dir: the run folder (default: the last run that this server started). It needs run.json and result/final.stl.
gate: true adds the key gate (met per check and case; exit_code 3 if it fails); limits: the YAML's, see topopt://levers/verify_run:gate
max_gb: refuse above this memory estimate in GB; it also refuses above the available RAM. force: run anyway.
On a wrong input or a run that cannot go on: {"ok": false, "error": ..., "exit_code": 1 or 2, "facts": ...} (exit_code 1: bad input, an unsolvable part, no usable solver; exit_code 2: a missing gmsh or scikit-fem, a meshing failure, a memory refusal, an empty region).
p99.9 is the main number; the peak can move with the mesh at sharp features. A load patch can hold a large part of the top volume: read near_regions and load_band_sensitivity.
mesh_size_mm: one mesh size. Default: the tet mesher size, about 1.3 to 2.2 voxels for an sdf STL.
levels_mm: several sizes; the finest is the reference level; the result gets a levels block.
region_band_mm: a mesh node belongs to a region when it is inside the region STL or within this band of its surface. Default: 0.5 voxel (counts_by_band: the counts at 0, 0.5 and 1 band).
solver: auto (default: cholesky on macOS arm64, pardiso, cholespy, superlu) or a factor name. The result names the one that ran and those auto passed.
yaml_path: the problem YAML, if the one that run.json names is gone (moved folder).
near_region_mm: distance from a fixed or load region for the stress numbers without the region patches.
load_sensitivity: also solve the load at bands of 0 and 2 times the band.
part_only: mesh result/part_only.stl (the run needs regions.not_printed); a removed body becomes a boundary condition at its contact face, see topopt://levers/verify_run:part_only. Key: part_only. Result keys (verify_status, when done): ok, exit_code (0 done, 3 limit failed), cases..von_mises.p99_9_by_volume_MPa. All other keys, and the keys that YAML blocks add (buckling.check, limits rows, bore_checks): topopt://levers/verify_run |
| verify_statusA | Show the state of a verify job and, when it is done, its result (the content of verify.json). run_dir: the run folder (default: the folder of the last verify job of this server, else the last run).
Call it again to watch the job. Nothing calls you back when the job ends: call it again in this turn until the state is done, error or failed, or end the turn and tell the user what to ask for the result. A promise of a later report cannot be kept. States: starting, meshing, solving, writing (the job works), done,
error, failed (the process ended with no final status; log_tail has its last output) and none (no job
and no result in the folder). It has elapsed_s and estimated_seconds_total (the estimate of verify_run).
result: with a done job, add the result (set false to get the state only).
result_matches_final_stl says whether the result is for the mesh file that the folder has now (result.run.verified_file:
nominal.stl when the run has machining stock, else final.stl; part_only.stl after verify_run with part_only=true,
and then the result has the block part_only). If it is false, the result belongs to an older file: run verify_run again. Tell the user the numbers; you decide what to do.
plain (a done job with the result): one plain line for each load case (the value that is compared, who gave the allowable, the margin) and a line for what the check does not cover. They are a base for the answer to "will it break?".
partial, coverage (a done job; also with result=false): partial is true when a check that the YAML or the call asks for failed or did not run; the other numbers still stand. coverage has ran (names), failed (name, reason) and not_run (name, reason, asked). A check that was not asked for is under not_run with asked false and does not make the result partial. Then summary and the first plain line say so. |
| ratchet_runA | Judge candidate edits of a finished part and keep the ones that pass the rules of the YAML (the ratchet). The start part is judged first (the baseline). A candidate that passes every listed rule becomes the new parent (folder acc_NN in the ratchet root). A candidate that fails is kept in the log with the reason. The rules are the list acceptance.rules in the problem YAML; a rule that is not listed is off. Nothing in the start run is written.
estimate_only: default True. It returns the rules, what each rule needs, what is missing (a rule with a missing input fails every candidate), the number of candidates and the time of verify (its own estimate), and starts nothing. Tell the user the numbers, then call again with estimate_only=false. A real start returns at once (a detached process does the work, like verify_run); call ratchet_status to watch it. Only one job goes at a time on this machine.
Reply to a start: ok, started, run_dir, root, pid, log_file, check (the estimate), message.
On a wrong input: {"ok": false, "error": ..., "exit_code": 1 or 2}.
run_dir: the finished run (needs run.json, labels.bin for marks, result/final.stl). Default: the last run of this server.
source: marks:FILE (a marks file of the Refine page: one candidate for each mark, made by apply_marks) or stl:FOLDER (each STL file of the folder is a candidate). marks: a marks file, the same as source="marks:FILE".
root: the ratchet folder (default: beside the run folder, <run folder>-ratchet). A second call with the same root goes on where the first stopped. If the rules, the mesh size or the inputs (the YAML, the region files) differ from the ratchet in the root, the call stops with the facts: use a new root; or rebaseline=true (judge the current parent again under the new inputs, keep the log); or overwrite=true (it moves the old files into earlier_NN).
override_lock: run although another process holds the job lock of the root.
stop_before_promote: the owner gate. The job stops before it promotes an accepted candidate (ratchet_status shows pending); ratchet_approve promotes it or drops it. Off by default; the YAML key acceptance.owner_gate does the same.
yaml_path: the problem YAML, if the path in run.json is not valid.
max_candidates: judge at most this many in this call. mesh_size_mm: the size of the baseline and of all candidates.
Each candidate is judged against the current parent with topopt printcheck and, if a rule needs it, topopt verify at the mesh size of the baseline. Every limit comes from the YAML and the log names its source.
A stress or stiffness rule on a marks candidate is judged like on any other candidate. If verify refuses the surface (an apply_marks step that fell back to the plain boolean seam), the log record has errors.verify and the rule check is no_input. |
| ratchet_statusA | Show the state of a ratchet: counts, the current parent, the last records with the reasons, and the job. root: the ratchet folder (default: the folder of the last ratchet that this server started).
last: how many of the last records to show (each: id, decision ACCEPT or REJECT, the rules that failed, the
reasons, promoted, owner_approval). The whole log is ratchet.jsonl in the root: one JSON line for each record.
States: starting, running (the job process is alive; file_state holds the state of the file when it is a final
one that waits for the process to end), finished (every candidate was judged, or max_candidates was
reached), waiting_for_owner (the opt-in gate: pending lists the accepted candidates, ratchet_approve decides),
baseline_failed (the start part could not be judged), stopped_after_approval (ratchet_approve ended the job after an approval), failed (the last process ended with an error, for example a
refused start: last_error has the exit code and the text; or it stopped with no exit code and no final state, for
example killed: log_tail has its last output) and none (no ratchet in the folder). The final states show only after the
process has ended, so the next call (ratchet_approve or ratchet_run) is not refused as busy.
Nothing calls you back when the job ends: call it again in this turn until the state is finished, waiting_for_owner, baseline_failed, stopped_after_approval or failed, or process_alive is false, or end the turn and tell the user what to ask for the result. A promise of a later report cannot be kept.
Also: job (the facts of the job: state, exit code, wall time, peak memory), process_alive, pid, mesh_size_mm
(one size for the whole ratchet), baseline (its failed rules and facts), log_facts (a skipped partial line). |
| ratchet_approveA | The opt-in owner gate: promote the accepted candidate that waits, or drop it. A ratchet started with stop_before_promote (or acceptance.owner_gate: true) stops before it promotes an accepted
candidate. ratchet_status shows it as pending with state waiting_for_owner. The owner decides; this tool writes the
decision to the log (owner_approval: given or declined). Ask the user first. It does not start a job: after an
approval, call ratchet_run again with the same root to judge the next candidates against the new parent.
root: the ratchet folder (default: the last ratchet that this server started).
id: the candidate (for example c003). Default: the one that waits.
decline: drop the candidate instead of promoting it.
Reply: ok, id, owner_approval, promoted (the new parent folder, or null) and parent. |
| build_regionsA | Build region STLs and a problem YAML from a part spec given as data. Use it when the user describes a part in words. Define the design space, fixed regions, loads (force vectors in N), keep-out regions (if any) and solid bodies (solid, forced solid).
It already returns check and warnings. Call check_problem only after you edit the YAML, or for your own STLs. Next: state your assumptions, then run_topopt. If the user gave no load (no object, no mass and no force), ask before the run. A guard stop names an override: read the memory estimate against the RAM (a run above it can freeze the machine), raise the guard in the spec or take a larger voxel, and report what, why and the price.
Writes /runs//. Reply keys: resource topopt://replies/build_regions (the meaning of each key). Use yaml_rel with run_topopt. On a wrong spec: {"ok": false, "error": one line that names the key path}. ok is false also when the builder fails (builder_ok false): problems gives the builder errors, and the files stay.
name: folder and file name; default is spec["name"].
spec: a dict (or YAML text) with the schema of the part-to-topopt skill (spec.yaml): regions (design, fixed, load, solid, keep_out; primitives box, cylinder, sphere, capsule, bowl, prism, stl and screw_ring (see find_features), subtract), load_cases, material (E, nu, density or filament), settings (voxel_size may be "auto"; optional min_member_mm, filter_solid_neighbours, stop_rules (as in the YAML: a mapping turns the stop rules on, and time_budget_s, the user's own time budget, ends the run at the first pass after it; enabled: false keeps the rules off; the check warns when the estimate is above the budget) and perimeter_max_mm2: a bound on the surface area of the part in mm2). Units mm, N, MPa. settings.volume_fraction (default 0.25) is the mean density of the FREE design voxels; the fixed, load and solid voxels are solid on top. The YAML keeps your value. A mass in grams that the user asks for goes to settings.target_mass_g in place of volume_fraction (one of the two keys; both is an error): the engine derives the fraction from the grid.
The report and derived.volume give the free and the forced-solid voxels, the target volume in mm3 and the target mass in g (the same numbers as check_problem).
Loads: a force entry can also have moment in N mm and at in mm, and a case can have pressures of {region, value_mpa}.
A fixed_axes mapping (fixed name -> axes) makes a roller of a fixed region.
A springs list of {region, stiffness: [kx, ky, kz]} (N/mm, total; or stiffness_per_area) makes an elastic support on a load region; fixed may then be empty.
A joints list of {region, to: [x, y, z], section: {kind: round, d: ..}} (mm; kinds round, tube, rect, props) bonds the faces of a load region to a rigid body and joins it with a beam to the fixed point to; fixed may then be empty.
A flat face of a region whose plane is not on a voxel face (a voxel belongs to a region when its centre is inside) moves to the nearest voxel face. When such planes change the part, the report has ONE note: line (also in warnings) with the voxel size, the grid origin, an item for each plane (the region, the plane, larger or smaller than your shape) and the options. check.region_planes has all planes, with offsets in mm and voxels, and changes_part (false when the layer is under a region of higher priority or outside the part).
Items in one region join (union).
Optional spec key mirror: a list of up to 3 axis-aligned planes with distinct axes, each {normal: x|y|z, at_mm: }, align: auto|face|centre optional. The tools return facts about it (parity, orphans, load asymmetry). |
| record_briefA | Record the brief of a problem YAML: what the user wants and the assumptions that you state. Say it, then go on. A case is one problem YAML with the STL files it names, one brief and many runs. This call makes the case
(the files <yaml stem>.case.json and <yaml stem>.case-log.jsonl beside the YAML) or replaces its brief.
The case keeps the SHA-256 of the YAML and of each region file with the brief, and the values of the method keys
of the YAML. If a file or a requirement key changes later, case_status and run_topopt say that the brief is
stale and name it. For a case with no brief or a stale brief, run_topopt returns awaiting_user with an override.
A change of a method setting (for example settings.solver or settings.voxel_size) is a fact and not stale:
run_topopt goes on, and brief_facts names the key with both values, so that you can report it. yaml_path: the problem YAML in the work folder. A YAML outside the work folder (an example in the code
folder, for example) gets no case file: the reply gives the fact and the options. A YAML in the code
folder of a source checkout is recorded, with a fact and an option (a work folder is the better place).
text: the brief in plain words: the goal, the loads and where they come from, the mounting, the material,
the critical sizes.
assumptions: a list of {"text": ..., "origin": ...}. origin is user (the user said it), inferred
(you concluded it) or default (a default value). The YAML assumptions list is a list of strings
that run.json copies; the reply names the texts that differ from the brief.
criteria: what the result has to show, in the same form (optional). |
| case_statusA | Show the case of a problem YAML: the brief, the input hashes, the earlier runs and what is stale. Changes nothing. brief_stated is true when a current brief is recorded. stale_files names the files that changed since the
brief. A change of a comment, of the format or of a method setting (for example settings.solver) in the YAML is a
fact, not stale. inputs has the SHA-256 of the YAML and of each region file now. runs lists the runs that
run_topopt started for this case, newest first. Each has its state (read from the run folder), stale and
stale_files for its own inputs (a run is stale after any change of the YAML content, a method setting too), and
facts, for example that the folder now holds another run. gate says what run_topopt does now.
yaml_path: the problem YAML (relative paths start at the work folder). |
| environmentB | Facts about this machine and the solver backends. Changes nothing. Use it to answer "can this machine run it, and how fast?", or before a long run. Tell the user the numbers that matter.
yaml_path: optional problem YAML (relative paths start at the work folder). With it, problem holds the time estimate for each backend (model values, not promises).
Reply keys: resource topopt://replies/environment (the meaning of each key).
If TOPOPT_PYTHON names another Python, the data is for that Python (run_interpreter says so).
recommendations are offers as data: when a faster backend is possible but not installed (pypardiso on Linux x86_64, the Accelerate shim on a Mac), each has a reason, the command and license_note (speedup_factor when it is measured).
This tool installs and builds nothing. If the user agrees, you run the command. Say the reason (and the speed-up) first. gpu_solver is a fact, not an offer. auto takes a GPU that passes a short self-test, and goes on with the CPU solver (auto_choice.cpu_solver) from the last checkpoint if gpu-mg fails during a run. settings.solver: gpu-mg asks for it on any OpenCL device (run_topopt has no solver argument: write it in the YAML) and has no fallback. The OpenCL device list and the self-test run in a child process (limit 60 s). At the limit opencl.problem says so, and auto takes the CPU rule; TOPOPT_AUTO_GPU=0 skips the wait.
A gpu-mg run that stops with "did not reach the relative residual" lists its options in the error (settings.gpu_tol, settings.gpu_backward_error, settings.gpu_max_iter in case B, a stiffer start design, another solver); the residual history is in gpu_mg.jsonl of the run folder.
gpu-mg takes springs, joints and per-axis supports (a fixed entry with axes), alone or together. In case C (no 64-bit float on the device) the joint term is computed in 32-bit float and the inner PCG removes the rigid modes of each body and 6 harmonic columns for each bonded patch, and solves them in 64-bit on the host: check prints the beam over the bond stiffness with the cases that were measured on pocl and on one Intel Arc iGPU (one host each; revision 133e5a1; a cube that only a joint holds solved at 9.9e-12 and above with the rigid modes removed, and at 3.3e-7 and above without; a part with two tube joints and no fixed region solved with the harmonic columns and stopped without them) and the memory of the columns, which the memory estimate does not count. These are facts, not a limit. A run that stops gives the ratio and the options (another solver, gpu_case B on a device with 64-bit float, a stiffer beam). |
| save_preferencesA | Keep the answers of the user (print bed, material, filaments, quality, outputs) for the next runs, in topopt-preferences.yaml in the work folder. It writes only that file. Use it once, after the user answered. environment shows the file in preferences. The server reads the file at each call.
A preference does not change a run by itself: run_topopt and check_problem fill nothing. build_regions takes a material, print or settings.voxel_size that the spec does not have at all, writes it into the YAML and says so in preferences_facts; a block that the spec gives, also a part of one, stays as it is. check_problem names a difference in preferences_facts.
Sections (each optional; checked like the problem YAML): print: printer_model (a name or id from find_printer; it gives the bed and type unless the YAML does), printer_type (fdm, sla, lpbf, sls), bed_mm (one number or [x, y] in mm), build_direction (+x to -z, or 3 numbers), self_supporting_angle_deg. A print limit (min_wall_mm) belongs to one part: it is refused. material: E (MPa), nu, density (g/cm3), filament (from find_material; gives E and density), name (free text, for example "PETG"). filaments: available (names or ids of the filaments the user has). quality: voxel_size (mm). outputs: renders (render_part), step (export_step), verify (verify_run). The STL and the part card are made in every run. [] is none of the three.
A section you do not give stays; one you give is merged key by key: a key with null is removed, and {} removes the section (outputs too). An unknown key stays in the file. With no section it makes an empty file if none exists (the user kept the defaults).
Returns: ok, path, values, changed, created, kept_not_used, problems, note. A wrong name, key or value gives {"ok": false, "error": ...} with the key, the value and the rule; nothing is written. It refuses a file that is not valid YAML and a folder that cannot be written. There is no CLI command: edit the file.
|
| find_printerA | Find a 3D printer by name in the catalog that ships with topopt and in the user printers of the work folder (topopt-printers.yaml). It reads; it changes nothing. The user only names the printer; the tool knows the print parameters. Use it when the user says which printer they have ("Bambu P1S", "my Prusa MK4S"), before you ask for a bed size or a process.
name: the model as the user says it. Case, punctuation, the maker name and the words "my" and "printer" do not matter ("p1s", "Bambu Lab P1S" and "bambu p1 s" are one printer).
Returns: ok, status, query, catalog_version, matched_by, origin, profile, derives, candidates, differs, suggestions, vendors, user_file, note. "match": profile is the printer: id, name, origin (catalog or user), bed_mm, build_height_mm, nozzle, temperatures, enclosure, nozzle material, extruder, and the source of the values (source_url, opened, tag, field_sources, note). A value that the source does not give is null (unknown): say so. derives says what print.printer_model: <id> sets in a run (the bed and the printer type, when the YAML does not give them) and what the profile cannot give. "ambiguous": candidates and differs (the facts on which they differ). Ask the user which one; do not pick for them. matched_by is "partial" when the text is only a part of a name (one candidate too: a similar model can have other numbers). "none": suggestions ("did you mean": never taken by itself) and a note on how to go on: look up the maker page and save_printer the values, or ask the user.
user_file: path, exists, entries, problems. A problem in the file is a fact; the shipped catalog still works.
Next: show the user the derived values, then save_preferences(print={"printer_model": id}) keeps the printer, or write print: {printer_model: <id>} in the YAML. A bed_mm or printer_type in the YAML wins. With a printer, the print report also compares the part height with its build height.
|
| save_printerA | Keep a printer that is not in the catalog, with its facts and their sources, in topopt-printers.yaml in the work folder. It writes only that file. Use it after find_printer says "none": look up the maker page (web search), show the user the values with the page address, and call this tool after the user agrees. A value that you cannot find stays null (unknown): ask the user for it, and leave it null if they do not know; a guess would look like a fact. The server reads the file at each call, so the printer is live at once.
profile: one printer as a dict. Needed: id (lower-case slug, for example "voron-trident"), vendor, model, printer_type ("fdm", "sla", "lpbf", "sls"), bed_mm ([x, y] in mm), and a source: source_url (the maker page) or source "user" (the user said the values).
Optional (null or left out = unknown): aliases (list), build_height_mm, nozzle_mm, nozzle_options_mm (list), max_nozzle_c, max_bed_c, enclosure ("open", "enclosed", "heated"), chamber_max_c (only with "heated"), stock_nozzle_material ("brass", "stainless", "hardened_steel", "tungsten_carbide"), extruder ("direct", "bowden"), opened (YYYY-MM-DD, the day the page was read), tag ("maker doc", "third party", "user"), field_sources ({field: page address or "user"}), note.
The entry is checked like the shipped catalog. The same id replaces a user entry; the id of a shipped printer is refused with another id to use, and a name or alias of a shipped printer with another model name or alias. Other entries stay.
Returns: ok, path, profile, created, replaced, note, problems (of the rest of the file). A wrong argument name or value gives {"ok": false, "error": ...}; nothing is written. A file that is not valid YAML is not changed. There is no CLI command: edit the file. |
| find_materialA | Find a 3D printing filament by name in the catalog that ships with topopt and in the user filaments of the work folder (topopt-filaments.yaml). It reads; it changes nothing. The user only names the filament; the tool knows the numbers of the data sheet. Use it when the user says which spool they have ("Bambu PLA Basic", "Prusament PETG"), before you ask for a modulus or a density.
name: the product as the user says it. Case, punctuation, the maker name and the word "my" do not matter ("polylite abs" and "Polymaker PolyLite ABS" are one filament). A family alone ("PLA", "PETG") names no product: the answer is "ambiguous" with every filament of the family.
Returns: ok, status, query, catalog_version, matched_by, origin, profile, derives, candidates, differs, suggestions, makers, user_file, note. "match": profile is the filament: id, name, family, sheet_version, the tensile modulus and strength in the layer plane (xy) and across the layers (z), density, temperatures, enclosure, hardened nozzle, drying, and the source of the values (source_url, opened, tag, field_sources, note). A value that no source states is null (unknown): say so. nu is null for every filament, because no data sheet gives it. derives says what material.filament: <id> sets in a run (E is the lower of the XY and Z modulus, taken from the bending modulus of the sheet when the sheet gives no tensile modulus; density is the one of the sheet, the middle of a range that the sheet gives, or else the median of the filaments of the family, labelled assumed; nu stays the assumed 0.35; a key in the YAML wins) and what the profile cannot give. "ambiguous": candidates and differs (the facts on which they differ). Ask the user which one; do not pick for them. "none": suggestions ("did you mean": never taken by itself) and a note on how to go on: look up the maker data sheet and save_material the values, or ask the user.
The numbers of a data sheet describe printed test bars of one maker and one set of print settings, not the part of the user.
user_file: path, exists, entries, problems. A problem in the file is a fact; the shipped catalog still works.
Next: show the user the derived values. printer_materials shows what the printer of the user can print; check_problem gives the same fit for a YAML with material.filament, in its key material. save_preferences(material={"filament": id}) keeps the filament, or write material: {filament: <id>} in the YAML.
|
| save_materialA | Keep a filament that is not in the catalog, with its facts and their sources, in topopt-filaments.yaml in the work folder. It writes only that file. Use it after find_material says "none": look up the data sheet of the maker (web search), show the user the values with the page address, and call this tool after the user agrees. A value that you cannot find stays null (unknown): ask the user for it, and leave it null if they do not know; a guess would look like a fact. The server reads the file at each call, so the filament is live at once.
profile: one filament as a dict. Needed: id (lower-case slug, for example "acme-pla-plus"), maker, product, family (PLA, PETG, ABS, PA6-CF, ...), and a source: source_url (the data sheet) or source "user" (the user said the values). A run needs a modulus (the tensile E_xy_mpa or E_z_mpa, MPa; a sheet that gives only a bending modulus gives bending_modulus_xy_mpa or bending_modulus_z_mpa, and E is taken from it) and a density (density_g_cm3, g/cm3) from it, or from the YAML.
Optional (null or left out = unknown): aliases (list), sheet_version, E_xy_mpa, E_z_mpa, bending_modulus_xy_mpa, bending_modulus_z_mpa, strength_xy_mpa, strength_z_mpa, strength_kind ("yield", "peak", "not stated"), elongation_xy_pct, density_range_g_cm3 ([min, max] of a sheet that gives a range, with density_g_cm3 null), hdt_18_c, hdt_045_c, nu, nozzle_c ([min, max] in C), bed_c ([min, max]), enclosure ("no", "recommended", "required"), chamber_c_min, hardened_nozzle (true or false), drying (text), direct_drive (true), abrasive (true for fibre), soft (true for TPU), conditioned (wet values of a polyamide: condition, strength_xy_mpa, strength_z_mpa, E_xy_mpa, E_z_mpa), opened (YYYY-MM-DD, the day the page was read), tag ("maker TDS", "maker page", "third party", "user"), field_sources ({field: page address or "user"}), enclosed_note (what the sheet says about printing in an enclosed printer), note.
The entry is checked like the shipped catalog. The same id replaces a user entry; the id of a shipped filament is refused with another id to use, and a name or alias of a shipped filament with another product name or alias. Other entries stay.
Returns: ok, path, profile, created, replaced, note, problems (of the rest of the file). A wrong argument name or value gives {"ok": false, "error": ...}; nothing is written. A file that is not valid YAML is not changed. There is no CLI command: edit the file. |
| printer_materialsA | Which filaments can this printer print? Every current filament of the catalog and the user file, grouped by state, with the reasons (older sheets of a listed product are counted in note). It reads; it changes nothing. printer: a printer as the user says it (a name or id; see find_printer). Without it, the printer of the preferences file (print.printer_model) is used; if there is none, the reply is {"ok": false, "error": ...} that says how to name one.
States (facts about the stated values, not a stop; no run is refused because of them): ok (every stated value fits), care (it can work, and the person has to do or know something: fit a hardened nozzle or a chamber heater, take the lower part of a temperature range), no (a stated need is above a stated limit of the printer as it ships, for example an enclosed printer, a nozzle temperature or a bed temperature; the user may have changed the printer), unknown (a value is missing on one side; the reason names it).
Returns: ok, printer (id, name, origin, from), catalog_version, counts, groups, available, user_file, note. groups: ok, care, no, unknown, each a list of {id, name, family, origin, available, source, reasons}. reasons lists the checks that are not ok (check, state, text); the text names both values that it compares. source is the data sheet of the filament; the source of the printer is printer.from. The reply is long (about 70 filaments): read the groups that matter and name the few that the user asked for. available: the filaments of filaments.available of the preferences, each with its state; they are also marked in the groups. An id that is not found now is left out and named in not_used; an older sheet has its state and a note with the newer id.
Say it as a fact with the reason ("PLA Basic: ok on a P1S", "ABS on an A1 mini: no, the maker asks for an enclosed printer"). The user decides. A filament that is not listed: find_material, then save_material.
|
| print_checkA | Check a finished part for printing. Geometry only: no solve, no run. It gives the same report as topopt printcheck. path: an STL file or a run folder (it has result/final.stl); relative paths start at the work folder. With no path, the last run is used. It works on any STL (final.stl, a nominal.stl, an edited.stl): no metrics.json and no YAML are needed. About 2 to 5 s for an 85,000-triangle part.
yaml_path: a problem YAML whose print block gives the settings and limits. A run folder already has the print block of its run. print_settings: a dict with keys of the print block, for example {"min_wall_mm": 1.2, "bed_mm": [250, 210]}; it wins over the YAML.
resolution_mm: the size of the smallest member that the mesh holds: the voxel size (a run folder gives it), or 0 for exact CAD. The default is the voxel size of the run, else the median mesh edge.
strict: opt in to the gate. Without it the tool only reports.
gate: with strict, state is fail only when a check that has a limit fails (unknown never sets it); exit_code is then 4 and override names the way on. No run stops on it.
On an input that cannot be read: {"ok": false, "error": ...}. A failed check is data, not an error.
A print pass is not a structural pass. A finished run already has this report as result/print_check.json (run_result shows its counts): call this tool again for other limits or another direction.
Each check has id, state (pass, fail or unknown), reason, value, limit (value, key and source: run.json, yaml, option, profile:, assumed or none) and method. Checks: mesh_closed (the STL as a float32 file: open and non-manifold edges, winding, volume), bodies, closed_voids (count and volume), bed_fit, overhang, support_volume, plate_contact, wall_thickness. The limits are print-block keys: bed_mm, max_bodies, min_wall_mm, min_wall_area_pct, max_closed_void_mm3, max_overhang_area_mm2, max_support_volume_mm3, min_plate_contact_mm2, and the V2 keys build_direction, printer_type and self_supporting_angle_deg. printer_model (a printer of find_printer) gives the bed, the type (source profile:) and the build height: bed_fit then also compares the part height with it. A check with no limit is unknown and its value is a fact. The thickness is an estimate: by the rule of this check it passes only if the limit is at least 3 x the resolution; a thin wall that is measured fails at any resolution. When it is unknown only for that rule, value.pass_needs gives the resolution that a pass needs (resolution_mm_at_most, and voxel_count_factor for a run folder: an estimate of the voxels of that grid against this one).
Also: summary, settings (with from: where each source came from), input (path, sha256), timing_s, definitions.
partial, coverage: partial is true when a check that is asked for stopped with an error (its record has state unknown and error; the other checks go on) or has a limit and could not decide. coverage has ran, failed (name, reason) and not_run (name, reason, asked); a check that is not asked for (the support volume of a powder-bed printer) does not make it partial. |
| export_stepA | Write a faceted STEP file of a finished run (extra step: OpenCASCADE). Post-only: no pass runs and no input file changes. Same as topopt step. The call is synchronous: it returns when the file is written, and a large export holds the call for minutes. Call it with estimate_only first for a large mesh: the estimate gives the time before the work starts.
Cost (Linux x86_64): the cost follows the number of faces after the simplification, and that follows the part. A hook gives 9,000 to 36,000 faces with the default tolerance: 10 to 40 MB, 12 to 40 s, about 1 GB. A larger part with 85,000 faces took 102 s and 1.8 GB; another with 356,000 faces took 613 s (10 minutes) and 5.8 GB, and the file was 402 MB. tolerance_mm=0 keeps every triangle: about 1.1 KB, 1 ms and 16 KB of memory for each triangle (322,000 triangles: 363 MB, 6 minutes, 5.5 GB).
run_dir: the run folder (relative paths start at the work folder; default: the last run). It wraps result/final.stl, or result/part_only.stl with part_only. Output: result/final.step (result/part_only.step) and its report next to it, result/final.step.json (result/part_only.step.json). out is another file path or an existing folder; the report is then next to that file (.json), so the run folder gets no file. An existing file is replaced only when it is a .step or .stp file; the input STL and any other existing file give an error with the options.
estimate_only: simplify, count the faces and estimate size, time and memory; nothing is written. It works without OpenCASCADE.
A failed check is a fact and the file is still written (exit_code 3). Without the extra the reply is {"ok": false, "missing_extra": "step", "install_command": ..., "error": ...}. Any other failure is {"ok": false, "error": ..., "exit_code": ...}.
tolerance_mm: the simplification of the mesh (manifold3d), the largest move of the surface in mm; 0 = none. Default: the voxel size of the run / 20 (0 when the run folder holds no voxel size). A larger value gives fewer faces and a smaller, faster file; a value that removes every triangle is an error and no file is written. merge_planes: coplanar faces join into one planar face (the frozen plates). schema: AP214 (default) or AP242 (ap-242 and ap242 also work).
The faces are the facets of the mesh, so a CAD program can cut holes in the solid and join it to other parts; it cannot edit the surfaces.
The reply is the report: input (triangles, volume), settings (tolerance_mm and where it comes from), simplify, merge_planes, build (bodies, voids, and the check of the model in memory), model (the file read back: solids, faces, edges), volume (STEP against STL), deviation (sampled distance in both directions: max and mean, in mm), checks (read_back, topology, before_write, valid_solid, closed, units, schema: pass or fail with a reason; none has a threshold), checks_failed, options, timing_s, peak_rss_bytes.
The reply of any other failure has the first line of stdout when there is one, and "signal" when the child process was killed. |
| cleanup_listA | List the geometric cleanup candidates of a finished part as a marks file, in a NEW folder: facts and options, no edit, no verdict. CONTRACTS.md section 29.3. The tool scans result/final.stl with the detectors of the pipeline stage s7_cleanup and writes marks.json (format 1, mm: apply_marks, the Refine page and the marks: source of ratchet_run read it) and cleanup.json (the full report). It lists loose bodies (no fixed and no load voxel), dead-end spurs, thin bumps, uneven struts (SMOOTH) and flat webs (FAIR). When a loose body exists the file holds the bodies only: judge them, then list the acc_01 of that ratchet root for the features. The list judges nothing: ratchet_run(marks=marks_rel) applies each mark and judges it with the rules of the YAML. The run folder and every input stay as they are.
Time: about 2 s on the short hook and 8 s on the ladle. The call runs in the server and finishes if the client stops waiting (Codex waits 60 s for a tool by default); then read cleanup.json, or call again with overwrite=true. A grid above max_voxels or the memory limit gives the facts and the options (pitch_mm, max_gb), not a crash.
A wrong input gives {"ok": false, "error": ...}.
run_dir: a finished run, or an acc_NN folder of a ratchet root (needs run.json, labels.bin, result/final.stl). Default: the last run of this server.
out_dir: the new folder. Default: -cleanup beside the run folder (beside the root for an acc_NN folder). Refused, with the reason and the options: a run folder or a folder inside one, a ratchet root or a folder inside one, a folder with an input file, a symbolic link, a folder with files that is not an earlier cleanup folder.
estimate_only: the memory and time estimate of the detector and the judge; no search, no file.
options: pitch_mm (0.5), max_candidates (10; null lists all), kinds (body, spur, bump, member), order ("pipeline" or "stress"), max_voxels, max_gb, and the limits of the detectors (spur_*, bump_*, member_*, mild_*, web_flatness, opening_radius_mm, frozen_near_mm, frozen_max_share, near_frozen_mm); cleanup.json gives each default and its source line in the pipeline. The constants of the method are not settable.
overwrite: write again into an earlier cleanup folder. detail: the full report in the reply.
yaml_path: the YAML of the run when run.json names a file that is not there. root: a ratchet root; its log gives the measured judge time for the estimate.
Each mark has the detector, the location, the operator, the thresholds used, the measured sizes and, when verify/fields.npz is there, the stress near it. The facts also say how well the detectors fit this part (applicability).
Reply keys: ok, counts, candidates (id, type, operator, detector, confidence, centre_mm, detected_volume_mm3, length_mm, nearest_frozen_mm, stress_ratio, mirror), applicability, bodies, estimate, routes, problems, notes, out_dir_rel, marks_rel, report_rel. |
| team_guideA | Playbooks for the roles of a topopt team: lead-design, cad, fea, print and finish. A role is a job, not a separate agent. One agent can do all five in one conversation. A client that runs
sub-agents can give each role to one. A playbook says what the role does, which tools it can use today,
what does not exist yet, and what to report. The text is the same as the MCP prompt of that role and the
resource topopt://roles/. role: a role id (lead-design, cad, fea, print, finish). Also accepted: any case, the plugin agent name
(cad-engineer) and the resource URI (topopt://roles/cad). Without a role, or with a name that matches
none, the answer is the list of roles with one line each. That is a fact, not an error. |
| find_featuresA | List the holes, bosses and flat faces of an STL file, in mm. Read only: the file is read and nothing is written (CONTRACTS.md section 45). stl_path: an STL file (a relative path starts at the work folder). About 4 s for a million triangles. A wrong input gives {"ok": false, "error": ...}.
A hole's point, axis, radius_mm and length_mm are the keys of a bore in interfaces (section 23).
holes: each cylindrical hole has id, point (the centre of the first end face), the unit axis, radius_mm, length_mm, end_point, depth (through, blind, closed or unknown), ends, arc_deg (the angle the wall covers), segments and chord_sag_mm (the facets lie that far inside the radius), fit (distance of the wall vertices to the circle) and coaxial_with (a stepped hole is two cylinders on one axis). Every hole is listed with its radius and fit, whatever its size.
bosses: convex cylinders (a shaft, a post), the same keys without depth.
flat_faces: planar regions, the largest first: normal (outward, unit), area_mm2, centroid, offset_mm.
mesh: faces, watertight, bounds_mm. A mesh that is not closed still gives what can be found (no repair).
method: numbers (each detection number as used) and counts. facts: plain sentences (an open mesh, a stepped hole, a feature near a limit, regions left out).
A hole whose wall joins a fillet, a tapered hole and a slot are not cylinders to this method.
Your STL as a region item of build_regions: {stl: {path: part.stl, plug_holes: [H1] or all, screw_holes: [H2] or all, ring_mm: 2}} (a relative path starts at the work folder; a growth box is one more item). Only a closed solid mesh is read (no repair). plug_holes fills those holes (ids stable for one file). screw_holes adds a frozen ring round each: t = 0.5 x d, at least 2 voxels, or ring_mm (a number, or {H2: 2}). {screw_ring: {path: part.stl, hole: H2}} is that ring as a fixed or load region. A mouth chamfer and a drill point stay. |
| show_problemA | Show a problem YAML to the user as a 3D view in the chat: the design space, the fixed, keep-out, solid and load regions, an arrow for each load case, and the time and memory estimate. No optimizer run: the same checks as check_problem, in the same time. yaml_path: the problem YAML (relative paths start at the work folder).
A client that shows MCP Apps (for example Claude Desktop) shows the card. The user can turn and zoom it. The view's button "Start the run" works in one of three ways. The host lets the view call tools: the click starts the run at once (run_topopt), the view watches it (run_status), and the host may tell you about the start and the end. While that run works, a second run_topopt is refused; run_status shows the run. When the run folder (runs/live) holds a run, the first click shows what the start replaces; a second click starts. The host takes only a message from the view: the button sends a chat message from the user that asks you to start the run. Neither: the view shows a line for the user to type. Another client shows only the text: the region counts and the estimate.
Use it when the YAML is ready, so that the user sees the setup before the run. The full facts and the warnings are in check_problem.
On a wrong input: {"ok": false, "error": ...}. |