forked from 1b-agent/fieldcraft-upstream
* geometry: open .solidcraft designs, rebuild them and keep persistent face names open_design(bytes) reads a design (container or JSON) with solidcraft-io, rebuilds its timeline with solidcraft-doc and returns its bodies; each Shape carries SolidCraft's persistent face names by B-rep face index, so a face keeps its name through a parameter edit. sample::plate_design builds a parametric test plate in code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * engine: geometry.open, geometry.bodies and geometry.tessellate The session holds the open design (shared, restored on a failed command, journalled when it changes). Hostile-params coverage includes non-files, missing files, oversized paths and extreme tolerances against an open design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: opening SolidCraft designs in the architecture and roadmap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * geometry: open_design_file reads through SolidCraft's bounded file access; simplify The engine's hand-rolled capped read is replaced by open_design_file, which uses solidcraft_io::vfs::read (refuses non-regular files before opening, works on the browser's in-memory files). Drops the duplicate size check, simplifies the sample sketch and the design-change test in Session::execute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
4.8 KiB
Rust
120 lines
4.8 KiB
Rust
//! Sample SolidCraft designs built in code: test parts whose recipe is this source, so no design
|
|
//! file is committed (docs/adr/0001-libraries.md, test geometry).
|
|
|
|
use solidcraft_doc::sketch::{ConstraintKind, Sketch};
|
|
use solidcraft_doc::{Direction, Document, Extent, FeatureKind, Operation, PlaneRef, ProfileSel};
|
|
use solidcraft_geom::Vec2;
|
|
|
|
use crate::{Error, Result};
|
|
|
|
/// A rectangular plate with a round through hole, as a parametric design: the sketch's width is
|
|
/// driven by the design parameter `width` and the extrusion by `thickness`, so editing either
|
|
/// rebuilds the same faces at a new size.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct Plate {
|
|
/// Along X, mm. At least `depth`.
|
|
pub width: f64,
|
|
/// Along Y, mm.
|
|
pub depth: f64,
|
|
/// Along Z, mm.
|
|
pub thickness: f64,
|
|
/// The hole is centred `depth / 2` from the corner at the origin, so it needs to be smaller
|
|
/// than `depth`.
|
|
pub hole_diameter: f64,
|
|
}
|
|
|
|
impl Default for Plate {
|
|
fn default() -> Self {
|
|
Plate { width: 40.0, depth: 30.0, thickness: 10.0, hole_diameter: 10.0 }
|
|
}
|
|
}
|
|
|
|
fn failed(e: impl std::fmt::Display) -> Error {
|
|
Error::Failed(format!("sample design: {e}"))
|
|
}
|
|
|
|
fn extrude(sketch: u64, distance: &str, operation: Operation) -> FeatureKind {
|
|
FeatureKind::Extrude {
|
|
sketch,
|
|
profiles: ProfileSel::All,
|
|
extent: Extent {
|
|
distance: distance.into(),
|
|
direction: Direction::Positive,
|
|
distance2: None,
|
|
start_offset: None,
|
|
through_all: false,
|
|
taper: None,
|
|
to: None,
|
|
to_offset: None,
|
|
from: None,
|
|
},
|
|
operation,
|
|
targets: vec![],
|
|
}
|
|
}
|
|
|
|
fn sketch_on_xy(doc: &mut Document, sketch: Sketch, name: &str) -> Result<u64> {
|
|
doc.add_feature(FeatureKind::Sketch { plane: PlaneRef::Origin { name: "XY".into() }, sketch }, Some(name)).map_err(failed)
|
|
}
|
|
|
|
/// The plate as `.solidcraft` file bytes.
|
|
pub fn plate_design(p: &Plate) -> Result<Vec<u8>> {
|
|
let ok = |v: f64| v.is_finite() && v > 0.0 && v <= 1e6;
|
|
if !(ok(p.width) && ok(p.depth) && ok(p.thickness) && ok(p.hole_diameter)) || p.width < p.depth || p.hole_diameter >= p.depth {
|
|
return Err(Error::Invalid(format!("plate dimensions out of range: {p:?}")));
|
|
}
|
|
let mut doc = Document::new("Plate");
|
|
doc.set_param("width", &format!("{} mm", p.width), Some("mm"), None).map_err(failed)?;
|
|
doc.set_param("thickness", &format!("{} mm", p.thickness), Some("mm"), None).map_err(failed)?;
|
|
|
|
let mut sk = Sketch::new();
|
|
let corners = [Vec2::new(0.0, 0.0), Vec2::new(p.width, 0.0), Vec2::new(p.width, p.depth), Vec2::new(0.0, p.depth)];
|
|
let [p0, p1, p2, p3] = corners.map(|c| sk.add_point(c, None));
|
|
let (p0, p1, p2, p3) = (p0.map_err(failed)?, p1.map_err(failed)?, p2.map_err(failed)?, p3.map_err(failed)?);
|
|
let l0 = sk.add_line_pts(p0, p1, None).map_err(failed)?;
|
|
let l1 = sk.add_line_pts(p1, p2, None).map_err(failed)?;
|
|
let l2 = sk.add_line_pts(p2, p3, None).map_err(failed)?;
|
|
let l3 = sk.add_line_pts(p3, p0, None).map_err(failed)?;
|
|
use ConstraintKind::{Coincident, Horizontal, Vertical};
|
|
// Point 0 of a sketch is its origin.
|
|
for c in [Coincident { p: p0, q: 0 }, Horizontal { l: l0 }, Horizontal { l: l2 }, Vertical { l: l1 }, Vertical { l: l3 }] {
|
|
sk.add_constraint(c, None).map_err(failed)?;
|
|
}
|
|
let width = doc.new_model_param("width", "mm");
|
|
sk.add_constraint(ConstraintKind::Length { l: l0, value: p.width }, Some(width)).map_err(failed)?;
|
|
let depth = doc.new_model_param(&format!("{} mm", p.depth), "mm");
|
|
sk.add_constraint(ConstraintKind::Length { l: l1, value: p.depth }, Some(depth)).map_err(failed)?;
|
|
let outline = sketch_on_xy(&mut doc, sk, "Outline")?;
|
|
doc.add_feature(extrude(outline, "thickness", Operation::NewBody), Some("Plate")).map_err(failed)?;
|
|
|
|
let mut sk = Sketch::new();
|
|
let c = p.depth / 2.0;
|
|
sk.add_circle(Vec2::new(c, c), p.hole_diameter / 2.0, None, None).map_err(failed)?;
|
|
let hole = sketch_on_xy(&mut doc, sk, "Hole sketch")?;
|
|
// Past the top face, so the cut never ends on it.
|
|
doc.add_feature(extrude(hole, "thickness + 1 mm", Operation::Cut), Some("Hole")).map_err(failed)?;
|
|
|
|
solidcraft_io::write_design_container(&doc).map_err(failed)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn hostile_dimensions_are_an_error() {
|
|
let base = Plate::default();
|
|
for p in [
|
|
Plate { width: f64::NAN, ..base },
|
|
Plate { width: -1.0, ..base },
|
|
Plate { thickness: 0.0, ..base },
|
|
Plate { thickness: f64::INFINITY, ..base },
|
|
Plate { width: 10.0, ..base },
|
|
Plate { hole_diameter: 30.0, ..base },
|
|
Plate { depth: 1e300, width: 1e300, ..base },
|
|
] {
|
|
assert!(plate_design(&p).is_err(), "{p:?}");
|
|
}
|
|
}
|
|
}
|