diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index 8c00d203..ac02ef58 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -1040,6 +1040,9 @@ pub struct Document { /// Physical material per body (by body name); others use the default. #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] pub materials: std::collections::BTreeMap, + /// Appearance colour per body (by body name, sRGB); it overrides the material's look. + #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] + pub appearances: std::collections::BTreeMap, /// Bodies moved into another component than their feature's (by body name). #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] pub body_components: std::collections::BTreeMap, @@ -1094,6 +1097,7 @@ impl Document { plastic: Default::default(), body_components: Default::default(), materials: Default::default(), + appearances: Default::default(), favorites: Default::default(), canvases: Vec::new(), param_comments: Default::default(), @@ -1102,6 +1106,25 @@ impl Document { } } + /// The colour a body is shown and exported in (sRGB as 0–1 fractions): its + /// appearance, else its material's look, else the colour it was imported with. + pub fn body_color(&self, name: &str, body: &solvecraft_kernel::Body) -> Option<[f32; 3]> { + let rgb = |c: [u8; 3]| c.map(|x| x as f32 / 255.0); + self.appearances.get(name).copied().or_else(|| self.materials.get(name).and_then(|m| material_color(m))).map(rgb).or_else(|| body.color()) + } + + /// The model with each body carrying the colour it is shown in (for export). + pub fn painted(&self, state: &crate::ModelState) -> crate::ModelState { + let mut st = state.clone(); + for b in &mut st.bodies { + let c = self.body_color(&b.name, &b.body); + if c != b.body.color() { + b.body = b.body.clone().with_color(c); + } + } + st + } + /// Density of a body's material (g/cm³). pub fn density(&self, body: &str) -> f64 { self.materials.get(body).and_then(|m| material_density(m)).unwrap_or(DEFAULT_DENSITY) @@ -1445,6 +1468,26 @@ pub const MATERIALS: [(&str, f64); 14] = [ ("Glass", 2.50), ]; +/// How a physical material looks (sRGB); the default material has no colour of its own. +pub fn material_color(name: &str) -> Option<[u8; 3]> { + Some(match name { + "Steel" => [158, 164, 172], + "Stainless Steel" => [186, 191, 198], + "Aluminum" => [204, 208, 214], + "Brass" => [208, 172, 88], + "Copper" => [204, 122, 80], + "Titanium" => [150, 148, 158], + "Cast Iron" => [104, 104, 110], + "ABS Plastic" => [222, 222, 216], + "PLA" => [110, 160, 222], + "Nylon" => [236, 230, 214], + "Polycarbonate" => [196, 216, 232], + "Oak" => [190, 146, 92], + "Glass" => [170, 208, 218], + _ => return None, + }) +} + pub fn material_density(name: &str) -> Option { MATERIALS.iter().find(|(n, _)| n.eq_ignore_ascii_case(name.trim())).map(|(_, d)| *d) } diff --git a/crates/engine/src/cmd/browser.rs b/crates/engine/src/cmd/browser.rs index 1068a0c0..a675daf4 100644 --- a/crates/engine/src/cmd/browser.rs +++ b/crates/engine/src/cmd/browser.rs @@ -219,6 +219,9 @@ fn rename_body(s: &mut Session, p: &Value) -> Result { if let Some(m) = d.materials.remove(&old) { d.materials.insert(name.to_string(), m); } + if let Some(c) = d.appearances.remove(&old) { + d.appearances.insert(name.to_string(), c); + } if let Some(c) = d.body_components.remove(&old) { d.body_components.insert(name.to_string(), c); } diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 24d227a6..f4b1f1ff 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -162,9 +162,10 @@ fn export_to(s: &Session, path: &str, format: Format, bodies: &[String]) -> Resu let name = std::path::Path::new(path).file_stem().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| s.doc.name.clone()); // A design with components exports as a STEP assembly; otherwise bodies as placed. let bytes = if format == Format::Step && bodies.is_empty() && !s.doc.occurrences.is_empty() { - solvecraft_io::step_assembly(&s.doc, &s.model.state(), &name)? + solvecraft_io::step_assembly(&s.doc, &s.doc.painted(&s.model.state()), &name)? } else { - solvecraft_io::export(&s.world_state(), bodies, format, &name)? + // Bodies carry the colour they are shown in (appearance, material or imported). + solvecraft_io::export(&s.doc.painted(&s.world_state()), bodies, format, &name)? }; std::fs::write(path, &bytes).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; Ok(json!({"path": path, "bytes": bytes.len(), "format": format!("{format:?}")})) diff --git a/crates/engine/src/cmd/inspect.rs b/crates/engine/src/cmd/inspect.rs index dfa1a5f9..9e4fd25e 100644 --- a/crates/engine/src/cmd/inspect.rs +++ b/crates/engine/src/cmd/inspect.rs @@ -23,6 +23,10 @@ pub static COMMANDS: &[CommandSpec] = &[ .at("SOLID", "MODIFY") .icon("material") .params("bodies: [names]; material: name (Steel, Aluminum, ABS Plastic, …; `material.list`)"), + CommandSpec::new("AppearanceCommand", "Appearance", appearance) + .at("SOLID", "MODIFY") + .icon("appearance") + .params("bodies: [names]; color: \"#rrggbb\" or [r, g, b] (0–255), or null to follow the material"), CommandSpec::new("material.list", "List Materials", material_list).noundo(), CommandSpec::new("engine.commands", "List Commands", commands).noundo(), ]; @@ -271,6 +275,41 @@ fn physical_material(s: &mut Session, p: &Value) -> Result { Ok(json!({"bodies": bodies, "material": canonical})) } +/// A colour parameter: `"#rrggbb"` or `[r, g, b]` (0–255). +fn color_param(v: &Value) -> Option<[u8; 3]> { + if let Some(t) = v.as_str() { + let h = t.trim().trim_start_matches('#'); + if h.len() != 6 || !h.is_ascii() { + return None; + } + let byte = |i: usize| h.get(i..i + 2).and_then(|x| u8::from_str_radix(x, 16).ok()); + return Some([byte(0)?, byte(2)?, byte(4)?]); + } + let a = v.as_array().filter(|a| a.len() == 3)?; + let c = |i: usize| a.get(i).and_then(Value::as_f64).filter(|x| x.is_finite() && (0.0..=255.0).contains(x)).map(|x| x.round() as u8); + Some([c(0)?, c(1)?, c(2)?]) +} + +fn appearance(s: &mut Session, p: &Value) -> Result { + let cmd = "AppearanceCommand"; + let bodies = string_list(p, "bodies"); + let st = s.model.state(); + if bodies.is_empty() || bodies.iter().any(|b| st.body(b).is_none()) { + return Err(bad(cmd, "`bodies` must list existing bodies")); + } + let color = match p.get("color") { + None | Some(Value::Null) => None, + Some(v) => Some(color_param(v).ok_or_else(|| bad(cmd, "`color` must be \"#rrggbb\" or [r, g, b] with values 0–255"))?), + }; + for b in &bodies { + match color { + Some(c) => s.doc_mut().appearances.insert(b.clone(), c), + None => s.doc_mut().appearances.remove(b), + }; + } + Ok(json!({"bodies": bodies, "color": color.map(|c| format!("#{:02x}{:02x}{:02x}", c[0], c[1], c[2]))})) +} + fn material_list(_s: &mut Session, _p: &Value) -> Result { Ok(json!({"materials": solvecraft_doc::MATERIALS.iter().map(|(n, d)| json!({"name": n, "density_g_cm3": d})).collect::>()})) } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index d39d5990..8428da35 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -951,6 +951,42 @@ fn sample_plate_step_round_trip() { } } +/// Bodies export in the colour they are shown in: a material's look, an appearance over it, +/// none for the default; and colours read from a STEP file survive the next export. +#[test] +fn material_and_appearance_colours_export_to_step() { + let mut s = Session::default(); + run(&mut s, "PrimitiveBox", json!({"length": 10, "width": 10, "height": 10, "body_name": "BrassBox"})); + run(&mut s, "PrimitiveBox", json!({"corner": [20, 0, 0], "length": 10, "width": 10, "height": 10, "body_name": "RedAbs"})); + run(&mut s, "PrimitiveBox", json!({"corner": [40, 0, 0], "length": 10, "width": 10, "height": 10, "body_name": "Plain"})); + run(&mut s, "PhysicalMaterialCommand", json!({"bodies": ["BrassBox"], "material": "Brass"})); + run(&mut s, "PhysicalMaterialCommand", json!({"bodies": ["RedAbs"], "material": "ABS Plastic"})); + run(&mut s, "AppearanceCommand", json!({"bodies": ["RedAbs"], "color": "#d01c1c"})); + assert!(s.execute("AppearanceCommand", &json!({"bodies": ["RedAbs"], "color": "#12"})).is_err()); + let rgb = |c: [u8; 3]| c.map(|x| x as f32 / 255.0); + let want = + [("BrassBox", Some(rgb(solvecraft_doc::material_color("Brass").unwrap()))), ("RedAbs", Some(rgb([0xd0, 0x1c, 0x1c]))), ("Plain", None)]; + let same = |a: Option<[f32; 3]>, b: Option<[f32; 3]>| match (a, b) { + (Some(a), Some(b)) => a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-3), + (None, None) => true, + _ => false, + }; + let check = |imp: &solvecraft_kernel::StepImport| { + for (name, c) in want { + let b = imp.bodies.iter().find(|b| b.name == name).unwrap_or_else(|| panic!("no body {name}")); + assert!(same(b.body.color(), c), "{name}: {:?} vs {c:?}", b.body.color()); + } + }; + check(&step_round_trip(&mut s, "colours")); + // Open the export and export again: the file's colours are kept. + let p = std::env::temp_dir().join(format!("solvecraft-colours-{}.step", std::process::id())); + run(&mut s, "ExportCommand", json!({"path": p.to_string_lossy()})); + let mut s2 = Session::default(); + run(&mut s2, "doc.open", json!({"path": p.to_string_lossy()})); + let _ = std::fs::remove_file(&p); + check(&step_round_trip(&mut s2, "colours2")); +} + /// Components export as a STEP assembly: products, occurrences, placements. #[test] fn components_export_as_step_assembly() { diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 23f46f61..0fa257d7 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -262,22 +262,7 @@ impl Tokens { /// How a physical material looks (the default appearance follows the material). pub fn material_color(name: &str) -> Option { - Some(match name { - "Steel" => Color32::from_rgb(158, 164, 172), - "Stainless Steel" => Color32::from_rgb(186, 191, 198), - "Aluminum" => Color32::from_rgb(204, 208, 214), - "Brass" => Color32::from_rgb(208, 172, 88), - "Copper" => Color32::from_rgb(204, 122, 80), - "Titanium" => Color32::from_rgb(150, 148, 158), - "Cast Iron" => Color32::from_rgb(104, 104, 110), - "ABS Plastic" => Color32::from_rgb(222, 222, 216), - "PLA" => Color32::from_rgb(110, 160, 222), - "Nylon" => Color32::from_rgb(236, 230, 214), - "Polycarbonate" => Color32::from_rgb(196, 216, 232), - "Oak" => Color32::from_rgb(190, 146, 92), - "Glass" => Color32::from_rgb(170, 208, 218), - _ => return None, - }) + solvecraft_engine::doc::material_color(name).map(|[r, g, b]| Color32::from_rgb(r, g, b)) } /// Switch themes (the visuals follow on the next [`apply`]). diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index c22cdeaa..86903057 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -215,7 +215,7 @@ fn build_scene(app: &SolveApp) -> GpuScene { } // With a component active, bodies outside it are drawn faded. let faded = s.active_component != 0 && !s.doc.component_within(s.doc.body_component(&b.name, b.feature), s.active_component); - let base = s.doc.materials.get(&b.name).and_then(|m| crate::theme::material_color(m)).unwrap_or(tk.body); + let base = body_color(&s.doc, &b.name, &b.body).unwrap_or(tk.body); let col = if faded { c4(base.gamma_multiply(0.35)) } else { c4(base) }; let m = b.mesh(); let (shaded, edges) = match app.ui.visual_style { @@ -930,9 +930,16 @@ fn build_highlight(app: &SolveApp) -> GpuScene { sc } -/// A body's display colour (its material's, else the theme's) as RGB. +/// A body's display colour (appearance, material or imported colour), if it has one. +fn body_color(doc: &solvecraft_engine::doc::Document, name: &str, body: &solvecraft_engine::kernel::Body) -> Option { + let to8 = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8; + doc.body_color(name, body).map(|[r, g, b]| Color32::from_rgb(to8(r), to8(g), to8(b))) +} + +/// A body's display colour (else the theme's) as RGB. fn body_rgb(app: &SolveApp, name: &str) -> (u8, u8, u8) { - let c = app.session.doc.materials.get(name).and_then(|m| crate::theme::material_color(m)).unwrap_or(Tokens::get().body); + let st = app.session.model.state(); + let c = st.body(name).and_then(|b| body_color(&app.session.doc, name, &b.body)).unwrap_or(Tokens::get().body); (c.r(), c.g(), c.b()) } diff --git a/docs/parity.md b/docs/parity.md index e51867b8..3d85c336 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -2,95 +2,95 @@ Generated by `cargo xtask parity` from `xtask/data/fusion-catalog.tsv` (the toolbar commands of Fusion's Design workspace, ids and names only) and the live command registry (`solvecraft-cli commands`). A command counts as live when SolveCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth. -**In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): 154 / 541 commands live (28%).** SOLID + SKETCH: 115 / 291 (40%). Registered commands: 162. +**In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): 220 / 541 commands live (41%).** SOLID + SKETCH: 140 / 291 (48%). Registered commands: 225. -Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE beyond what solids need, PCB, render, animation, simulation, manufacture, drawings, MANAGE, UTILITIES and the rest of PLASTIC. All Design tabs together: 248 / 1175 (21%). +Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE beyond what solids need, PCB, render, animation, simulation, manufacture, drawings, MANAGE, UTILITIES and the rest of PLASTIC. All Design tabs together: 372 / 1175 (32%). | Tab | Panel | Live | Total | % | |---|---|---:|---:|---:| -| SOLID | CREATE | 16 | 35 | 46% | -| SOLID | MODIFY | 14 | 32 | 44% | -| SOLID | ASSEMBLE | 2 | 21 | 10% | +| SOLID | CREATE | 22 | 35 | 63% | +| SOLID | MODIFY | 18 | 32 | 56% | +| SOLID | ASSEMBLE | 7 | 21 | 33% | | SOLID | CONFIGURE | 0 | 3 | 0% | | SOLID | CONSTRUCT | 2 | 20 | 10% | -| SOLID | INSPECT | 1 | 25 | 4% | -| SOLID | INSERT | 3 | 11 | 27% | +| SOLID | INSPECT | 4 | 25 | 16% | +| SOLID | INSERT | 5 | 11 | 45% | | SOLID | SELECT | 0 | 15 | 0% | | SOLID | POSITION | 2 | 2 | 100% | | SKETCH | CREATE | 41 | 41 | 100% | | SKETCH | MODIFY | 12 | 12 | 100% | | SKETCH | CONSTRAINTS | 15 | 15 | 100% | | SKETCH | CONFIGURE | 0 | 3 | 0% | -| SKETCH | INSPECT | 1 | 25 | 4% | -| SKETCH | INSERT | 4 | 13 | 31% | +| SKETCH | INSPECT | 4 | 25 | 16% | +| SKETCH | INSERT | 6 | 13 | 46% | | SKETCH | ASSEMBLE | 0 | 1 | 0% | | SKETCH | SELECT | 0 | 15 | 0% | | SKETCH | FINISH SKETCH | 2 | 2 | 100% | -| SHEET METAL | CREATE | 8 | 19 | 42% | -| SHEET METAL | MODIFY | 7 | 21 | 33% | -| SHEET METAL | ASSEMBLE | 1 | 20 | 5% | +| SHEET METAL | CREATE | 14 | 19 | 74% | +| SHEET METAL | MODIFY | 12 | 21 | 57% | +| SHEET METAL | ASSEMBLE | 6 | 20 | 30% | | SHEET METAL | CONFIGURE | 0 | 3 | 0% | | SHEET METAL | CONSTRUCT | 2 | 20 | 10% | -| SHEET METAL | INSPECT | 1 | 25 | 4% | -| SHEET METAL | INSERT | 3 | 11 | 27% | +| SHEET METAL | INSPECT | 4 | 25 | 16% | +| SHEET METAL | INSERT | 5 | 11 | 45% | | SHEET METAL | SELECT | 0 | 15 | 0% | | SHEET METAL | REFOLD | 0 | 1 | 0% | | SHEET METAL | POSITION | 2 | 2 | 100% | -| ASSEMBLY | INSERT | 1 | 8 | 12% | -| ASSEMBLY | CREATE | 4 | 8 | 50% | -| ASSEMBLY | RELATIONSHIPS | 0 | 11 | 0% | -| ASSEMBLY | MOTION | 0 | 3 | 0% | -| ASSEMBLY | MODIFY | 5 | 11 | 45% | +| ASSEMBLY | INSERT | 3 | 8 | 38% | +| ASSEMBLY | CREATE | 5 | 8 | 62% | +| ASSEMBLY | RELATIONSHIPS | 4 | 11 | 36% | +| ASSEMBLY | MOTION | 2 | 3 | 67% | +| ASSEMBLY | MODIFY | 8 | 11 | 73% | | ASSEMBLY | CONTACT | 0 | 2 | 0% | | ASSEMBLY | CONFIGURE | 0 | 3 | 0% | | ASSEMBLY | CONSTRUCT | 2 | 20 | 10% | -| ASSEMBLY | INSPECT | 1 | 25 | 4% | +| ASSEMBLY | INSPECT | 4 | 25 | 16% | | ASSEMBLY | SELECT | 0 | 15 | 0% | | ASSEMBLY | POSITION | 2 | 2 | 100% | -| PLASTIC | enclosure subset | 0 | 5 | 0% | +| PLASTIC | enclosure subset | 5 | 5 | 100% | ### Deferred tabs | Tab | Panel | Live | Total | % | |---|---|---:|---:|---:| -| SURFACE | CREATE | 5 | 25 | 20% | -| SURFACE | MODIFY | 10 | 26 | 38% | -| SURFACE | ASSEMBLE | 1 | 20 | 5% | +| SURFACE | CREATE | 7 | 25 | 28% | +| SURFACE | MODIFY | 13 | 26 | 50% | +| SURFACE | ASSEMBLE | 6 | 20 | 30% | | SURFACE | CONFIGURE | 0 | 3 | 0% | | SURFACE | CONSTRUCT | 2 | 20 | 10% | -| SURFACE | INSPECT | 1 | 25 | 4% | -| SURFACE | INSERT | 3 | 11 | 27% | +| SURFACE | INSPECT | 4 | 25 | 16% | +| SURFACE | INSERT | 5 | 11 | 45% | | SURFACE | SELECT | 0 | 15 | 0% | | SURFACE | POSITION | 2 | 2 | 100% | | MESH | CREATE | 2 | 5 | 40% | | MESH | PREPARE | 0 | 4 | 0% | -| MESH | MODIFY | 5 | 30 | 17% | -| MESH | ASSEMBLE | 1 | 21 | 5% | +| MESH | MODIFY | 6 | 30 | 20% | +| MESH | ASSEMBLE | 7 | 21 | 33% | | MESH | CONFIGURE | 0 | 3 | 0% | | MESH | CONSTRUCT | 2 | 20 | 10% | -| MESH | INSPECT | 1 | 25 | 4% | -| MESH | INSERT | 2 | 10 | 20% | +| MESH | INSPECT | 4 | 25 | 16% | +| MESH | INSERT | 4 | 10 | 40% | | MESH | SELECT | 0 | 10 | 0% | | MESH | EXPORT | 0 | 1 | 0% | | MESH | POSITION | 2 | 2 | 100% | | FORM | CREATE | 2 | 14 | 14% | -| FORM | MODIFY | 3 | 31 | 10% | +| FORM | MODIFY | 5 | 31 | 16% | | FORM | SYMMETRY | 0 | 6 | 0% | | FORM | UTILITIES | 0 | 5 | 0% | | FORM | CONSTRUCT | 2 | 20 | 10% | -| FORM | INSPECT | 1 | 25 | 4% | -| FORM | INSERT | 4 | 13 | 31% | +| FORM | INSPECT | 4 | 25 | 16% | +| FORM | INSERT | 6 | 13 | 46% | | FORM | ASSEMBLE | 0 | 1 | 0% | | FORM | SELECT | 0 | 15 | 0% | | FORM | FINISH FORM | 0 | 1 | 0% | -| PLASTIC | SETUP | 0 | 2 | 0% | -| PLASTIC | CREATE | 16 | 35 | 46% | -| PLASTIC | MODIFY | 14 | 32 | 44% | -| PLASTIC | ASSEMBLE | 1 | 20 | 5% | +| PLASTIC | SETUP | 2 | 2 | 100% | +| PLASTIC | CREATE | 26 | 35 | 74% | +| PLASTIC | MODIFY | 18 | 32 | 56% | +| PLASTIC | ASSEMBLE | 6 | 20 | 30% | | PLASTIC | CONFIGURE | 0 | 3 | 0% | | PLASTIC | CONSTRUCT | 2 | 20 | 10% | -| PLASTIC | INSPECT | 1 | 26 | 4% | -| PLASTIC | INSERT | 3 | 11 | 27% | +| PLASTIC | INSPECT | 4 | 26 | 15% | +| PLASTIC | INSERT | 5 | 11 | 45% | | PLASTIC | SELECT | 0 | 15 | 0% | | PLASTIC | POSITION | 2 | 2 | 100% | | MANAGE | ASSIGN | 0 | 1 | 0% | @@ -103,7 +103,7 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE | UTILITIES | NEST | 0 | 1 | 0% | | UTILITIES | ADD-INS | 0 | 2 | 0% | | UTILITIES | UTILITY | 1 | 3 | 33% | -| UTILITIES | INSPECT | 1 | 24 | 4% | +| UTILITIES | INSPECT | 4 | 24 | 17% | | UTILITIES | CREATE PMI | 0 | 4 | 0% | | UTILITIES | SELECT | 0 | 15 | 0% | | UTILITIES | POSITION | 2 | 2 | 100% | @@ -115,17 +115,23 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › CREATE › Revolve (`Revolve`) - SOLID › CREATE › Sweep (`Sweep`) - SOLID › CREATE › Loft (`SolidLoft`) +- SOLID › CREATE › Rib (`FusionRibCommand`) +- SOLID › CREATE › Web (`FusionWebCommand`) +- SOLID › CREATE › Emboss (`EmbossCmd`) - SOLID › CREATE › Hole (`FusionHoleCommand`) - SOLID › CREATE › Thread (`FusionThreadCommand`) - SOLID › CREATE › Box (`PrimitiveBox`) - SOLID › CREATE › Cylinder (`PrimitiveCylinder`) - SOLID › CREATE › Sphere (`PrimitiveSphere`) - SOLID › CREATE › Torus (`PrimitiveTorus`) +- SOLID › CREATE › Coil (`PrimitiveCoil`) - SOLID › CREATE › Pipe (`PrimitivePipe`) - SOLID › CREATE › Bounding Solid (`StockModelCommand`) - SOLID › CREATE › Rectangular Pattern (`PatternRectangular`) - SOLID › CREATE › Circular Pattern (`PatternCircular`) +- SOLID › CREATE › Pattern on Path (`PatternOnPath`) - SOLID › CREATE › Mirror (`MirrorCommand`) +- SOLID › CREATE › Joint Origin (`JointOrigin`) - SOLID › MODIFY › Press Pull (`FusionPressPullCommand`) - SOLID › MODIFY › Fillet (`FusionFilletEdgesCommand`) - SOLID › MODIFY › Chamfer (`FusionChamferCommand`) @@ -134,17 +140,31 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › MODIFY › Scale (`ModifyScale`) - SOLID › MODIFY › Combine (`FusionCombineCommand`) - SOLID › MODIFY › Offset Face (`FusionOffsetFacesCommand`) +- SOLID › MODIFY › Replace Face (`FusionReplaceFaceCommand`) - SOLID › MODIFY › Split Body (`FusionSplitBodyCommand`) - SOLID › MODIFY › Move/Copy (`FusionMoveCommand`) +- SOLID › MODIFY › Align (`AlignCmd`) - SOLID › MODIFY › Delete (`FusionDeleteCommand`) +- SOLID › MODIFY › Remove (`SoftDeleteCommand`) - SOLID › MODIFY › Physical Material (`PhysicalMaterialCommand`) +- SOLID › MODIFY › Appearance (`AppearanceCommand`) - SOLID › MODIFY › Change Parameters (`ChangeParameterCommand`) - SOLID › MODIFY › Compute All (`FusionComputeAllCommand`) - SOLID › ASSEMBLE › Insert Component (`FusionImportCommandFromToolbar`) - SOLID › ASSEMBLE › New Component (`FusionCreateNewComponentCommand`) +- SOLID › ASSEMBLE › Joint (`JointAssembleCmdNew`) +- SOLID › ASSEMBLE › As-Built Joint (`JointAsBuiltCmd`) +- SOLID › ASSEMBLE › Rigid Group (`RigidGroupCmd`) +- SOLID › ASSEMBLE › Drive Joints (`FusionMoveJointsCommand`) +- SOLID › ASSEMBLE › Motion Link (`FusionMotionRelationshipCommand`) - SOLID › CONSTRUCT › Offset Plane (`ConstructionPlaneOffsetFromPlaneCommand`) - SOLID › CONSTRUCT › Plane at Angle (`ConstructionPlaneAtAngleCommand`) - SOLID › INSPECT › Measure (`MeasureCommand`) +- SOLID › INSPECT › Interference (`InterferenceCheckCommand`) +- SOLID › INSPECT › Section Analysis (`FusionHalfSectionViewCommand`) +- SOLID › INSPECT › Curvature Comb Analysis (`FusionCurvatureCombAnalysisCommand`) +- SOLID › INSERT › Decal (`FusionAddEditDecalCommand`) +- SOLID › INSERT › Canvas (`FusionAddCanvasCommand`) - SOLID › INSERT › Insert SVG (`SketchImportSVG`) - SOLID › INSERT › Insert DXF (`ImportDxfFileCommand`) - SOLID › INSERT › Insert Mesh (`ParaMeshInsertAlignCommand`) @@ -219,6 +239,11 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SKETCH › CONSTRAINTS › Curvature (`ConstraintSmooth`) - SKETCH › CONSTRAINTS › Polygon (`SketchPolygonConstraintCmd`) - SKETCH › INSPECT › Measure (`MeasureCommand`) +- SKETCH › INSPECT › Interference (`InterferenceCheckCommand`) +- SKETCH › INSPECT › Section Analysis (`FusionHalfSectionViewCommand`) +- SKETCH › INSPECT › Curvature Comb Analysis (`FusionCurvatureCombAnalysisCommand`) +- SKETCH › INSERT › Decal (`FusionAddEditDecalCommand`) +- SKETCH › INSERT › Canvas (`FusionAddCanvasCommand`) - SKETCH › INSERT › Insert SVG (`SketchImportSVG`) - SKETCH › INSERT › Insert DXF (`ImportDxfFileCommand`) - SKETCH › INSERT › Insert Mesh (`ParaMeshInsertAlignCommand`) @@ -231,11 +256,6 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › CREATE › Create Form (`TSplineBaseFeatureCreationCommand`) - SOLID › CREATE › Derive (`PushDeriveCommand`) - SOLID › CREATE › Automated Modeling (`AutomatedModelingCommand`) -- SOLID › CREATE › Rib (`FusionRibCommand`) -- SOLID › CREATE › Web (`FusionWebCommand`) -- SOLID › CREATE › Emboss (`EmbossCmd`) -- SOLID › CREATE › Coil (`PrimitiveCoil`) -- SOLID › CREATE › Pattern on Path (`PatternOnPath`) - SOLID › CREATE › Geometric Pattern (`FusionPatternGeometricCommand`) - SOLID › CREATE › Thicken (`FusionSurfaceThickenCommand`) - SOLID › CREATE › Boundary Fill (`SurfaceSculpt`) @@ -245,14 +265,10 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › CREATE › Create Linked PCB (`PCBExternalComponentCreationCmd`) - SOLID › CREATE › Derive PCB (`PCBDeriveCmd`) - SOLID › CREATE › Create Unlinked PCB (`PCBCreateCmd`) -- SOLID › CREATE › Joint Origin (`JointOrigin`) - SOLID › CREATE › Generate Timeline (`TimelineBuilderCommand`) - SOLID › MODIFY › Edit Face (`EditFaceCommand`) -- SOLID › MODIFY › Replace Face (`FusionReplaceFaceCommand`) - SOLID › MODIFY › Split Face (`FusionSplitFaceCommand`) - SOLID › MODIFY › Silhouette Split (`FusionPartingLineSplitCmd`) -- SOLID › MODIFY › Align (`AlignCmd`) -- SOLID › MODIFY › Remove (`SoftDeleteCommand`) - SOLID › MODIFY › Arrange (`ArrangeCommand`) - SOLID › MODIFY › Remove Features (`FusionRemoveFeaturesCommand`) - SOLID › MODIFY › Remove Faces (`FusionRemoveFacesCommand`) @@ -260,7 +276,6 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › MODIFY › Volumetric Lattice (`VolFieldCreateLatticeCommand`) - SOLID › MODIFY › Volumetric Texture (`VolFieldTextureCommand`) - SOLID › MODIFY › VolFieldModifyModelCommand (`VolFieldModifyModelCommand`) -- SOLID › MODIFY › Appearance (`AppearanceCommand`) - SOLID › MODIFY › Manage Materials (`MaterialCommand`) - SOLID › MODIFY › Compute Unresolved (`FusionComputeUnresolvedCommand`) - SOLID › MODIFY › Convert (`TSpline2BRepCommand`) @@ -272,12 +287,7 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › ASSEMBLE › Center Constraint (`MidplaneConstraintCmd`) - SOLID › ASSEMBLE › Tangent Constraint (`TangentConstraintCmd`) - SOLID › ASSEMBLE › Joint Constraint (`JointConstraintCmd`) -- SOLID › ASSEMBLE › Joint (`JointAssembleCmdNew`) -- SOLID › ASSEMBLE › As-Built Joint (`JointAsBuiltCmd`) - SOLID › ASSEMBLE › As-Built Constraint (`AsBuiltConstraintCmd`) -- SOLID › ASSEMBLE › Rigid Group (`RigidGroupCmd`) -- SOLID › ASSEMBLE › Drive Joints (`FusionMoveJointsCommand`) -- SOLID › ASSEMBLE › Motion Link (`FusionMotionRelationshipCommand`) - SOLID › ASSEMBLE › Motion Study (`FusionMotionStudyCommand`) - SOLID › ASSEMBLE › Enable Contact Sets (`EnableContactSetsCmd`) - SOLID › ASSEMBLE › Enable All Contact (`EnableAllContactCmd`) @@ -304,9 +314,6 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › CONSTRUCT › Point At Center Of Circle/Sphere/Torus (`ConstructionPointFromCircleOrSphereCommand`) - SOLID › CONSTRUCT › Point At Edge And Plane (`ConstructionPointAtEdgeAndPlaneCommand`) - SOLID › CONSTRUCT › Point Along Path (`ConstructionPointAlongPathCommand`) -- SOLID › INSPECT › Interference (`InterferenceCheckCommand`) -- SOLID › INSPECT › Section Analysis (`FusionHalfSectionViewCommand`) -- SOLID › INSPECT › Curvature Comb Analysis (`FusionCurvatureCombAnalysisCommand`) - SOLID › INSPECT › Curvature Map Analysis (`FusionCurvatureMapAnalysisCommand`) - SOLID › INSPECT › Draft Analysis (`FusionDraftAnalysisCommand`) - SOLID › INSPECT › Environment Map Analysis (`FusionEnvironmentMapAnalysisCommand`) @@ -328,8 +335,6 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SOLID › INSPECT › Change Summary (`ShowChangeAwarenessToolbarCmd`) - SOLID › INSPECT › History (`ShowHistoryToolbarCmd`) - SOLID › INSPECT › Sustainability Insights (`GravityClimate_ManufacturingSustainabilityInsights_MainDialog`) -- SOLID › INSERT › Decal (`FusionAddEditDecalCommand`) -- SOLID › INSERT › Canvas (`FusionAddCanvasCommand`) - SOLID › INSERT › Insert T-Spline (`TSplineImportCommand`) - SOLID › INSERT › Insert Fastener (`FusionFastenersCommand`) - SOLID › INSERT › Insert Derive (`PullDeriveCommand`) @@ -355,9 +360,6 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SKETCH › CONFIGURE › Configure (`FusionStartDesignConfigModeCmd`) - SKETCH › CONFIGURE › Display Configuration Table (`FusionShowDesignConfigPanelCmd`) - SKETCH › CONFIGURE › Create Configuration Rules (`FusionConfigurationRulesCmd`) -- SKETCH › INSPECT › Interference (`InterferenceCheckCommand`) -- SKETCH › INSPECT › Section Analysis (`FusionHalfSectionViewCommand`) -- SKETCH › INSPECT › Curvature Comb Analysis (`FusionCurvatureCombAnalysisCommand`) - SKETCH › INSPECT › Curvature Map Analysis (`FusionCurvatureMapAnalysisCommand`) - SKETCH › INSPECT › Draft Analysis (`FusionDraftAnalysisCommand`) - SKETCH › INSPECT › Environment Map Analysis (`FusionEnvironmentMapAnalysisCommand`) @@ -379,8 +381,6 @@ Deferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE - SKETCH › INSPECT › Change Summary (`ShowChangeAwarenessToolbarCmd`) - SKETCH › INSPECT › History (`ShowHistoryToolbarCmd`) - SKETCH › INSPECT › Sustainability Insights (`GravityClimate_ManufacturingSustainabilityInsights_MainDialog`) -- SKETCH › INSERT › Decal (`FusionAddEditDecalCommand`) -- SKETCH › INSERT › Canvas (`FusionAddCanvasCommand`) - SKETCH › INSERT › Insert T-Spline (`TSplineImportCommand`) - SKETCH › INSERT › Insert Fastener (`FusionFastenersCommand`) - SKETCH › INSERT › Duplicate With Joints (`FusionDuplicateWithJointsCommand`)