Implement the reviewed UI icon system and durable review workflow (#853)

Thanks @Nathan-Dane.
This commit is contained in:
Nathan Dane authored and GitHub committed 2026-10-17 04:07:16 -04:00
1 parent c599238653
commit 64966bab88
374 files changed
+8187 -2221

No files matched your search

+8 -8
View File
@@ -13,16 +13,16 @@ use crate::params::{bad, bool_, str_};
use crate::{Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("body.rename", "Rename Body", rename_body).params("body: its name, name: the new name (features that use the body follow)"),
CommandSpec::new("body.rename", "Rename Body", rename_body).icon("rename").params("body: its name, name: the new name (features that use the body follow)"),
CommandSpec::new("sketch.redefine", "Redefine Sketch Plane", redefine_sketch)
.icon("plane")
.params("sketch: id or name; plane: as sketch.create; author_path?: exact owner occurrence ids; explicitly rebinds or freezes the plane while preserving geometry and dimension names"),
CommandSpec::new("browser.group", "New Group", group)
.icon("folder")
.params("folder: bodies|sketches|construction; component?: id (default 0); name?; items?: [keys] (they leave other groups)"),
CommandSpec::new("browser.ungroup", "Ungroup", ungroup).params("group: id (its items go back to the folder)"),
CommandSpec::new("browser.rename_group", "Rename Group", rename_group).params("group: id, name"),
CommandSpec::new("browser.move", "Move Browser Items", move_items)
CommandSpec::new("browser.ungroup", "Ungroup", ungroup).icon("folder").params("group: id (its items go back to the folder)"),
CommandSpec::new("browser.rename_group", "Rename Group", rename_group).icon("rename").params("group: id, name"),
CommandSpec::new("browser.move", "Move Browser Items", move_items).icon("reorder")
.params("folder, component?, items: [keys]; group?: id (into it; omit to take them out of groups); before?: key (position in the group)"),
CommandSpec::new("document.units", "Change Design Units", document_units)
.icon("settings")
@@ -30,13 +30,13 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("view.save", "New Named View", view_save)
.icon("camera")
.params("name?; camera: {target: [x,y,z], yaw, pitch, distance, fov?} (replaces a view of the same name)"),
CommandSpec::new("view.rename", "Rename Named View", view_rename).params("view: name, name"),
CommandSpec::new("view.delete", "Delete Named View", view_delete).params("view: name"),
CommandSpec::new("view.list", "List Named Views", view_list).noundo().doc_only(),
CommandSpec::new("view.rename", "Rename Named View", view_rename).icon("rename").params("view: name, name"),
CommandSpec::new("view.delete", "Delete Named View", view_delete).icon("delete").params("view: name"),
CommandSpec::new("view.list", "List Named Views", view_list).icon("list").noundo().doc_only(),
CommandSpec::new("view.apply", "Restore Named View", view_apply).icon("camera").noundo().params(
"view?: front|back|top|bottom|left|right|iso|home (frames the model) | a named view's name; or camera?: {target: [x,y,z], yaw, pitch, distance, fov?} (radians, mm) → camera. The viewport (and the MCP screenshot) moves there. View state: not saved, not an undo step",
),
CommandSpec::new("browser.order", "Order Browser Items", order).params("folder, component?, order: [keys] (the folder's items in display order)"),
CommandSpec::new("browser.order", "Order Browser Items", order).icon("reorder").params("folder, component?, order: [keys] (the folder's items in display order)"),
CommandSpec::new("browser.visibility", "Show/Hide", visibility).icon("eye").noundo().params(
"visible: bool; items?: [sketch id or name | body name | construction name | joint name | O|X|Y|Z|XY|XZ|YZ]; \
or joints?: [joint id or name] (their glyphs; they still constrain and move parts); \
+2 -1
View File
@@ -12,8 +12,9 @@ use crate::params::{bad, string_list, vec3};
use crate::{Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("timeline.copy", "Copy Features", copy).noundo().params("features: [ids or names] (in timeline order on paste)"),
CommandSpec::new("timeline.copy", "Copy Features", copy).icon("copy").noundo().params("features: [ids or names] (in timeline order on paste)"),
CommandSpec::new("timeline.paste", "Paste Features", paste)
.icon("paste")
.params("translate?: [x,y,z] (moves the copies' geometry); the copies go at the marker"),
];
+1 -1
View File
@@ -12,7 +12,7 @@ use super::CommandSpec;
use crate::params::bad;
use crate::{Result, Sel, Session};
pub static COMMANDS: &[CommandSpec] = &[CommandSpec::new("inspect.compare", "Compare Bodies", compare_bodies).params(
pub static COMMANDS: &[CommandSpec] = &[CommandSpec::new("inspect.compare", "Compare Bodies", compare_bodies).icon("compare").params(
"a, b: body names (default: the two selected bodies, or the design's only two), in world placement; tolerance?: mm (0.001 to 10, default 0.01): \
surfaces closer than this agree; spacing?: mm (default 0.5): the largest piece of a face sampled as one point, coarser for a body that would need \
more than max_points? (default 8 million, up to 50 million) samples; max_regions? (default 50); color?: true | {only_in_a?, only_in_b?: \"#rrggbb\"} \
+10 -10
View File
@@ -29,8 +29,8 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("component.from_bodies", "Create Components from Bodies", from_bodies)
.icon("component")
.params("bodies: [names] → one new component per body, named after it"),
CommandSpec::new("component.activate", "Activate Component", activate).noundo().params("component: id or name (0 or \"root\" for the design); occurrence?, path?: exact placement used to author shared geometry"),
CommandSpec::new("component.list", "List Components", list).noundo().params(
CommandSpec::new("component.activate", "Activate Component", activate).icon("activate").noundo().params("component: id or name (0 or \"root\" for the design); occurrence?, path?: exact placement used to author shared geometry"),
CommandSpec::new("component.list", "List Components", list).icon("list").noundo().params(
"compact?: bool (counts of bodies, features, sketches and construction planes instead of their names; occurrences leave out \
identity transforms; children as occurrence ids); bodies?: bool (with compact: body names, not their count)",
),
@@ -40,20 +40,20 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("occurrence.properties", "Occurrence Physical Properties", properties::occurrence)
.icon("measure").noundo()
.params("occurrence: id or name; path?: [occurrence ids] (required if nested under multiple instances); descendants?: bool (default true). Physical properties of this placement in world coordinates, including pending moves"),
CommandSpec::new("component.rename", "Rename Component", rename).params("component, name"),
CommandSpec::new("component.delete", "Delete Component", delete).params("component: deletes it, its subcomponents, occurrences and features"),
CommandSpec::new("component.rename", "Rename Component", rename).icon("rename").params("component, name"),
CommandSpec::new("component.delete", "Delete Component", delete).icon("delete").params("component: deletes it, its subcomponents, occurrences and features"),
CommandSpec::new("component.unlock", "Unlock Contents", unlock).icon("rigid_group").params(
"component: removes the rigid joints that hold its contents together (an imported assembly's rigid group), so its parts \
move and take joints one by one → joints (the ids removed)",
),
CommandSpec::new("component.move_bodies", "Move Bodies to Component", move_bodies).params("bodies: [names], component"),
CommandSpec::new("component.move_sketches", "Move Sketches to Component", move_sketches).params("sketches: [ids or names], component"),
CommandSpec::new("occurrence.copy", "Paste (Instance)", copy_occurrence)
CommandSpec::new("component.move_bodies", "Move Bodies to Component", move_bodies).icon("move").params("bodies: [names], component"),
CommandSpec::new("component.move_sketches", "Move Sketches to Component", move_sketches).icon("move").params("sketches: [ids or names], component"),
CommandSpec::new("occurrence.copy", "Paste (Instance)", copy_occurrence).icon("copy")
.params("component (or occurrence): what to place again; path?: validates selected occurrence; parent?: component (default: the same parent); translate?: [x,y,z]. Copies the selected effective transform, including pending moves"),
CommandSpec::new("component.paste_new", "Paste New", paste_new).params("component: independently copy its nested definition graph and history, materials and styles; occurrence?, path?: selected source placement; translate?: [x,y,z]. Reassigned external bodies become base geometry; retained external links and omitted boundary joints are reported"),
CommandSpec::new("occurrence.move", "Move Occurrence", move_occurrence)
CommandSpec::new("component.paste_new", "Paste New", paste_new).icon("paste").params("component: independently copy its nested definition graph and history, materials and styles; occurrence?, path?: selected source placement; translate?: [x,y,z]. Reassigned external bodies become base geometry; retained external links and omitted boundary joints are reported"),
CommandSpec::new("occurrence.move", "Move Occurrence", move_occurrence).icon("move")
.params("occurrence; path?: exact placement; space?: local|world (default local); translate?: [x,y,z]; axis?: [x,y,z], angle?: radians or \"30 deg\", origin?: [x,y,z]; capture?: bool (default true; false keeps it pending until Capture Position). Nested occurrence definitions remain shared"),
CommandSpec::new("occurrence.ground", "Ground", ground).params("occurrence; grounded?: bool (default toggles)"),
CommandSpec::new("occurrence.ground", "Ground", ground).icon("ground").params("occurrence; grounded?: bool (default toggles)"),
CommandSpec::new("component.capture_position", "Capture Position", capture).at("SOLID", "POSITION").icon("capture").params("keeps pending occurrence moves"),
CommandSpec::new("component.revert_position", "Revert Position", revert).at("SOLID", "POSITION").icon("revert").noundo().params("occurrences?: [ids] (default all); drops selected pending occurrence moves"),
];
+3 -1
View File
@@ -20,12 +20,14 @@ pub static COMMANDS: &[CommandSpec] = &[
"path: a .solidcraft design → a new component linked to it, holding its solid bodies as placed in that design; \
name? (default: the design's name); parent?: component id or name (default: the active one); translate?: [x,y,z]",
),
CommandSpec::new("component.update_links", "Update Links", update_links).params(
CommandSpec::new("component.update_links", "Update Links", update_links).icon("update").params(
"component?: id or name (default: every linked component) — reads the linked designs again and replaces their bodies where they changed",
),
CommandSpec::new("component.break_link", "Break Link", break_link)
.icon("break")
.params("component: id or name of a linked component — keeps its bodies as they are and forgets the design file"),
CommandSpec::new("component.links", "List Links", list_links)
.icon("chain")
.noundo()
.params("→ linked components: component, name, path, file (resolved), found"),
];
+7 -7
View File
@@ -14,17 +14,17 @@ pub static COMMANDS: &[CommandSpec] = &[
),
CommandSpec::new("config.table", "Display Configuration Table", show)
.at("SOLID", "CONFIGURE")
.icon("params")
.icon("configuration_table")
.noundo()
.params("→ columns (param:<name> or suppress:<feature>), rows, the active row and whether the design still matches it"),
CommandSpec::new("config.column", "Configuration Column", column).params("param? | feature? | material? | appearance? (target name); delete?: bool"),
CommandSpec::new("config.row", "Configuration Row", row).params(
CommandSpec::new("config.column", "Configuration Column", column).icon("configuration_table").params("param? | feature? | material? | appearance? (target name); delete?: bool"),
CommandSpec::new("config.row", "Configuration Row", row).icon("configuration_table").params(
"name; from? (row to copy; default current values); values?: {column key: expression, suppression, material name, appearance name or Look object}; rename?; delete?: bool",
),
CommandSpec::new("config.activate", "Activate Configuration", activate).params("row: name — sets its parameter values and suppressions"),
CommandSpec::new("config.preview", "Preview Configuration", preview).noundo().params("row: name — validates a rebuild without changing the design or history"),
CommandSpec::new("config.rule", "Configuration Rule", rule).params("rule?: {id,name,enabled,condition,effect}; id? + delete?; id? + index? (ordered move)"),
CommandSpec::new("config.rules", "Configuration Rules", show).noundo().params("ordered typed rules, rows and active state"),
CommandSpec::new("config.activate", "Activate Configuration", activate).icon("activate").params("row: name — sets its parameter values and suppressions"),
CommandSpec::new("config.preview", "Preview Configuration", preview).icon("eye").noundo().params("row: name — validates a rebuild without changing the design or history"),
CommandSpec::new("config.rule", "Configuration Rule", rule).icon("configuration_table").params("rule?: {id,name,enabled,condition,effect}; id? + delete?; id? + index? (ordered move)"),
CommandSpec::new("config.rules", "Configuration Rules", show).icon("list").noundo().params("ordered typed rules, rows and active state"),
];
fn column_of(s: &Session, p: &Value, cmd: &str) -> Result<Column> {
+17 -17
View File
@@ -21,69 +21,69 @@ const REF: &str =
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("construct.axis.through_cylinder", "Axis Through Cylinder/Cone/Torus", axis_face)
.at("SOLID", "CONSTRUCT")
.icon("axis")
.icon("axis_through_cylinder")
.params("face: ref (a cylinder, cone or torus face, or a circular edge); name?"),
CommandSpec::new("construct.axis.normal_to_face", "Axis Perpendicular To Face", axis_normal)
.at("SOLID", "CONSTRUCT")
.icon("axis")
.icon("axis_normal_to_face")
.params("face: ref; point: ref (the axis goes through it, square to the face); name?"),
CommandSpec::new("construct.axis.two_planes", "Axis Through Two Planes", axis_planes)
.at("SOLID", "CONSTRUCT")
.icon("axis")
.icon("axis_two_planes")
.params("a, b: plane or planar face refs; name?"),
CommandSpec::new("construct.axis.two_points", "Axis Through Two Points", axis_points)
.at("SOLID", "CONSTRUCT")
.icon("axis")
.icon("axis_two_points")
.params("a, b: point refs (vertices, sketch points, points); name?"),
CommandSpec::new("construct.axis.edge", "Axis Through Edge", axis_edge)
.at("SOLID", "CONSTRUCT")
.icon("axis")
.icon("axis_edge")
.params("edge: ref (a straight edge or sketch line); name?"),
CommandSpec::new("construct.point.vertex", "Point At Vertex", point_at)
.at("SOLID", "CONSTRUCT")
.icon("point")
.icon("point_vertex")
.params("point: ref (vertex, sketch point, point); name?"),
CommandSpec::new("construct.point.two_edges", "Point Through Two Edges", point_edges)
.at("SOLID", "CONSTRUCT")
.icon("point")
.icon("point_two_edges")
.params("a, b: edge or line refs (where they meet, or come closest); name?"),
CommandSpec::new("construct.point.three_planes", "Point Through Three Planes", point_planes)
.at("SOLID", "CONSTRUCT")
.icon("point")
.icon("point_three_planes")
.params("a, b, c: plane or planar face refs; name?"),
CommandSpec::new("construct.point.center", "Point At Center Of Circle/Sphere/Torus", point_center)
.at("SOLID", "CONSTRUCT")
.icon("point")
.icon("point_center")
.params("of: ref (circular edge, sketch circle or arc, sphere or torus face); name?"),
CommandSpec::new("construct.point.edge_plane", "Point At Edge And Plane", point_edge_plane)
.at("SOLID", "CONSTRUCT")
.icon("point")
.icon("point_edge_plane")
.params("edge: edge or line ref; plane: plane or planar face ref; name?"),
CommandSpec::new("construct.point.along_path", "Point Along Path", point_along)
.at("SOLID", "CONSTRUCT")
.icon("point")
.icon("point_along_path")
.params("path: edge or sketch curve ref; t: expr, 0…1 along it (default 0.5); name?"),
CommandSpec::new("construct.plane.midplane", "Midplane", plane_mid)
.at("SOLID", "CONSTRUCT")
.icon("plane")
.icon("plane_midplane")
.params("a, b: plane or planar face refs (halfway between; bisecting when they meet at an angle); name?"),
CommandSpec::new("construct.plane.two_edges", "Plane Through Two Edges", plane_edges)
.at("SOLID", "CONSTRUCT")
.icon("plane")
.icon("plane_two_edges")
.params("a, b: straight edge or line refs in one plane; name?"),
CommandSpec::new("construct.plane.three_points", "Plane Through Three Points", plane_points)
.at("SOLID", "CONSTRUCT")
.icon("plane")
.icon("plane_three_points")
.params("a, b, c: point refs; name?"),
CommandSpec::new("construct.plane.tangent", "Tangent Plane", plane_tangent)
.at("SOLID", "CONSTRUCT")
.icon("plane")
.icon("plane_tangent")
.params("face: ref (cylinder, cone, sphere or torus); at?: [x,y,z] where it touches (default: the face pick); name?"),
CommandSpec::new("construct.plane.along_path", "Plane Along Path", plane_along)
.at("SOLID", "CONSTRUCT")
.icon("plane")
.icon("plane_along_path")
.params("path: edge or sketch curve ref; t: expr, 0…1 along it (default 0.5); name? The plane is square to the path there"),
CommandSpec::new("construct.plane.perpendicular", "Perpendicular Plane", plane_perpendicular).at("SOLID", "CONSTRUCT").icon("plane").params(
CommandSpec::new("construct.plane.perpendicular", "Perpendicular Plane", plane_perpendicular).at("SOLID", "CONSTRUCT").icon("plane_perpendicular").params(
"plane: plane or planar face ref; line: straight edge, sketch line or axis ref; name? The plane holds the line and is square to the plane",
),
];
+4 -3
View File
@@ -8,20 +8,21 @@ use crate::params::{bad, str_};
use crate::{EngineError, Result, Session, crash};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("diagnostics.crash_reports", "Crash Reports", list).noundo().params(
CommandSpec::new("diagnostics.crash_reports", "Crash Reports", list).icon("bug_report").noundo().params(
"limit?: number (default 20). Lists this computer's crash reports, newest first: {dir, reports: [{id, kind, time, message, \
command, count, version, path, replayable}]}. kind: command_panic | panic | kernel_panic_recovered | unclean_exit. \
Read one with diagnostics.crash_report; reproduce it with `solidcraft-cli crashes replay <id>`",
),
CommandSpec::new("diagnostics.crash_report", "Crash Report", read).noundo().params(
CommandSpec::new("diagnostics.crash_report", "Crash Report", read).icon("bug_report").noundo().params(
"id: report id. The whole report: message, location, backtrace, app version and commit, system, the command that \
crashed and the recent ones, notes, and design_path / replay_path when the crash can be replayed \
(`solidcraft-cli run <replay_path> --in <design_path>`)",
),
CommandSpec::new("diagnostics.crash_reports.clear", "Delete Crash Reports", clear)
.icon("delete")
.noundo()
.params("id?: one report (default: all). Returns {deleted}"),
CommandSpec::new("diagnostics.crash_test", "Crash Test", crash_test).noundo().enabled(crash_test_enabled).params(
CommandSpec::new("diagnostics.crash_test", "Crash Test", crash_test).icon("bug_report").noundo().enabled(crash_test_enabled).params(
"mode: panic | kernel | abort. Checks that crash reports work: panic = an internal error in this command (caught; \
the design is unchanged), kernel = one inside the geometry kernel (caught), abort = the app ends at once (reported \
at the next start). Only when the SOLIDCRAFT_CRASH_TEST environment variable is set",
+18 -18
View File
@@ -12,11 +12,11 @@ use crate::{EngineError, Result, Sel, Session, Snapshot};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("edit.undo", "Undo", undo).icon("undo").key("Ctrl+Z").noundo().doc_only(),
CommandSpec::new("edit.redo", "Redo", redo).icon("redo").key("Ctrl+Y").noundo().doc_only(),
CommandSpec::new("edit.checkpoint", "Checkpoint", checkpoint)
CommandSpec::new("edit.checkpoint", "Checkpoint", checkpoint).icon("checkpoint")
.noundo()
.doc_only()
.params("→ {checkpoint: id}: remember the design, active sketch and undo history (restore with edit.restore_checkpoint)"),
CommandSpec::new("edit.restore_checkpoint", "Restore Checkpoint", restore_checkpoint)
CommandSpec::new("edit.restore_checkpoint", "Restore Checkpoint", restore_checkpoint).icon("restore")
.noundo()
.doc_only()
.params("checkpoint: id; forget?: bool (true = drop it without going back). Goes back to it, undo history included"),
@@ -26,48 +26,48 @@ pub static COMMANDS: &[CommandSpec] = &[
.icon("delete")
.key("Delete")
.params("features: [id or name] (a sketch takes its dependent features with it) → deleted: [ids]; failing?: [{feature, error}] (later features that worked and now fail)"),
CommandSpec::new("timeline.rename", "Rename", rename).params("feature: id|name, name (references to it follow)"),
CommandSpec::new("timeline.rename", "Rename", rename).icon("rename").params("feature: id|name, name (references to it follow)"),
CommandSpec::new("timeline.group", "New Feature Folder", group)
.icon("folder")
.doc_only()
.params("name?; features?: [id|name]; parent?: folder id (features leave any other timeline folder)"),
CommandSpec::new("timeline.ungroup", "Remove Feature Folders", ungroup)
CommandSpec::new("timeline.ungroup", "Remove Feature Folders", ungroup).icon("folder")
.doc_only()
.params("group: id | groups: [ids] (features remain in the timeline)"),
CommandSpec::new("timeline.rename_group", "Rename Feature Folder", rename_group).doc_only().params("group: id, name"),
CommandSpec::new("timeline.move_to_group", "Move Features to Folder", move_to_group)
CommandSpec::new("timeline.rename_group", "Rename Feature Folder", rename_group).icon("rename").doc_only().params("group: id, name"),
CommandSpec::new("timeline.move_to_group", "Move Features to Folder", move_to_group).icon("folder")
.doc_only()
.params("features?: [id|name], groups?: [ids], group?: id (omit or null to move to the timeline root)"),
CommandSpec::new("timeline.move_many", "Move Timeline Items", move_many).params(
CommandSpec::new("timeline.move_many", "Move Timeline Items", move_many).icon("reorder").params(
"features?: [id|name], groups?: [folder ids], group?: folder id, position: insertion slot; atomically changes folder membership and moves all selected contents as one block",
),
CommandSpec::new("timeline.rollback", "Move Timeline Marker", rollback).params("position: number of features to keep active (omit = end)"),
CommandSpec::new("timeline.roll_to", "Roll History Marker", rollback)
CommandSpec::new("timeline.rollback", "Move Timeline Marker", rollback).icon("rollback").params("position: number of features to keep active (omit = end)"),
CommandSpec::new("timeline.roll_to", "Roll History Marker", rollback).icon("rollback")
.params("position: number of features to keep active (omit = end) | feature: id|name (the marker goes right after it)"),
CommandSpec::new("timeline.reorder", "Reorder Feature", reorder)
CommandSpec::new("timeline.reorder", "Reorder Feature", reorder).icon("reorder")
.params("feature: id|name, position: its new index; refused when a feature that worked would fail"),
CommandSpec::new("timeline.reorder_many", "Reorder Features", reorder_many).params(
CommandSpec::new("timeline.reorder_many", "Reorder Features", reorder_many).icon("reorder").params(
"features: [id|name], position: insertion slot in the current history; selected features keep their relative order; refused when a feature that worked would fail",
),
CommandSpec::new("timeline.redefine", "Edit Feature", redefine)
CommandSpec::new("timeline.redefine", "Edit Feature", redefine).icon("edit")
.params("feature: id|name, command: the feature's command id, params: its parameters (the feature is rebuilt in place)"),
CommandSpec::new("timeline.dependents", "Feature Dependents", dependents)
CommandSpec::new("timeline.dependents", "Feature Dependents", dependents).icon("graph")
.noundo()
.params("feature: id|name → features deleted with it, and features that would fail without it"),
CommandSpec::new("timeline.suppress", "Suppress Feature", suppress).params("feature: id|name, suppressed?: bool (default toggles)"),
CommandSpec::new("timeline.edit", "Edit Feature", edit_feature).params(
CommandSpec::new("timeline.suppress", "Suppress Feature", suppress).icon("suppress").params("feature: id|name, suppressed?: bool (default toggles)"),
CommandSpec::new("timeline.edit", "Edit Feature", edit_feature).icon("edit").params(
"feature: id|name, set: {fields to change, merged into the feature's definition as timeline.inspect returns it, \
e.g. {\"extent\": {\"distance\": \"30\"}} (extrude), {\"angle\": \"90 deg\"} (revolve); \
expressions are text} → recomputed: features rebuilt, restored: features whose earlier result for the same inputs was reused",
),
CommandSpec::new("timeline.inspect", "Inspect Feature", inspect_feature).noundo().params(
CommandSpec::new("timeline.inspect", "Inspect Feature", inspect_feature).icon("inspect").noundo().params(
"feature: id|name → type, definition (the fields timeline.edit's set changes, with their current values), \
inputs: [{label, param, expression, unit}] (named inputs parameters.change can also set), error, warning",
),
CommandSpec::new("select.set", "Select", select_set)
CommandSpec::new("select.set", "Select", select_set).icon("select")
.noundo()
.params("items: [{type: body|edge|face|feature|sketch_curve|sketch_point|profile, …}], add?: bool"),
CommandSpec::new("select.clear", "Clear Selection", select_clear).noundo(),
CommandSpec::new("select.clear", "Clear Selection", select_clear).icon("select").noundo(),
];
fn undo(s: &mut Session, _p: &Value) -> Result<Value> {
+6 -6
View File
@@ -48,7 +48,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("features: [names] | bodies: [names] | components: [names]; axis: X|Y|Z | {origin, dir}; count; angle? (default 360 deg; partial arcs include both ends); alignment?: left|center|right (default right; original stays fixed); reversed?: bool (Flip; selects the extra side for even centered counts)"),
CommandSpec::new("solid.pattern.path", "Pattern on Path", pattern_path)
.at("SOLID", "CREATE")
.icon("pattern_rect")
.icon("pattern_path")
.params("features: [names] | bodies: [names]; path_sketch: sketch; path: [curve ids in order]; count; spacing (between instances) | distance (first to last); orientation?: identical|path; flip?: bool"),
CommandSpec::new("solid.mirror", "Mirror", mirror).at("SOLID", "CREATE").icon("mirror").params("features: [names] | bodies: [names] (combine?: join with the original); plane: XY|XZ|YZ|construction plane name or id|{origin, normal}|{origin, x_dir, y_dir} (origin coordinates may be length expressions; the mirror follows them, or a construction plane)"),
CommandSpec::new("solid.hole", "Hole", hole)
@@ -62,7 +62,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("path_sketch: sketch; path: [curve ids in order]; diameter: expr; wall?: expr (hollow); operation?, targets?, name?, body_name?"),
CommandSpec::new("solid.bounding_solid", "Bounding Solid", bounding_solid)
.at("SOLID", "CREATE")
.icon("box")
.icon("bounding_solid")
.params("bodies?: [names] (default: all); margin?: expr; name?, body_name?"),
CommandSpec::new("solid.scale", "Scale", scale)
.at("SOLID", "MODIFY")
@@ -70,7 +70,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("bodies: [names]; factor: expr | factors: [x, y, z] exprs; origin?: [x,y,z]"),
CommandSpec::new("face.delete", "Delete Face", delete_face)
.at("SOLID", "MODIFY")
.icon("delete")
.icon("delete_face")
.params("faces: [[x,y,z] points on the faces, or face names from model.faces]; body?; heal?: bool (default true: the neighbouring planes close the gap, as when a rounded edge, hole or boss is taken off; false: the gap stays open and the faces left are a surface body)"),
CommandSpec::new("solid.offset_face", "Offset Face", offset_face)
.at("SOLID", "MODIFY")
@@ -94,17 +94,17 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("base: XY|XZ|YZ|plane name, or a planar face {face: [x,y,z], body?, index?, path?}; offset: expr; name?"),
CommandSpec::new("construct.plane.at_angle", "Plane at Angle", plane_angle)
.at("SOLID", "CONSTRUCT")
.icon("plane")
.icon("plane_angle")
.params("base: XY|XZ|YZ|plane name; axis: X|Y|Z|{origin, dir}; angle: expr; name?"),
CommandSpec::new("solid.split_face", "Split Face", split_face)
.at("SOLID", "MODIFY")
.icon("split")
.icon("split_face")
.params(
"faces: [[x,y,z] points on the faces to split, or face names from model.faces]; body?; and the splitting tool: plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended) — or sketch: id or name (its curves, along the sketch's normal)",
),
CommandSpec::new("solid.split_body", "Split Body", split_body)
.at("SOLID", "MODIFY")
.icon("split")
.icon("split_body")
.params("body: name; plane: XY|XZ|YZ|plane name|{origin, normal} — or tool: {body, point} (a face of a body, its surface extended) — or sketch: id, line: curve id (a straight sketch line, along the sketch's normal)"),
CommandSpec::new("solid.press_pull", "Press Pull", press_pull)
.at("SOLID", "MODIFY")
+3 -3
View File
@@ -28,7 +28,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("sketch, curves: [open curve ids, in order]; thickness (across the sketch plane); depth? (default: to the next face); flip?: bool (fill to the other side)"),
CommandSpec::new("solid.web", "Web", web)
.at("SOLID", "CREATE")
.icon("rib")
.icon("web")
.params("sketch, curves: [open curve ids] (one wall each); thickness; depth? (default: to the next face); flip?: bool"),
CommandSpec::new("solid.emboss", "Emboss", emboss)
.at("SOLID", "CREATE")
@@ -36,11 +36,11 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("sketch, profiles?; depth; mode?: emboss|deboss; targets?; face?: world point [x,y,z] selecting a planar or complete exterior cylindrical wall (inferred when exactly one supported tangent cylinder matches the selected profiles)"),
CommandSpec::new("solid.replace_face", "Replace Face", replace_face)
.at("SOLID", "MODIFY")
.icon("offset_face")
.icon("replace_face")
.params("faces: [[x,y,z] points on planar faces, or face names from model.faces]; target: plane (XY|XZ|YZ|construction plane|{origin, normal}) | target_face: [x,y,z] on a parallel planar face; body?"),
CommandSpec::new("solid.align", "Align", align)
.at("SOLID", "MODIFY")
.icon("move")
.icon("align")
.params("bodies: [names]; from: [x,y,z] | {snap: vertex|edge_mid|face_center|circle_center, at: [x,y,z]} | from_face: [x,y,z]; to: (the same) | to_face: [x,y,z] (faces also turn to meet); flip?: bool"),
CommandSpec::new("solid.remove", "Remove", remove).at("SOLID", "MODIFY").icon("delete").params("bodies: [names] (removed from here on in the timeline)"),
];
+5 -5
View File
@@ -38,23 +38,23 @@ pub static COMMANDS: &[CommandSpec] = &[
.icon("save")
.noundo()
.params("path; format?: container|json (default: json for .json, otherwise container); compression?: fast|compact (default: fast); cache?: bool (warm native geometry, default: false); preview?: bool (saved display meshes, default: false)"),
CommandSpec::new("doc.recovery_list", "Recoverable Designs", recovery_list)
CommandSpec::new("doc.recovery_list", "Recoverable Designs", recovery_list).icon("restore")
.noundo()
.params("dir? (default: the recovery folder) → designs autosaved by apps that crashed or closed with unsaved changes"),
CommandSpec::new("doc.recover", "Recover Design", recover)
CommandSpec::new("doc.recover", "Recover Design", recover).icon("restore")
.noundo()
.params("id (from doc.recovery_list); dir?; discard?: bool (delete the entry once open) — opens it with its file path, unsaved"),
CommandSpec::new("doc.recovery_discard", "Discard Recovered Design", recovery_discard).noundo().params("id, or all: true; dir?"),
CommandSpec::new("doc.recovery_discard", "Discard Recovered Design", recovery_discard).icon("delete").noundo().params("id, or all: true; dir?"),
CommandSpec::new("file.export", "Export", export)
.icon("export")
.noundo()
.params("path; format?: stl|stla|obj|step|3mf|3mf-tagged|urdf|json (default from extension; 3mf-tagged: 3MF whose triangles carry their B-rep face, at full precision, for meshing and analysis tools; docs/formats/3mf-face-tags.md; urdf: the whole assembly as a robot, with STL meshes written next to it; docs/urdf.md); bodies?: [names] (JSON and URDF export the complete design); urdf only: mesh_prefix? (before each mesh file name, e.g. package://robot/meshes/), effort? (default 1000), velocity? (default 10)"),
CommandSpec::new("file.save_mesh", "Save As Mesh", save_mesh)
.at("MESH", "MODIFY")
.icon("export")
.icon("mesh_export")
.noundo()
.params("path; format?: stl|3mf|3mf-tagged (default from the extension, else stl; 3mf-tagged: docs/formats/3mf-face-tags.md); bodies?: [names] (default: all); ascii?: bool (STL); slicer?: id, name or program path from file.slicers (open the saved file there)"),
CommandSpec::new("file.slicers", "Installed Slicers", slicers)
CommandSpec::new("file.slicers", "Installed Slicers", slicers).icon("export")
.noundo()
.params("→ the slicers found on this machine: [{id, name, icon?}]; pass an id or name as file.save_mesh's `slicer`"),
];
+9 -9
View File
@@ -14,14 +14,14 @@ pub static COMMANDS: &[CommandSpec] = &[
.key("I")
.noundo()
.params("bodies?: [names] (default all); or items: [1-2 selections] → length/area/volume; diameter, radius, circumference or arc_length/angle_deg of circles, arcs and cylinders; slot sizes; min (distance_mm, from, to), max, center_distance (centre to centre, or from an axis) and angle"),
CommandSpec::new("document.inspect", "Inspect Design", inspect).noundo().params("measure?: bool (include body measurements)"),
CommandSpec::new("sketch.inspect", "Inspect Sketch", sketch_inspect).noundo().params("sketch?: id|name (default active)"),
CommandSpec::new("model.edges", "List Edges", model_edges).noundo().params("body: name"),
CommandSpec::new("model.faces", "List Faces", model_faces).noundo().params(
CommandSpec::new("document.inspect", "Inspect Design", inspect).icon("inspect").noundo().params("measure?: bool (include body measurements)"),
CommandSpec::new("sketch.inspect", "Inspect Sketch", sketch_inspect).icon("inspect").noundo().params("sketch?: id|name (default active)"),
CommandSpec::new("model.edges", "List Edges", model_edges).icon("inspect").noundo().params("body: name"),
CommandSpec::new("model.faces", "List Faces", model_faces).icon("inspect").noundo().params(
"body: name → per face: index, name (what face.delete, sheet.convert `face`, solid.shell … take in place of a point), exact, area, centroid (not always on the face), \
plane_normal, point (a point on the face that picks it), cylinder? ({origin, axis, radius} of a cylindrical face)",
),
CommandSpec::new("model.threads", "List Threads", model_threads).noundo(),
CommandSpec::new("model.threads", "List Threads", model_threads).icon("inspect").noundo(),
CommandSpec::new("material.assign", "Physical Material", physical_material)
.at("SOLID", "MODIFY")
.icon("material")
@@ -31,17 +31,17 @@ pub static COMMANDS: &[CommandSpec] = &[
appearance?: library name (appearance.library) | color: \"#rrggbb\" or [r, g, b] (0–255), opacity? (0–1, default 1); \
pbr?: {metallic:0..1, roughness:0.045..1, dielectric_ior:1..3} (requires color/appearance and opacity 1); omitted/null uses legacy scalar shading. Neither appearance nor color (or color: null): clear, so the face follows its body, the body its component, the component its material",
),
CommandSpec::new("appearance.library", "Appearance Library", appearance_library)
CommandSpec::new("appearance.library", "Appearance Library", appearance_library).icon("library")
.noundo()
.params("→ the built-in appearances (name, colour, opacity)"),
CommandSpec::new("appearance.list", "List Appearances", appearance_list)
CommandSpec::new("appearance.list", "List Appearances", appearance_list).icon("list")
.noundo()
.params("body?: name → assigned appearances; with body, its resolved look and its faces' (face index, look)"),
CommandSpec::new("material.list", "List Materials", material_list)
CommandSpec::new("material.list", "List Materials", material_list).icon("list")
.noundo()
.doc_only()
.params("→ the built-in materials, then the design's own (user: true, with their look)"),
CommandSpec::new("engine.commands", "List Commands", commands).noundo(),
CommandSpec::new("engine.commands", "List Commands", commands).icon("list").noundo(),
];
pub fn measure_json(b: &solidcraft_doc::ModelBody) -> Value {
+6 -6
View File
@@ -37,7 +37,7 @@ pub static COMMANDS: &[CommandSpec] = &[
reason: why the joint can move nothing (suppressed, its origins lost, or its chain joined to nothing grounded → unconnected: \
the occurrence named)",
),
CommandSpec::new("joint.reach", "Reach Point", reach).params(
CommandSpec::new("joint.reach", "Reach Point", reach).icon("reach").params(
"occurrence: id|name; point: [x,y,z] (where its point goes, world); at?: [x,y,z] (its point, in the world where it is now; default: the centre of its bodies' box); z?: [x,y,z] (the way its axis then points); axis?: [x,y,z] (that axis, in the world now; default: its own z); joints?: [ids or names] (only these move toward the point; the other joints of a closed loop they are in follow); quiet?: bool; continuous?: bool (a frame of a drag: the answer says continuous, and the app's command line doesn't echo it). Moves the values of the joints on its chain from where they are to the nearest pose, within their limits, through motion links, keeping closed loops closed as joint.drive does (contact sets are not checked) → reached; error_mm (and error_deg with z): how close it got; point: where its point is; values: [{joint, name, values}] of the joints that moved, as joint.list gives them; moved; stopped_by_limit: [{joint, value, limit: min|max}]: the limits it rests on short of the point; stopped_by_loop: {joint}: the closed loop that stopped it (its chain alone gets closer); reason: why nothing can move",
),
CommandSpec::new("joint.motion_link", "Motion Link", motion_link)
@@ -64,14 +64,14 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("joint.cam", "Cam", cam_cmd).at("SOLID", "ASSEMBLE").icon("cam").params(
"a: the cam's joint (revolute); b: the follower's joint, a slider (a translating follower) or a revolute (an oscillating one), b driven by a; profile: {sketch: id|name, index?: the profile (default: the largest)} | {edges: [[x,y,z]…] (points on edges of the cam's bodies forming one closed loop, world), occurrence?}: the cam's outline, in a plane square to its axis, read again whenever the design changes; follower?: knife|roller|flat (default knife; flat: a face square to a slider's line); radius?: the roller's radius (mm, an expression or {circle: [x,y,z], occurrence?}: the roller's round face, which is its tip too); tip?: a joint origin where the follower touches: its knife's point or its roller's centre, such as {circle: [x,y,z]} on the roller (needed for an oscillating follower; a translating one's line runs through it, by default along its slider's axis); flip?: bool (ride the cam from the other side than the one the follower is on now); link?: index (replace that motion link) → link, lift: {min, max, rise, unit} (b's travel over a turn of the cam), reverse (the side it rides from), follower_offset (mm: how far a translating follower's line passes from the cam's axis); b keeps where it is",
),
CommandSpec::new("joint.limits", "Joint Limits", limits).params("joint; index?: value index (default 0); min?, max? (omit both to clear)"),
CommandSpec::new("joint.edit", "Edit Joint", edit).params("joint; type?, flip?, offset?, angle?, suppressed?, name?"),
CommandSpec::new("joint.delete", "Delete Joint", delete).params("joint: id|name (or origin: name, link: index)"),
CommandSpec::new("joint.list", "List Joints", list).noundo().params(
CommandSpec::new("joint.limits", "Joint Limits", limits).icon("joint").params("joint; index?: value index (default 0); min?, max? (omit both to clear)"),
CommandSpec::new("joint.edit", "Edit Joint", edit).icon("edit").params("joint; type?, flip?, offset?, angle?, suppressed?, name?"),
CommandSpec::new("joint.delete", "Delete Joint", delete).icon("delete").params("joint: id|name (or origin: name, link: index)"),
CommandSpec::new("joint.list", "List Joints", list).icon("list").noundo().params(
"→ joints (type, occurrences, values, limits), origins, links, degrees of freedom, conflicts, floating: [{occurrences, joints}] \
(joint chains joined to nothing grounded: the solver places none of them, so driving them moves nothing)",
),
CommandSpec::new("joint.solve", "Solve Joints", solve_cmd).params("re-place the occurrences from the joints → conflicts, errors, dof"),
CommandSpec::new("joint.solve", "Solve Joints", solve_cmd).icon("compute").params("re-place the occurrences from the joints → conflicts, errors, dof"),
CommandSpec::new("inspect.interference", "Interference", interference).at("SOLID", "INSPECT").icon("interference").noundo().params(
"bodies?: [names] (default: all, in world placement) → interferences: pairs that overlap with the overlap volume, area, bounding box and centre; unchecked: pairs that could not be checked, with the reason. Bodies are pruned by bounding box first, so any number of bodies is fine; at most 20000 pairs whose boxes overlap are intersected per call (truncated: true and skipped_pairs: n say when more were left out)",
),
+3 -3
View File
@@ -6,11 +6,11 @@ use serde_json::{Value, json};
use solidcraft_io::catalog::{Catalog, Definition, Provenance};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("library.inspect", "Inspect Local Library", inspect).noundo().doc_only()
CommandSpec::new("library.inspect", "Inspect Local Library", inspect).icon("inspect").noundo().doc_only()
.params("path: scalar library JSON; returns validated catalog and exact entry revisions, without changing the design"),
CommandSpec::new("library.export", "Export Design Library", export).noundo().doc_only()
CommandSpec::new("library.export", "Export Design Library", export).icon("export").noundo().doc_only()
.params("path: .json; id? (default user.local), name?, provenance?, overwrite? false. Copies assigned looks and design-local physical definitions; never changes the design"),
CommandSpec::new("library.apply", "Apply Local Library Entry", apply).enabled(super::not_in_sketch)
CommandSpec::new("library.apply", "Apply Local Library Entry", apply).icon("import").enabled(super::not_in_sketch)
.params("path, entry: qualified ID, revision: exact content SHA-256; targets?: {bodies?, faces?: [{body,point}], components?}; name?: physical material name on import. Copies values in one undo step; material conflicts are explicit"),
];
+1 -1
View File
@@ -21,7 +21,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("material.edit", "Edit Physical Material", edit).icon("material").enabled(has_user_materials).params(
"material: a material defined in this design; name? (bodies and configuration cells follow); density_g_cm3?; appearance? | color?, opacity?, pbr? (as material.create; color: null clears the look). Omitted appearance/color retains the entire current look",
),
CommandSpec::new("material.delete", "Delete Physical Material", delete).enabled(has_user_materials).params(
CommandSpec::new("material.delete", "Delete Physical Material", delete).icon("delete").enabled(has_user_materials).params(
"material: a material defined in this design; replace?: material for the bodies and configuration cells that use it (without it, a material in use is refused)",
),
];
+1
View File
@@ -22,6 +22,7 @@ pub static COMMANDS: &[CommandSpec] = &[
reverse-DNS key such as com.example.tool (an object; null removes) → metadata, changed: [keys]",
),
CommandSpec::new("document.metadata.remove", "Remove Design Property", remove)
.icon("delete")
.doc_only()
.params("key, or keys: [keys] (any key, also one another tool wrote) → metadata, removed: [keys that were there]"),
];
+25 -18
View File
@@ -30,35 +30,41 @@ macro_rules! trace_doc {
}
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("contact.enable_sets", "Enable Contact Sets", enable_sets).at("SOLID", "ASSEMBLE").icon("joint").params(
CommandSpec::new("contact.enable_sets", "Enable Contact Sets", enable_sets).at("SOLID", "ASSEMBLE").icon("contact_sets").params(
"enabled?: bool (default true) — driving joints and motion studies stop where bodies in one contact set would pass through each other",
),
CommandSpec::new("contact.enable_all", "Enable All Contact", enable_all)
.at("SOLID", "ASSEMBLE")
.icon("joint")
.icon("contact_all")
.params("every occurrence takes part in contact (one set of all)"),
CommandSpec::new("contact.disable_all", "Disable Contact", disable_all).at("SOLID", "ASSEMBLE").icon("joint").params("contact off"),
CommandSpec::new("contact.disable_all", "Disable Contact", disable_all).at("SOLID", "ASSEMBLE").icon("contact_off").params("contact off"),
CommandSpec::new("contact.create", "New Contact Set", new_set)
.at("SOLID", "ASSEMBLE")
.icon("joint")
.icon("contact_new")
.params("occurrences: [ids or names] (two or more); name?"),
CommandSpec::new("contact.edit", "Edit Contact Set", edit_set).params("set: id|name; occurrences?; name?; suppressed?: bool; delete?: bool"),
CommandSpec::new("contact.list", "List Contact Sets", list_sets).noundo().doc_only().params("→ sets, enabled, all"),
CommandSpec::new("contact.check", "Check Contact", check).noundo().params("→ pairs in contact sets that pass through each other now"),
CommandSpec::new("motion.study", "Motion Study", study).at("SOLID", "ASSEMBLE").icon("joint").params(
CommandSpec::new("contact.edit", "Edit Contact Set", edit_set)
.icon("edit")
.params("set: id|name; occurrences?; name?; suppressed?: bool; delete?: bool"),
CommandSpec::new("contact.list", "List Contact Sets", list_sets).icon("list").noundo().doc_only().params("→ sets, enabled, all"),
CommandSpec::new("contact.check", "Check Contact", check)
.icon("interference")
.noundo()
.params("→ pairs in contact sets that pass through each other now"),
CommandSpec::new("motion.study", "Motion Study", study).at("SOLID", "ASSEMBLE").icon("play").params(
"name?; study? (edit an existing one); steps? (timeline length, default 100); keys: [{joint, index?, points: [[step, value], …]}] \
(values: degrees for angles, mm for distances, or expressions)",
),
CommandSpec::new("motion.play", "Play Motion Study", play).params(
CommandSpec::new("motion.play", "Play Motion Study", play).icon("play").params(
"study: id|name; step (0…steps) — sets the joints to the study's values there (contact sets stop them); quiet?: bool \
(positions of only the occurrences that moved) → values: [{joint, values: [{name, value, unit}]}], positions, moved: whether \
any occurrence moved, unmoved: [{joint, name, reason}] (keyed joints that can move nothing, such as a chain joined to \
nothing grounded)",
),
CommandSpec::new("motion.export", "Export Motion Study", export)
.icon("export")
.noundo()
.params("study; samples? (default steps + 1); path? (.json or .csv) → occurrence positions per step (the design is not changed)"),
CommandSpec::new("motion.export_animation", "Export Motion Animation", animation::export).noundo().params(
CommandSpec::new("motion.export_animation", "Export Motion Animation", animation::export).icon("export").noundo().params(
"study: id|name, or joint: id|name with from and to (degrees for angles, mm for distances, or expressions) and index?; path: a .gif file, or a folder for PNG frames (frame-0001.png, frame-0002.png, …); samples? (frames, 2…1000; default the study's steps + 1, or 100); width?, height? (16…4096 pixels, default 800 × 500); camera?: {target, yaw, pitch, roll?, distance, fov?, up?} (ui.camera's reply; fields left out keep the default view: view.apply's camera, or the home view framing the model); fps? (1…50, default 20); grid?, axes? (default true: the ground grid and the origin's axes) - draws the study as it plays, or the joint as it is driven, with the headless renderer → path, format, frames, width, height, fps (GIF), bytes, renderer, stopped_at, stopped_by_loop, stopped_by_contact (the design is not changed)",
),
CommandSpec::new("motion.trace", "Motion Trace", trace).at("SOLID", "ASSEMBLE").icon("trace").noundo().params(concat!(
@@ -66,24 +72,25 @@ pub static COMMANDS: &[CommandSpec] = &[
" → points: [[x,y,z]…] world positions, samples (taken), stopped_at (the sample where the motion stopped short; the trace \
ends there), stopped_by_loop, stopped_by_contact (the design is not changed)"
)),
CommandSpec::new("motion.trace.keep", "Keep Motion Trace", keep_trace).params(concat!(
CommandSpec::new("motion.trace.keep", "Keep Motion Trace", keep_trace).icon("trace").params(concat!(
trace_doc!(),
"; name? (default Motion TraceN) - keeps the trace as a 3D sketch spline through its points (a new sketch with 3D Sketch \
on, in the root component, where no joint moves it; a fixed curve, it does not follow later changes) → sketch, name, curves, points"
)),
CommandSpec::new("motion.plot", "Plot Motion Study", analysis::plot).noundo().params(analysis::PLOT_DOC),
CommandSpec::new("motion.interference", "Motion Interference", analysis::interference).noundo().params(analysis::INTERFERENCE_DOC),
CommandSpec::new("motion.list", "List Motion Studies", list_studies).noundo(),
CommandSpec::new("motion.delete", "Delete Motion Study", delete_study).params("study"),
CommandSpec::new("explode.create", "Exploded View", explode_create).at("SOLID", "ASSEMBLE").icon("joint").params(
CommandSpec::new("motion.plot", "Plot Motion Study", analysis::plot).icon("graph").noundo().params(analysis::PLOT_DOC),
CommandSpec::new("motion.interference", "Motion Interference", analysis::interference).icon("interference").noundo().params(analysis::INTERFERENCE_DOC),
CommandSpec::new("motion.list", "List Motion Studies", list_studies).icon("list").noundo(),
CommandSpec::new("motion.delete", "Delete Motion Study", delete_study).icon("delete").params("study"),
CommandSpec::new("explode.create", "Exploded View", explode_create).at("SOLID", "ASSEMBLE").icon("explode").params(
"name?; scale? (automatic: each occurrence pushed away from the assembly's centre by scale × its offset, default 1); \
moves?: [{occurrence, translate: [x,y,z]}] (instead of automatic)",
),
CommandSpec::new("explode.show", "Show Exploded View", explode_show)
.icon("explode")
.noundo()
.params("view: id|name (or none: back to the assembled placement) — shown only; the design keeps its placements"),
CommandSpec::new("explode.list", "List Exploded Views", explode_list).noundo(),
CommandSpec::new("explode.delete", "Delete Exploded View", explode_delete).params("view"),
CommandSpec::new("explode.list", "List Exploded Views", explode_list).icon("list").noundo(),
CommandSpec::new("explode.delete", "Delete Exploded View", explode_delete).icon("delete").params("view"),
];
const MAX_ITEMS: usize = 10_000;
+15 -5
View File
@@ -15,34 +15,44 @@ pub static COMMANDS: &[CommandSpec] = &[
favorite?: bool, new_name?: rename (references follow); or delete: name → recomputed: features rebuilt, \
restored: features whose earlier result for the same inputs was reused (e.g. a value set back)",
),
CommandSpec::new("parameters.list", "List Parameters", list).noundo().doc_only().params(
CommandSpec::new("parameters.list", "List Parameters", list).icon("list").noundo().doc_only().params(
"favorites?: bool (only favourites), filter?: all|user|sketch|feature|favorites → parameters (name, expression, unit, value in its unit, \
source, feature/input, comment, favorite, uses, used_by, error) and cycles",
),
CommandSpec::new("parameters.add", "Add User Parameter", add).params("name, expression, unit?, comment?, favorite?: bool"),
CommandSpec::new("parameters.add", "Add User Parameter", add).icon("new").params("name, expression, unit?, comment?, favorite?: bool"),
CommandSpec::new("parameters.rename", "Rename Parameter", rename)
.icon("rename")
.params("name, new_name (expressions, feature inputs and dimensions that use it follow)"),
CommandSpec::new("parameters.delete", "Delete Parameter", delete).params("name (refused while something uses it; the error lists the users)"),
CommandSpec::new("parameters.favorite", "Favourite Parameter", favorite).params("name, favorite?: bool (default true)"),
CommandSpec::new("parameters.delete", "Delete Parameter", delete)
.icon("delete")
.params("name (refused while something uses it; the error lists the users)"),
CommandSpec::new("parameters.favorite", "Favourite Parameter", favorite).icon("star").params("name, favorite?: bool (default true)"),
CommandSpec::new("parameters.move", "Move Parameter", move_param)
.icon("reorder")
.params("name, before | after: another user parameter; the user parameter is listed just before or after it"),
CommandSpec::new("parameters.comment", "Comment Parameter", comment).params("name, comment"),
CommandSpec::new("parameters.comment", "Comment Parameter", comment).icon("text").params("name, comment"),
CommandSpec::new("parameters.users", "Parameter Users", users)
.icon("graph")
.noundo()
.params("name → users (parameters, feature inputs, dimensions) and the features it drives"),
CommandSpec::new("parameters.graph", "Parameter Graph", graph)
.icon("graph")
.noundo()
.params("→ nodes, edges [user, used] (users include Feature:<name>), cycles, errors"),
CommandSpec::new("parameters.export", "Export Parameters", export)
.icon("export")
.noundo()
.params("format?: csv|json (default csv, or from the path), path?: write to a file → text"),
CommandSpec::new("parameters.import", "Import Parameters", import)
.icon("import")
.params("text: CSV (header with name, expression, unit?, comment?) or JSON [{name, expression, unit?, comment?}] | path; all or nothing"),
CommandSpec::new("expr.evaluate", "Evaluate Expression", evaluate)
.icon("params")
.noundo()
.doc_only()
.params("expression, kind?: length|angle|unitless (or unit?: mm|in|deg…) → ok, value (mm/rad), display_value, references, error"),
CommandSpec::new("parameters.complete", "Complete Expression", complete)
.icon("params")
.noundo()
.doc_only()
.params("prefix?: the text before the cursor → word, suggestions [{text, kind: parameter|function|unit|constant, detail}]"),
+2 -2
View File
@@ -15,7 +15,7 @@ use crate::params::{bad, num, str_, vec3};
use crate::{EngineError, Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("parts.library", "Standard Parts", library)
CommandSpec::new("parts.library", "Standard Parts", library).icon("library")
.noundo()
.params("family? → families (name, standard, sizes, lengths) and, with family, each size's dimensions (mm)"),
CommandSpec::new("parts.fastener", "Insert Fastener", insert).at("SOLID", "INSERT").icon("fastener").params(
@@ -26,7 +26,7 @@ pub static COMMANDS: &[CommandSpec] = &[
length? (mm; screws, bolts, set screws, pins; default a standard length near 2.5 d); \
at?: [x,y,z] on a hole's rim or wall (the part seats on the hole's face with a rigid joint) | point?: [x,y,z] with direction?: [x,y,z] (no joint); name?",
),
CommandSpec::new("parts.resize", "Change Part Size", resize)
CommandSpec::new("parts.resize", "Change Part Size", resize).icon("scale")
.params("component: id|name of an inserted part; size?; length? — rebuilds it (joints keep it seated)"),
];
+2 -2
View File
@@ -21,7 +21,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("plastic.lip", "Lip", lip).at("PLASTIC", "CREATE").icon("lip").params(
"face: [x,y,z] on the rim face of a shelled body; width, height; type?: lip|groove; gap? (groove clearance); side?: inside|outside (which rim edge); body?",
),
CommandSpec::new("plastic.snap_fit", "Snap Fit", snap_fit).at("PLASTIC", "CREATE").icon("snap").params(
CommandSpec::new("plastic.snap_fit", "Snap Fit", snap_fit).at("PLASTIC", "CREATE").icon("snap_fit").params(
"position: [x,y,z] on a face; direction? (default: out of the face); hook: [x,y,z] (the catch's direction); length, thickness, width, catch_depth, catch_length; body?",
),
CommandSpec::new("plastic.rest", "Rest", rest).at("PLASTIC", "CREATE").icon("rest").params(
@@ -36,7 +36,7 @@ pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("plastic.assign_rule", "Assign Plastic Rule", assign_rule).at("PLASTIC", "SETUP").icon("plastic_assign").params(
"bodies: [names]; rule: name (empty: no rule) — plastic features on these bodies take its draft and clearance as defaults",
),
CommandSpec::new("plastic.rules", "List Plastic Rules", list_rules).noundo().params("→ rules with their expressions and values, the active rule and the rules assigned to bodies"),
CommandSpec::new("plastic.rules", "List Plastic Rules", list_rules).icon("plastic_rule").noundo().params("→ rules with their expressions and values, the active rule and the rules assigned to bodies"),
];
fn rule_json(s: &Session, r: &PlasticRule) -> Value {
+10 -10
View File
@@ -13,27 +13,27 @@ use crate::{Result, Session};
mod jobs;
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("render.preflight", "Check Render Request", jobs::preflight).noundo().doc_only()
CommandSpec::new("render.preflight", "Check Render Request", jobs::preflight).icon("inspect").noundo().doc_only()
.params("setup?: saved name OR settings?: full RenderSetup; transparent_background?: false; replace?: false. Validates the installed image host and bounded captured product scene without starting work. Stale geometry is rejected. See docs/render-jobs.md"),
CommandSpec::new("render.start", "Render Image", jobs::start).noundo().doc_only().at("RENDER", "IMAGE").icon("box")
CommandSpec::new("render.start", "Render Image", jobs::start).noundo().doc_only().at("RENDER", "IMAGE").icon("render_image")
.params("Same request as render.preflight. Captures an immutable product scene and queues a bounded image job. Returns its session-scoped job identity, captured scene_generation and progress. Does not change the design. See docs/render-jobs.md"),
CommandSpec::new("render.status", "Render Progress", jobs::status).noundo().doc_only()
CommandSpec::new("render.status", "Render Progress", jobs::status).icon("list").noundo().doc_only()
.params("job: {session, serial, generation}. Non-blocking progress, error and completed frame metadata; no image bytes and no rendering in this call."),
CommandSpec::new("render.cancel", "Cancel Render", jobs::cancel).noundo().doc_only()
CommandSpec::new("render.cancel", "Cancel Render", jobs::cancel).icon("delete").noundo().doc_only()
.params("job: {session, serial, generation}. Requests cooperative cancellation; completed jobs remain completed. Late cancelled frames cannot be published."),
CommandSpec::new("render.export", "Export Render Image", jobs::export).noundo().doc_only()
CommandSpec::new("render.export", "Export Render Image", jobs::export).icon("export").noundo().doc_only()
.params("job: {session, serial, generation}, path: PNG destination, overwrite?: false. Exports a retained completed image atomically; an existing destination is preserved on failure. See docs/render-jobs.md"),
CommandSpec::new("render.backends", "Render Backends", backends)
CommandSpec::new("render.backends", "Render Backends", backends).icon("list")
.noundo()
.doc_only()
.params("No parameters. Returns {schema_version: 1, default_backend: builtin, backends: [{id, label, availability, executors: [{id, availability, profiles, limits, scratch}]}]}. Metadata only; does not start rendering or probe a GPU. See docs/render-backends.md"),
CommandSpec::new("render.settings.get", "Render Settings", get_settings).noundo().doc_only()
CommandSpec::new("render.settings.get", "Render Settings", get_settings).icon("settings").noundo().doc_only()
.params("name?: saved setup. Without name returns {setups, defaults}; with name returns {name, setup}. Does not evaluate the model or apply a camera. See docs/render-settings.md"),
CommandSpec::new("render.settings.set", "Save Render Settings", set_settings).doc_only()
CommandSpec::new("render.settings.set", "Save Render Settings", set_settings).icon("settings").doc_only()
.params("name: setup name, set: object of fields to replace (backend, mode, camera, width, height, format, exposure_ev, environment_rotation, environment_intensity, background). Creates from defaults, validates atomically, one undo step; identical edits are no-ops. See docs/render-settings.md"),
CommandSpec::new("render.scene", "Inspect Render Scene", scene).noundo().at("RENDER", "SCENE").icon("box")
CommandSpec::new("render.scene", "Inspect Render Scene", scene).noundo().at("RENDER", "SCENE").icon("render_scene")
.params("No parameters. Authoritative product-scene metadata, without modelling overlays or mesh payloads. Explicitly settles stale geometry; does not render an image. See docs/render-workspace.md"),
CommandSpec::new("render.settings.apply", "Apply Render Camera", apply_settings).noundo().doc_only()
CommandSpec::new("render.settings.apply", "Apply Render Camera", apply_settings).icon("camera").noundo().doc_only()
.params("name: saved setup. Applies its existing Camera snapshot including roll/up as view state, without modifying the document or starting a render."),
];
+2 -2
View File
@@ -14,12 +14,12 @@ use crate::params::{bad, str_};
use crate::{EngineError, Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("timeline.add", "Add Recorded Feature", add).params(
CommandSpec::new("timeline.add", "Add Recorded Feature", add).icon("new").params(
"feature: a feature as the design file and timeline.inspect hold it ({type, name?, id?, …}; id and name are kept when free, \
so later steps can refer to them); params?: [{name, expression, unit?, comment?}] (the model parameters its sketch \
dimensions use); the feature goes at the marker → feature, name, error, warning",
),
CommandSpec::new("timeline.export_script", "Export Command Script", export_script).noundo().doc_only().params(
CommandSpec::new("timeline.export_script", "Export Command Script", export_script).icon("export").noundo().doc_only().params(
"path?: write the script to this file → script: {commands: [...]} that rebuilds the design (solidcraft-cli eval/run), \
steps, omitted: what the script leaves out (appearances, folders, views, configurations); components and assemblies \
are not supported yet",
+4 -4
View File
@@ -25,7 +25,7 @@ pub static COMMANDS: &[CommandSpec] = &[
Thickness, K-factor and bend radius come from the rule (sheet.manage_rules); passing them here is refused, \
and so is the corner relief on a base or contour flange",
),
CommandSpec::new("sheet.perforate", "Perforate", perforate).at("SHEET METAL", "CREATE").icon("hem").params(
CommandSpec::new("sheet.perforate", "Perforate", perforate).at("SHEET METAL", "CREATE").icon("perforate").params(
"diameter, pitch: expr; margin?: expr (clear of edges, cut-outs and bends; default: the diameter); pattern?: staggered (60°, default) | square; body? - a perforation field over the whole flat sheet: holes in the flat pattern (perforations), the DXF (layer PERFORATION) and the measured volume, not cut into the B-rep (#732)",
),
CommandSpec::new("sheet.hem", "Hem", hem)
@@ -59,7 +59,7 @@ pub static COMMANDS: &[CommandSpec] = &[
`corner_relief` across; tear none, each bend's end is cut straight from the corner to its flange's end; default notch); \
active?: bool; delete?: bool",
),
CommandSpec::new("sheet.rules", "List Sheet Metal Rules", list_rules).noundo().doc_only().params("→ rules with their expressions and values, and the active one"),
CommandSpec::new("sheet.rules", "List Sheet Metal Rules", list_rules).icon("sheet_rules").noundo().doc_only().params("→ rules with their expressions and values, and the active one"),
CommandSpec::new("sheet.flat_pattern", "Create Flat Pattern", flat_pattern)
.at("SHEET METAL", "CREATE")
.icon("flat")
@@ -67,7 +67,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params(
"body? → flat size, outline loops and cut-outs (flat coordinates, as point lists), cutout_shapes (the same cut-outs exactly, in the same order: {circle: {center, radius}} for a round hole, else {segments: [{line: [a, b]} | {arc: {center, radius, start_deg, sweep_deg}} | {polyline: [...]}]}), pockets (with their own cutout_shapes), bend lines and the bend table",
),
CommandSpec::new("sheet.export_dxf", "Export Flat Pattern as DXF", export_dxf)
CommandSpec::new("sheet.export_dxf", "Export Flat Pattern as DXF", export_dxf).icon("dxf")
.noundo()
.params(
"body?, path?, curves?: exact|arcs (default exact: an ellipse as ELLIPSE, splines, text outlines and other Béziers as SPLINE; arcs: tangent arcs and lines within 0.001 mm, for machines that read only those), \
@@ -77,7 +77,7 @@ pub static COMMANDS: &[CommandSpec] = &[
→ DXF (AutoCAD 2000, mm): layers OUTLINE (the outer contour), CUT (what is cut out inside it), TEXT or ETCH (letters), BEND_UP and BEND_DOWN (dashed centre lines), BEND_NOTES, \
POCKET_<BOTTOM|TOP|INTERNAL>_Z<from>_TO_<to>_MM (one per side and depth) and PERFORATION; inner contours come before the outline",
),
CommandSpec::new("sheet.export_bend_table", "Export Bend Table", export_bend_table)
CommandSpec::new("sheet.export_bend_table", "Export Bend Table", export_bend_table).icon("bendtable")
.noundo()
.params("body?, format?: csv|json (default csv), path? → bend table in mm and degrees, including rule and flat centre-line coordinates; a bend that cut-outs split has a row for each piece"),
];
+11 -11
View File
@@ -38,7 +38,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.key("L")
.enabled(in_sketch)
.params(concat!("points: [[x,y] | \"l1.end\" | {at, ref}…], closed?: bool, construction?: bool, ids?: [..], infer?: bool (add horizontal/vertical)", connect_doc!(), created_doc!())),
CommandSpec::new("sketch.draw.dimensioned", "Draw Dimensioned Sketch Geometry", draw_dimensioned)
CommandSpec::new("sketch.draw.dimensioned", "Draw Dimensioned Sketch Geometry", draw_dimensioned).icon("dimension")
.enabled(in_sketch)
.params(concat!("command: sketch creation command, params: object, dimensions: object of role to numeric or expression value; creates geometry and all driving dimensions atomically in one undo step", created_doc!())),
CommandSpec::new("sketch.rectangle.two_point", "2-Point Rectangle", rect_two_point)
@@ -91,12 +91,12 @@ pub static COMMANDS: &[CommandSpec] = &[
.params(concat!("center, radius (to vertices), sides, angle?: deg", connect_doc!(), created_doc!())),
CommandSpec::new("sketch.polygon.circumscribed", "Circumscribed Polygon", polygon_circumscribed)
.at("SKETCH", "CREATE")
.icon("polygon")
.icon("polygon_circumscribed")
.enabled(in_sketch)
.params(concat!("center, radius (to edge midpoints), sides, angle?: deg", connect_doc!(), created_doc!())),
CommandSpec::new("sketch.polygon.edge", "Edge Polygon", polygon_edge)
.at("SKETCH", "CREATE")
.icon("polygon")
.icon("polygon_edge")
.enabled(in_sketch)
.params(concat!("p0, p1: one edge, sides", connect_doc!(), created_doc!())),
CommandSpec::new("sketch.slot.center_to_center", "Center to Center Slot", slot_c2c)
@@ -106,7 +106,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params(concat!("p0, p1: arc centres, width", connect_doc!(), created_doc!())),
CommandSpec::new("sketch.slot.overall", "Overall Slot", slot_overall)
.at("SKETCH", "CREATE")
.icon("slot")
.icon("slot_overall")
.enabled(in_sketch)
.params(concat!("p0, p1: overall ends, width", connect_doc!(), created_doc!())),
CommandSpec::new("sketch.point", "Point", draw_point).at("SKETCH", "CREATE").icon("point").enabled(in_sketch).params(concat!("point: [x,y], id?", connect_doc!(), created_doc!())),
@@ -174,27 +174,27 @@ pub static COMMANDS: &[CommandSpec] = &[
.key("X")
.enabled(in_sketch)
.params("curves: [ids], value?: bool (default: construction unless all of them already are)"),
CommandSpec::new("sketch.delete", "Delete Sketch Entities", sketch_delete)
CommandSpec::new("sketch.delete", "Delete Sketch Entities", sketch_delete).icon("delete")
.enabled(in_sketch)
.params("entities: [curve, point or constraint ids]"),
CommandSpec::new("sketch.move_point", "Drag Sketch Point", move_point).enabled(in_sketch).params("point: ref, to: [x,y]"),
CommandSpec::new("sketch.drag", "Drag Sketch Geometry", drag).enabled(in_sketch).live(drag_live).params(
CommandSpec::new("sketch.move_point", "Drag Sketch Point", move_point).icon("sketch_move").enabled(in_sketch).params("point: ref, to: [x,y]"),
CommandSpec::new("sketch.drag", "Drag Sketch Geometry", drag).icon("sketch_move").enabled(in_sketch).live(drag_live).params(
"entity: point or curve, from: [x,y] where it was grabbed, to: [x,y], live?: bool: a point goes to `to`, a line (spline, conic) slides by to - from, a circle or arc grabbed on its edge takes the radius to `to`; the rest moves as little as it can and keeps its solution branch (a long move is followed in short steps, each solved with the entity held; a step that cannot be solved that way is approached as far as it can be and the next ones tried; when none past it solves, the last one is left to the sketch's ordinary solve, which may pull it back or leave the sketch unsolved; a step that would sweep an arc through its start is undone and the drag ends there). Returns `reached`: how much of the way from `from` to `to` the geometry followed (0..1), and `at`: where the grabbed spot went (`from` moved as the dragged points did), the `from` of the next move. `live: true` (each frame of an interactive drag) solves the sketch once and leaves the features after it as they were (stale) until `sketch.drag_end` or any command that reads the model; `dof` is then the one from before the drag",
),
CommandSpec::new("sketch.drag_end", "End Sketch Drag", drag_end).enabled(in_sketch).doc_only().params(
CommandSpec::new("sketch.drag_end", "End Sketch Drag", drag_end).icon("sketch_move").enabled(in_sketch).doc_only().params(
"constraint?: {command: a sketch.constraint.* id, params}: ends a drag made of `sketch.drag` `live: true` frames: adds the constraint it let go on (not kept if it over-constrains the sketch), then evaluates the whole model once; returns the constraint's result and the sketch's solved state and degrees of freedom",
),
CommandSpec::new("sketch.dimension_text", "Move Dimension Text", dimension_text)
CommandSpec::new("sketch.dimension_text", "Move Dimension Text", dimension_text).icon("edit")
.enabled(in_sketch)
.params("dimension: constraint id or parameter name; at: [x,y] text centre (sketch coordinates) | reset: true (default place)"),
CommandSpec::new("sketch.options", "Sketch Palette Options", sketch_options).icon("sketch").params(
"sketch?: id|name (default active); any of show_profile, show_points, show_dimensions, show_constraints, show_projected, show_construction, grid, snap, slice, sketch_3d: bool. Returns them all",
),
CommandSpec::new("sketch.chain", "Chain", chain_cmd)
CommandSpec::new("sketch.chain", "Chain", chain_cmd).icon("chain")
.enabled(in_sketch)
.noundo()
.params("curve: id: the curves joined to it end to end (what a double-click selects)"),
CommandSpec::new("sketch.solve", "Solve Sketch", solve_cmd)
CommandSpec::new("sketch.solve", "Solve Sketch", solve_cmd).icon("compute")
.enabled(in_sketch)
.noundo()
.params("reports the solver status and degrees of freedom"),
+4 -1
View File
@@ -12,17 +12,20 @@ use crate::params::{bad, req_vec3, vec3};
use crate::{Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("sketch.line3d", "3D Line", line3d).enabled(in_sketch).params(concat!(
CommandSpec::new("sketch.line3d", "3D Line", line3d).icon("line").enabled(in_sketch).params(concat!(
"points: [[x,y,z]…] world points (2 or more): one 3D line per segment, joined end to end (needs 3D Sketch on)",
created_doc!()
)),
CommandSpec::new("sketch.spline3d", "3D Spline", spline3d)
.icon("spline")
.enabled(in_sketch)
.params(concat!("points: [[x,y,z]…] world fit points (2 or more): a smooth 3D curve through them (needs 3D Sketch on)", created_doc!())),
CommandSpec::new("sketch.point3d", "3D Point", point3d)
.icon("point")
.enabled(in_sketch)
.params(concat!("point: [x,y,z] world point (needs 3D Sketch on)", created_doc!())),
CommandSpec::new("sketch.move3d", "Move 3D Point", move3d)
.icon("sketch_move")
.enabled(in_sketch)
.params("wire: drawn 3D curve or point id, index?: fit point (default 0), to: [x,y,z] | by: [dx,dy,dz]; points joined to it move too"),
];
+2 -2
View File
@@ -18,9 +18,9 @@ pub static COMMANDS: &[CommandSpec] = &[
.icon("decal")
.params("path | data; face: [x,y,z] on a planar face (the image is centred there); width?: mm; angle?: deg; opacity?: (default 1)"),
CommandSpec::new("canvas.edit", "Edit Canvas", edit_canvas).at("SKETCH", "INSERT").icon("canvas").params("canvas: id or name; center?, width?, angle?: deg, opacity?, flip?, visible?, name?"),
CommandSpec::new("canvas.calibrate", "Calibrate Canvas", calibrate)
CommandSpec::new("canvas.calibrate", "Calibrate Canvas", calibrate).icon("measure")
.params("canvas: id or name; a, b: two points on the canvas plane [x,y]; distance: their true distance (mm): scales the image about a"),
CommandSpec::new("canvas.delete", "Delete Canvas", delete_canvas).params("canvas: id or name"),
CommandSpec::new("canvas.delete", "Delete Canvas", delete_canvas).icon("delete").params("canvas: id or name"),
];
fn image_bytes(p: &Value, cmd: &str) -> Result<Vec<u8>> {
+3 -3
View File
@@ -17,14 +17,14 @@ use crate::params::{bad, string_list, vec2};
use crate::{Result, Sel, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("sketch.copy", "Copy Sketch Entities", copy)
CommandSpec::new("sketch.copy", "Copy Sketch Entities", copy).icon("copy")
.noundo()
.enabled(in_sketch)
.params("entities?: [curve or point ids] (default: the selected sketch geometry); the constraints and dimensions among them come along"),
CommandSpec::new("sketch.cut", "Cut Sketch Entities", cut)
CommandSpec::new("sketch.cut", "Cut Sketch Entities", cut).icon("cut")
.enabled(in_sketch)
.params("entities?: [curve or point ids] (default: the selected sketch geometry): copies them as sketch.copy does, then deletes them (one undo step)"),
CommandSpec::new("sketch.paste", "Paste Sketch Entities", paste).enabled(in_sketch).params(
CommandSpec::new("sketch.paste", "Paste Sketch Entities", paste).icon("paste").enabled(in_sketch).params(
"at?: [x, y] (sketch point the copy's centre goes to) | translate?: [x, y]; without either, offset from the original in its own sketch and at the same coordinates in another; sketch?: target (default: the active sketch)",
),
];
+3 -3
View File
@@ -41,14 +41,14 @@ pub static COMMANDS: &[CommandSpec] = &[
.icon("constrainer")
.enabled(in_sketch)
.params("entities: [one or two sketch entity ids]: applies the constraint the geometry nearly has (horizontal/vertical, parallel, perpendicular, collinear, coincident, concentric, equal, tangent)"),
CommandSpec::new("sketch.toggle_driven", "Toggle Driven Dimension", toggle_driven)
CommandSpec::new("sketch.toggle_driven", "Toggle Driven Dimension", toggle_driven).icon("dimension")
.enabled(in_sketch)
.params("constraint: dimension id, driven?: bool (default toggles); a driving dimension gets a parameter with its current value"),
CommandSpec::new("sketch.glyphs", "Constraint Glyphs", glyphs)
CommandSpec::new("sketch.glyphs", "Constraint Glyphs", glyphs).icon("eye")
.enabled(in_sketch)
.noundo()
.params("sketch?: id|name: every constraint with where to draw its glyph (sketch coordinates)"),
CommandSpec::new("sketch.snap", "Snap Inference", snap)
CommandSpec::new("sketch.snap", "Snap Inference", snap).icon("snap")
.enabled(in_sketch)
.noundo()
.params("at: [x,y], from?: [x,y] (previous click), radius?: mm (snap distance, default 1): what a click there would snap to and infer"),
+5 -5
View File
@@ -47,7 +47,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params(concat!("curves: [three lines/circles/arcs], near: [x,y] (which of the solutions)", created_doc!())),
CommandSpec::new("sketch.slot.center_point", "Center Point Slot", slot_center_point)
.at("SKETCH", "CREATE")
.icon("slot")
.icon("slot_center")
.enabled(in_sketch)
.params(concat!("center: slot centre, end: one arc centre, width", created_doc!())),
CommandSpec::new("sketch.slot.arc_three_point", "Three Point Arc Slot", arc_slot_three)
@@ -57,7 +57,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params(concat!("start, through, end: points on the centre arc, width", created_doc!())),
CommandSpec::new("sketch.slot.arc_center", "Center Point Arc Slot", arc_slot_center)
.at("SKETCH", "CREATE")
.icon("arc_slot")
.icon("arc_slot_center")
.enabled(in_sketch)
.params(concat!("center, start: centre arc start, end: direction of the centre arc end (counter-clockwise), width", created_doc!())),
CommandSpec::new("sketch.ellipse", "Ellipse", ellipse).at("SKETCH", "CREATE").icon("ellipse").enabled(in_sketch).params(concat!(
@@ -97,16 +97,16 @@ pub static COMMANDS: &[CommandSpec] = &[
.icon("centerline")
.enabled(in_sketch)
.params("curves: [line ids], value?: bool (default toggles): centerline line type (not in profiles; an axis)"),
CommandSpec::new("sketch.infinite", "Infinite Line", infinite_line)
CommandSpec::new("sketch.infinite", "Infinite Line", infinite_line).icon("infinite")
.enabled(in_sketch)
.params("curves: [line ids], value?: bool (default toggles): infinite line type (#1359): construction geometry drawn across the whole view through the line's two points; off, a normal line"),
CommandSpec::new("sketch.text", "Text", text).at("SKETCH", "CREATE").icon("text").enabled(in_sketch).params(concat!(
CommandSpec::new("sketch.text", "Text", text).at("SKETCH", "CREATE").icon("sketch_text").enabled(in_sketch).params(concat!(
"text: string (\\n breaks lines), at: baseline start [x,y], height?: capital height mm (default 5), angle?: deg; at's x and y, height and angle each take a number or an expression over the design's parameters as a string (\"plate_width / 2\", \"label_size\"): the text keeps the expression, follows the parameters, and returns it in `exprs`; an expression naming no parameter is the number it comes to, a bare number in one is in the design's units, \
font?: an installed font's family or full name, or a .ttf/.otf file path (~ and %VAR% expand; default: the built-in Liberation Sans), ",
text_style_doc!(),
created_doc!()
)),
CommandSpec::new("sketch.edit_text", "Edit Text", edit_text)
CommandSpec::new("sketch.edit_text", "Edit Text", edit_text).icon("edit")
.enabled(in_sketch)
.live(edit_text_live)
.params(concat!(
+2 -2
View File
@@ -15,13 +15,13 @@ use crate::{EngineError, Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("sketch.insert_dxf", "Insert DXF", insert_dxf)
.at("SKETCH", "INSERT")
.icon("dxf")
.icon("sketch_dxf")
.params(concat!("path | text: ASCII DXF (blocks expanded); at?: [x,y] offset, scale?, tolerance?: mm within which ends join (default 0.0001); plane?: (when no sketch is active, a new sketch on it; default XY)", created_doc!())),
CommandSpec::new("sketch.insert_svg", "Insert SVG", insert_svg)
.at("SKETCH", "INSERT")
.icon("svg")
.params(concat!("path | text: SVG; at?: [x,y] offset, scale?, tolerance?: mm within which ends join (default 0.0001); plane?: (when no sketch is active, a new sketch on it; default XY)", created_doc!())),
CommandSpec::new("sketch.export_dxf", "Save As DXF", export_dxf)
CommandSpec::new("sketch.export_dxf", "Save As DXF", export_dxf).icon("sketch_dxf")
.noundo()
.params("sketch?: id|name (default active); path?: file to write (else the DXF text is returned)"),
];
+6 -6
View File
@@ -31,12 +31,12 @@ pub static COMMANDS: &[CommandSpec] = &[
.params(concat!("point | a, b: a corner of two lines; distance", created_doc!())),
CommandSpec::new("sketch.chamfer.distance_angle", "Distance and Angle Chamfer", chamfer_dist_angle)
.at("SKETCH", "MODIFY")
.icon("sketch_chamfer")
.icon("sketch_chamfer_angle")
.enabled(in_sketch)
.params(concat!("point | a, b: a corner of two lines; distance: along a, angle: deg from a", created_doc!())),
CommandSpec::new("sketch.chamfer.two_distance", "Two Distance Chamfer", chamfer_two)
.at("SKETCH", "MODIFY")
.icon("sketch_chamfer")
.icon("sketch_chamfer_two_distances")
.enabled(in_sketch)
.params(concat!("point | a, b: a corner of two lines; distance: along a, distance2: along b", created_doc!())),
CommandSpec::new("sketch.offset", "Offset", offset)
@@ -50,7 +50,7 @@ pub static COMMANDS: &[CommandSpec] = &[
"distance2?: positive length expression (Two Sides: an independent distance the other way, as for `solid.extrude`). Each side is its own offset association, listed in `sides`; flip?: bool reverses the primary side; `param`/`param2` name the driving dimensions",
created_doc!()
)),
CommandSpec::new("sketch.offset.break", "Break Offset Association", break_offset)
CommandSpec::new("sketch.offset.break", "Break Offset Association", break_offset).icon("sketch_break")
.enabled(in_sketch)
.params("curve: derived offset curve id (retains geometry and makes its points editable)"),
CommandSpec::new("sketch.trim", "Trim", trim)
@@ -66,17 +66,17 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("curve: id, at: [x,y] near the end to extend (to the nearest curve)"),
CommandSpec::new("sketch.break", "Break", break_cmd)
.at("SKETCH", "MODIFY")
.icon("break")
.icon("sketch_break")
.enabled(in_sketch)
.params(concat!("curve: id, at: [x,y] (split at intersections) | points: [[x,y]…] (1…1000 positions projected onto the curve; duplicates are merged)", created_doc!())),
CommandSpec::new("sketch.scale", "Sketch Scale", scale)
.at("SKETCH", "MODIFY")
.icon("scale")
.icon("sketch_scale")
.enabled(in_sketch)
.params("entities: [curve/point ids], base: [x,y] or point ref, factor: number"),
CommandSpec::new("sketch.move", "Move/Copy Sketch Entities", move_copy)
.at("SKETCH", "MODIFY")
.icon("move")
.icon("sketch_move")
.enabled(in_sketch)
.params(concat!("entities: [ids], translate?: [dx,dy], angle?: deg, center?: [x,y], copy?: bool; picking a text outline or linked point transforms its whole text source once, and copies remain editable text", created_doc!())),
CommandSpec::new("sketch.mirror", "Mirror", mirror)
+2 -2
View File
@@ -54,10 +54,10 @@ pub static COMMANDS: &[CommandSpec] = &[
.icon("fit_section")
.enabled(in_sketch)
.params(concat!("body: name (a mesh or solid body): its section with the sketch plane, fitted with lines and arcs, as normal (unlinked) geometry", created_doc!())),
CommandSpec::new("sketch.break_link", "Break Link", break_link)
CommandSpec::new("sketch.break_link", "Break Link", break_link).icon("sketch_break")
.enabled(in_sketch)
.params("link?: link id (j1) | entities?: [curve or point ids]: linked geometry becomes normal sketch geometry"),
CommandSpec::new("sketch.auto_project", "Auto-Project Edges on Reference", auto_project)
CommandSpec::new("sketch.auto_project", "Auto-Project Edges on Reference", auto_project).icon("project")
.noundo()
.params("value?: bool (default toggles): project a face's edges when a sketch is created on it"),
];
+10 -10
View File
@@ -18,23 +18,23 @@ use crate::{Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("surface.patch", "Patch", patch)
.at("SURFACE", "CREATE")
.icon("patch")
.icon("surface_patch")
.params("sketch?: id|name and profiles? (planar patches of sketch regions) | edges: [[x,y,z]] (points on a closed loop of a body's edges: one face on them, planar when the loop is, else a smooth B-spline surface) and body?; continuity?: g0|g1|g2 (edges only, default g0: how the patch meets the faces beside the loop — g1 their tangent planes, g2 also their curvature across the edges) or [{edge: [x,y,z], g: g0|g1|g2}] (those edges so, the rest g0) or {all, edges}; points?: [[x,y,z]] (edges only, up to 64: points the patch passes through); crown?: number −0.9…3 (default 0: only a fitted patch, whose middle becomes fuller by this many times the loop's size — the bump vanishes on the loop, so the face still meets the picked edges and the continuity across them; a loop that is planar and g0 is the exact plane whatever the crown; with points, crown needs a g1 or g2 edge); name?, body_name?"),
CommandSpec::new("surface.stitch", "Stitch", stitch)
.at("SURFACE", "MODIFY")
.icon("stitch")
.icon("surface_stitch")
.params("bodies: [names] (surfaces sewn into the first; a solid when they close); tolerance?: expr (default: tight)"),
CommandSpec::new("surface.thicken", "Thicken", thicken)
.at("SURFACE", "CREATE")
.icon("thicken")
.icon("surface_thicken")
.params("bodies: [surface names]; thickness: expr (negative: against the normal); symmetric?: bool; operation?: new|join|cut|intersect; targets?"),
CommandSpec::new("surface.extend", "Extend", extend)
.at("SURFACE", "MODIFY")
.icon("extend")
.icon("surface_extend")
.params("body: surface name; edges: [[x,y,z]] (points on boundary edges to push out); distance: expr (unless up_to); mode?: natural (the surface itself continued: exact for planes and surfaces of revolution, a B-spline's end spans continued; the default) | tangent (a ruled run-off face along each edge, G1) | curvature (a run-off face keeping the surface's curvature across the edge, G2); up_to?: plane (XY|XZ|YZ|plane name|{origin, normal}) | {body, point} (a face, its surface extended) | \"bounds\" (natural: back to the surface's own bounds, undoing a trim); neighbours?: bool (default true: the edges next to an extended edge are lengthened to meet it; false: the extension's sides run square to the edge)"),
CommandSpec::new("surface.trim", "Trim", trim)
.at("SURFACE", "MODIFY")
.icon("trim")
.icon("surface_trim")
.params("body: surface name; plane: XY|XZ|YZ|plane name|{origin, normal} or tool: {body, point} (a face, its surface extended); keep: [x,y,z] (a point on the side to keep)"),
CommandSpec::new("surface.extrude", "Surface Extrude", surface_extrude)
.at("SURFACE", "CREATE")
@@ -58,7 +58,7 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("faces: [[x,y,z]] (points on the faces); body?; distance: expr (signed, along the faces' normals; 0 copies the faces); chain?: bool (each pick's whole smooth chain of faces); tolerance?: expr (how far a fitted face may stray, default 0.01 mm). Planes and cylinders move exactly; other faces are fitted. A new surface body (a solid where its faces close)"),
CommandSpec::new("surface.ruled", "Ruled Surface", ruled)
.at("SURFACE", "CREATE")
.icon("ruled")
.icon("surface_ruled")
.params("body; edges: [[x,y,z]] (points on a chain of its edges, open or closed); distance: expr (how far the strip reaches); mode?: normal|tangent|angle|direction (default normal: along the normal of the face beside each edge; tangent: continuing it); angle?: expr (angle mode: from `direction`, else from the face's normal, turned away from the face; 90 deg from the normal is tangent); direction?: [x,y,z] (direction mode: the rulings; angle mode: the reference, e.g. a pull direction for a draft); face?: [x,y,z] (a point on the face the directions come from; default: the face beside the most picked edges); flip?: bool (the other side); corners?: mitre|round (sharp corners of the chain; inside corners are always cut back). Lines and arcs give exact planes, cylinders and cones, other edges a fitted ruled B-spline. A new surface body"),
CommandSpec::new("surface.blend", "Blend Surface", blend)
.at("SURFACE", "CREATE")
@@ -66,19 +66,19 @@ pub static COMMANDS: &[CommandSpec] = &[
.params("from, to: {body, edges: [[x,y,z]]} (points on the edges of one chain of each body: two picks on the same edge is that one edge, more chain up); continuity?: [g0|g1|g2|g3, g0|g1|g2|g3] (each end, default g1: how much of the surface at the picked edges the blend repeats — the tangent plane, the curvature, or how that curvature changes); bulge?: [number, number] (default 0: how full each end leaves, -0.9 flattest to 3 fullest); align?: [[x,y,z], [x,y,z]] (a point each end leaves towards, when the faces at the picks do not say); stitch?: bool (sew the blend onto its two bodies where the boundaries meet, replacing them); name?, body_name?"),
CommandSpec::new("surface.untrim", "Untrim", untrim)
.at("SURFACE", "MODIFY")
.icon("untrim")
.icon("surface_untrim")
.params("face: {body, point}; holes?: all|[[x,y,z]] (points on the inner loops to remove; default: all); outer?: bool (restore the outer boundary to the surface's natural bounds: a free-form surface's own, an analytic surface's span of the face); in_place?: bool (untrim a surface body's face where it is; a solid's face is always copied out). A surface body; the face keeps its name"),
CommandSpec::new("surface.unstitch", "Unstitch", unstitch)
.at("SURFACE", "MODIFY")
.icon("unstitch")
.icon("surface_unstitch")
.params("body; faces?: [[x,y,z]] (points on the faces to take out, each its own surface body; the rest stay together under the body's name; default: every face its own surface body). A solid comes apart into surfaces"),
CommandSpec::new("surface.reverse_normal", "Reverse Normal", reverse_normal)
.at("SURFACE", "MODIFY")
.icon("reverse_normal")
.icon("surface_reverse_normal")
.params("body (a surface); faces?: [[x,y,z]] (points on the faces to turn over; default: all of them)"),
CommandSpec::new("surface.fair", "Fair Surface", fair)
.at("SURFACE", "MODIFY")
.icon("spline_cv")
.icon("surface_fair")
.params("body: surface name; faces?: [[x,y,z]] points on the faces to fair (default: every face); strength?: number (0..1 of the full fairing step, default 1); iterations?: number (1..1000 steps, default 1); keep_edges?: bool (default true: hold the boundary row and the row next to it, so a face keeps meeting its neighbours in position and in tangent); tolerance?: expr mm (how far a control vertex may move, default 0.001 of the body's size). Moves the interior control vertices of each B-spline face to flatten its curvature variation while the boundary stays put: the face keeps its edges and the body its shape within the tolerance, and the warning in the reply carries the energy before and after; name?"),
planned!("surface.fillet", "Surface Fillet", "769")
.at("SURFACE", "MODIFY")
+1 -1
View File
@@ -21,7 +21,7 @@ const MAX_SAMPLES: usize = 512;
pub static COMMANDS: &[CommandSpec] = &[CommandSpec::new("surface.boundary", "Boundary Surface", boundary)
.at("SURFACE", "CREATE")
.icon("boundary")
.icon("surface_boundary")
.params(
"sketch?: id|name (default: the sketch being edited); curves: [2 to 4 sketch curve ids in loop order] (each one ends where the next begins and the last where the first does; the patch runs through them and leaves each boundary along the tangent across it); continuity?: g0|g1|g2|g3 | [one per curve] (default g1: how much of the surface across each boundary the patch repeats — a sketch curve's own plane, so g1 and up hold exactly and g0 lets the middle bow); crown?: number -0.9…3 (default 0: how full the middle is, without touching a boundary); samples?: 8…512 stations per boundary (default 25); name?, body_name? (a surface body)",
)];
+1
View File
@@ -558,6 +558,7 @@ fn blend_corners_where_edges_meet_on_a_curved_wall() {
fn a_boundary_found_across_a_seam_is_not_used() {
let r = fuzz::run(20, 8);
assert_eq!(r.outcome.kind, "ok", "seed 20: {:?}\n{}", r.outcome, r.script());
assert!(r.steps.iter().any(|s| s.kind == "circular pattern"), "the pattern must build, not be skipped as unsupported: {:?}", r.unsupported);
}
/// Seed 33 ends in a fillet on its second body (#854's corner blends build it differently from