From 0638f91f00f7dd6f902be0a50dd6ff3b9ad94513 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Wed, 14 Oct 2026 10:15:41 -0400 Subject: [PATCH] engine: read the files commands name through fieldcraft_io::fs file.open, geometry.open, geometry.import.mesh and reopening a model's geometry read through the io crate's file edge instead of SolidCraft's vfs, so in the browser they see the files the page holds (the same store saves write to). Natively it is the same capped regular-file read. fieldcraft-geometry drops its path-based readers (read_file, open_design_file, read_3mf_file): it works on bytes, and reading files is the io crate's job. Part of #119 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --- crates/engine/src/cmd/file.rs | 17 +++++++++++------ crates/engine/src/cmd/geometry.rs | 4 ++-- crates/geometry/src/design.rs | 13 ++----------- crates/geometry/src/lib.rs | 10 +--------- crates/geometry/src/tagged_mesh.rs | 6 ------ 5 files changed, 16 insertions(+), 34 deletions(-) diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 545ead2..2fc0087 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -89,6 +89,12 @@ fn open_sample(s: &mut Session, p: &Value) -> Result { Ok(reply) } +/// A file of at most `max` bytes, through FieldCraft's file edge ([`fieldcraft_io::fs`]: the disk, +/// or the page's files in the browser). +pub(crate) fn read_file(path: &str, max: u64) -> Result> { + Ok(fieldcraft_io::fs::read_capped(Path::new(path), max)?) +} + /// `path` made absolute against the working folder (as given where there is none). pub(crate) fn absolute(path: &str) -> String { std::path::absolute(path).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| path.to_string()) @@ -119,7 +125,7 @@ fn reopen( stored: &SourceFile, base: Option<&Path>, roots: &Roots, - max: usize, + max: u64, open: impl FnOnce(&[u8]) -> fieldcraft_geometry::Result, warnings: &mut Vec, ) -> (Option, SourceFile) { @@ -132,7 +138,7 @@ fn reopen( return (None, kept); } }; - let bytes = match fieldcraft_geometry::read_file(&checked, max) { + let bytes = match read_file(&checked, max) { Ok(b) => b, Err(e) => { warnings.push(format!("{what} `{path}` was not opened ({e}); the model opens without it")); @@ -173,13 +179,13 @@ impl Session { let mut sources = Geometry::default(); let mut design: Option> = None; if let Some(stored) = &file.geometry.design { - let (opened, source) = reopen("design", stored, base, roots, MAX_DESIGN_BYTES, open_design, &mut warnings); + let (opened, source) = reopen("design", stored, base, roots, MAX_DESIGN_BYTES as u64, open_design, &mut warnings); design = opened.map(Arc::new); sources.design = Some(source); } let mut surfaces = Vec::new(); for stored in &file.geometry.meshes { - let (opened, source) = reopen("mesh", stored, base, roots, tagged_mesh::MAX_FILE_BYTES, tagged_mesh::read_3mf, &mut warnings); + let (opened, source) = reopen("mesh", stored, base, roots, tagged_mesh::MAX_FILE_BYTES as u64, tagged_mesh::read_3mf, &mut warnings); surfaces.extend(opened.into_iter().flatten().map(Arc::new)); sources.meshes.push(source); } @@ -226,8 +232,7 @@ fn load(s: &mut Session, bytes: &[u8], path: Option, saved: bool) -> Res fn open(s: &mut Session, p: &Value) -> Result { let path = absolute(&file_param(s, p, "path", Access::Read)?); - let max = usize::try_from(fieldcraft_io::MAX_FILE_BYTES).unwrap_or(usize::MAX); - let bytes = fieldcraft_geometry::read_file(&path, max)?; + let bytes = read_file(&path, fieldcraft_io::MAX_FILE_BYTES)?; load(s, &bytes, Some(path.clone()), true).map_err(|e| EngineError::Params(format!("{path}: {e}"))) } diff --git a/crates/engine/src/cmd/geometry.rs b/crates/engine/src/cmd/geometry.rs index 9ee9b76..8447df7 100644 --- a/crates/engine/src/cmd/geometry.rs +++ b/crates/engine/src/cmd/geometry.rs @@ -42,7 +42,7 @@ pub const IMPORT_MESH: CommandSpec = CommandSpec { fn open(s: &mut Session, p: &Value) -> Result { let path = super::file_param(s, p, "path", Access::Read)?; - let bytes = fieldcraft_geometry::read_file(&path, MAX_DESIGN_BYTES)?; + let bytes = super::file::read_file(&path, MAX_DESIGN_BYTES as u64)?; let source = SourceFile::of(super::file::absolute(&path), &bytes)?; let design = open_design(&bytes)?; let out = json!({ @@ -101,7 +101,7 @@ fn import_mesh(s: &mut Session, p: &Value) -> Result { if s.sources.meshes.len() >= fieldcraft_io::MAX_MESH_SOURCES { return Err(EngineError::Params(format!("a model imports at most {} meshes", fieldcraft_io::MAX_MESH_SOURCES))); } - let bytes = fieldcraft_geometry::read_file(&path, tagged_mesh::MAX_FILE_BYTES)?; + let bytes = super::file::read_file(&path, tagged_mesh::MAX_FILE_BYTES as u64)?; let source = SourceFile::of(super::file::absolute(&path), &bytes)?; let surfaces = tagged_mesh::read_3mf(&bytes)?; let first = s.surfaces.len(); diff --git a/crates/geometry/src/design.rs b/crates/geometry/src/design.rs index 6d975f2..9ffe3e2 100644 --- a/crates/geometry/src/design.rs +++ b/crates/geometry/src/design.rs @@ -2,9 +2,8 @@ //! with SolidCraft, and hand back its bodies with persistent face names. //! //! The design is rebuilt from its authoritative inputs (sketches, features, parameters); cached -//! display meshes in the file are never used. [`open_design`] takes bytes; [`open_design_file`] -//! reads a path through SolidCraft's file access, which in the browser is the in-memory files the -//! web host loaded. +//! display meshes in the file are never used. [`open_design`] takes the file's bytes (reading +//! files is `fieldcraft_io::fs`'s job, which in the browser holds the page's files). use solidcraft_doc::{Document, Model, naming}; use solidcraft_kernel::{KernelError, guard}; @@ -45,11 +44,6 @@ pub fn open_design(bytes: &[u8]) -> Result { rebuild(&doc) } -/// Read a design file (a regular file of at most [`MAX_DESIGN_BYTES`]) and rebuild it. -pub fn open_design_file(path: &str) -> Result { - open_design(&crate::read_file(path, MAX_DESIGN_BYTES)?) -} - /// Rebuild an in-memory document (the design half of [`open_design`]). pub(crate) fn rebuild(doc: &Document) -> Result { let model = guard("rebuild design", || { @@ -165,8 +159,5 @@ mod tests { assert!(open_design(bytes).is_err(), "{bytes:?}"); } assert!(open_design(&vec![b' '; MAX_DESIGN_BYTES + 1]).is_err()); - for path in ["", "/", "/dev/zero", "/no/such/design.solidcraft"] { - assert!(open_design_file(path).is_err(), "{path}"); - } } } diff --git a/crates/geometry/src/lib.rs b/crates/geometry/src/lib.rs index bca9fc4..4aec077 100644 --- a/crates/geometry/src/lib.rs +++ b/crates/geometry/src/lib.rs @@ -22,7 +22,7 @@ pub mod sample; #[cfg(feature = "scripts")] mod script; -pub use design::{Design, FeatureError, MAX_DESIGN_BYTES, open_design, open_design_file}; +pub use design::{Design, FeatureError, MAX_DESIGN_BYTES, open_design}; pub mod tagged_mesh; #[cfg(feature = "scripts")] pub use script::{MAX_SCRIPT_BYTES, script_design}; @@ -47,14 +47,6 @@ impl From for Error { } } -/// Read a regular file of at most `max_bytes` through SolidCraft's file access (in the browser, -/// the in-memory files the web host loaded). Opening a design or a 3MF package from these bytes -/// ([`open_design`], [`tagged_mesh::read_3mf`]) lets the caller record a hash of exactly what it -/// opened. -pub fn read_file(path: &str, max_bytes: usize) -> Result> { - solidcraft_io::vfs::read(path, max_bytes).map_err(|e| Error::Invalid(format!("cannot read `{path}`: {e}"))) -} - /// SolidCraft's linear modelling tolerance (mm): points closer than this are the same point. const LINEAR_TOLERANCE: f64 = solidcraft_geom::LINEAR_TOL; diff --git a/crates/geometry/src/tagged_mesh.rs b/crates/geometry/src/tagged_mesh.rs index 53b87ca..66e7a8f 100644 --- a/crates/geometry/src/tagged_mesh.rs +++ b/crates/geometry/src/tagged_mesh.rs @@ -344,12 +344,6 @@ fn surface(mut o: Object, k: usize, id: u32, xf: &Xf, unit: f64) -> Result Result> { - read_3mf(&crate::read_file(path, MAX_FILE_BYTES)?) -} - /// The surfaces of a 3MF package, one per build item, in millimetres. pub fn read_3mf(bytes: &[u8]) -> Result> { if bytes.len() > MAX_FILE_BYTES {