* Imports become components: a STEP assembly's tree as nested components (#1386) Every imported file (STEP, IGES, 3MF, STL, OBJ: inserted, dropped, opened as a design, or file.insert_step / file.insert_mesh / doc.open from scripts, the CLI and MCP) now comes in as a new component named after the file, with its Import feature inside it. A STEP assembly's products become nested components; a part placed more than once is one component with an occurrence per placement, and the Import feature skips the file's later copies (`skip`, format 21 note). The contents are held as one rigid group (rigid joints, glyphs hidden); moving an inner part is refused until component.unlock ("Unlock Contents") releases it. Y up turns the import's component. Replies report component, component_name and occurrence. Undo is one step; designs with loose imports are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep an import's rigid group hidden when the window loads the file in the background Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Import instances' skip list is format 22: format 21 ships in 0.12.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Catalog entries for Unlock Contents Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d43c414b7b
commit
27529d9742
37 files changed
+968
-103
No files matched your search
@@ -1475,6 +1475,11 @@ pub enum FeatureKind {
|
||||
/// The file's product/assembly tree, kept for components.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
components: Vec<solidcraft_kernel::StepNode>,
|
||||
/// The file's bodies (by index) this feature does not build: later instances of a part
|
||||
/// the file places more than once, shown by the other occurrences of that part's
|
||||
/// component (#1386). Empty in designs from before format 22: every body is built.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
skip: Vec<u64>,
|
||||
},
|
||||
/// Rebuild an upstream constant-section mesh with analytic planar/cylindrical faces.
|
||||
MeshConvert {
|
||||
|
||||
+25
-7
@@ -4681,15 +4681,12 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
FeatureKind::Import { step, .. } => {
|
||||
FeatureKind::Import { step, skip, .. } => {
|
||||
let imp = kernel::step_import_shared(step)?;
|
||||
let digest = crate::config::payload_digest(step);
|
||||
// Files from board and assembly tools give thousands of bodies the same name: name
|
||||
// them from a set, not by searching the bodies for every candidate (cubic).
|
||||
let mut names = BodyNames::new(st);
|
||||
for (k, b) in imp.bodies.iter().enumerate() {
|
||||
let base = f.body_names.get(k).filter(|n| !n.trim().is_empty()).unwrap_or(&b.name);
|
||||
let name = names.unique(base);
|
||||
let names = import_body_names(st, &f.body_names, &imp, skip);
|
||||
for (b, name) in imp.bodies.iter().zip(names) {
|
||||
let Some(name) = name else { continue };
|
||||
let b = crate::lineage::numbered_tool(b.body.clone().with_import_certificate(&digest, f.id)?);
|
||||
st.bodies.push(ModelBody::traced_new(name, b, f.id));
|
||||
}
|
||||
@@ -4722,6 +4719,27 @@ fn mesh_bodies(meshes: &[crate::MeshData]) -> Result<Vec<(String, Body)>> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The names an Import feature gives the file's bodies, added after the bodies of `st`, in the
|
||||
/// file's order: `None` for a body it skips (a later instance of a repeated part). `body_names`
|
||||
/// are the feature's stored names (by index), else the file's own.
|
||||
pub fn import_body_names(st: &ModelState, body_names: &[String], imp: &kernel::StepImport, skip: &[u64]) -> Vec<Option<String>> {
|
||||
let skip: std::collections::HashSet<u64> = skip.iter().copied().collect();
|
||||
// Files from board and assembly tools give thousands of bodies the same name: name them
|
||||
// from a set, not by searching the bodies for every candidate (cubic).
|
||||
let mut names = BodyNames::new(st);
|
||||
imp.bodies
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(k, b)| {
|
||||
if skip.contains(&(k as u64)) {
|
||||
return None;
|
||||
}
|
||||
let base = body_names.get(k).filter(|n| !n.trim().is_empty()).unwrap_or(&b.name);
|
||||
Some(names.unique(base))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// [`unique_body_name`] for many bodies in a row: the same names, in time linear in the bodies.
|
||||
struct BodyNames {
|
||||
taken: std::collections::HashSet<String>,
|
||||
|
||||
@@ -200,13 +200,21 @@
|
||||
//! and a bend ending beside more of the fixed face its bend relief (0.11.0 left every corner a
|
||||
//! notch and cut no bend relief), and a wall on part of an edge bends only where it stands
|
||||
//! (0.11.0 refused it).
|
||||
//! - 22: an Import (STEP) feature can list `skip`: indices of the file's bodies it does not
|
||||
//! build, because they are later instances of a part the file places more than once and the
|
||||
//! other occurrences of that part's component show them (#1386: an imported file becomes a
|
||||
//! component, an assembly in it nested components). Version 21 designs need no
|
||||
//! transformation: an Import without it builds every body, as it always did, and designs
|
||||
//! whose imports sit loose in a component are left as they are. The version stamp makes
|
||||
//! 0.12.0 (which reads up to 21) ask for an update instead of building those instances again
|
||||
//! on top of their occurrences.
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{DocError, Document, Result};
|
||||
|
||||
/// The format this build writes.
|
||||
pub const FORMAT: u32 = 21;
|
||||
pub const FORMAT: u32 = 22;
|
||||
|
||||
pub fn format_string(n: u32) -> String {
|
||||
format!("solidcraft/{n}")
|
||||
@@ -223,7 +231,7 @@ pub fn version_of(s: &str) -> Option<u32> {
|
||||
|
||||
/// JSON-level upgrades: entry `i` turns format `i + 1` into `i + 2`.
|
||||
type JsonStep = fn(&mut Value) -> Result<()>;
|
||||
const JSON_STEPS: [JsonStep; 20] = [
|
||||
const JSON_STEPS: [JsonStep; 21] = [
|
||||
|_| Ok(()),
|
||||
v2_to_v3,
|
||||
v3_to_v4,
|
||||
@@ -244,6 +252,7 @@ const JSON_STEPS: [JsonStep; 20] = [
|
||||
|_| Ok(()),
|
||||
|_| Ok(()),
|
||||
|_| Ok(()),
|
||||
|_| Ok(()),
|
||||
];
|
||||
|
||||
/// Stored face and edge names were worked out from geometry before format 14; they get the
|
||||
|
||||
@@ -1054,9 +1054,18 @@ pub fn solve_holding(doc: &Document, st: &ModelState, held: &Held) -> Solution {
|
||||
Tree { joints: asm.joints.iter().map(|j| (j.id, j)).collect(), frames, fixed: placed.clone(), parent: BTreeMap::new(), order: Vec::new() };
|
||||
let mut used: BTreeSet<u64> = BTreeSet::new();
|
||||
let mut queue: VecDeque<u64> = placed.keys().copied().collect();
|
||||
// The joints at each occurrence, in the joints' order (a large assembly is not walked joint by
|
||||
// joint for every occurrence).
|
||||
let mut at: BTreeMap<u64, Vec<&Joint>> = BTreeMap::new();
|
||||
for j in joints.iter().copied() {
|
||||
at.entry(j.a.occurrence).or_default().push(j);
|
||||
if j.b.occurrence != j.a.occurrence {
|
||||
at.entry(j.b.occurrence).or_default().push(j);
|
||||
}
|
||||
}
|
||||
while let Some(occ) = queue.pop_front() {
|
||||
let Some(t) = placed.get(&occ).copied() else { continue };
|
||||
for j in &joints {
|
||||
for j in at.get(&occ).map(Vec::as_slice).unwrap_or_default() {
|
||||
if used.contains(&j.id) || !tree.frames.contains_key(&j.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ pub use assembly::{
|
||||
pub use document::*;
|
||||
pub use eval::{
|
||||
EdgeRef, FeatureResult, Model, ModelBody, ModelState, PlacedBodies, RevolveExtent, SolvedSketch, ThreadInfo, curve_pick_error, edge_names_for,
|
||||
edge_refs, face_point_names, face_point_state, hole_direction_at, object_height, parse_metric_thread, pattern_matrices, revolve_extent,
|
||||
revolve_extent_with_convention, select_profiles, sketch_hole_direction, state_in_frame, state_in_frame_at, through_all_distance, world_state,
|
||||
world_state_reusing,
|
||||
edge_refs, face_point_names, face_point_state, hole_direction_at, import_body_names, object_height, parse_metric_thread, pattern_matrices,
|
||||
revolve_extent, revolve_extent_with_convention, select_profiles, sketch_hole_direction, state_in_frame, state_in_frame_at, through_all_distance,
|
||||
world_state, world_state_reusing,
|
||||
};
|
||||
pub use params::{ParamRow, ParamUser, cycles};
|
||||
pub use solidcraft_kernel as kernel;
|
||||
|
||||
Reference in new issue
Block a user