Components are frames (user bug #4)
What a component holds moves with its occurrence, and picks in the world land in the
component's frame:
- Sketches and construction planes show where their component is placed (free move, joint
drive, Capture Position); sketch.inspect reports sketch points in the world.
- frames.rs maps a command's world picks into the frame of the body they lie on (through the
inverse of its placement: a moved component or a pasted instance), points that make new
geometry into the active component's frame, and directions with them; instance body names
("Body1 (Pin:2)") become the component's body. Fillet/chamfer edges, shell/offset/draft
faces, face extrude/revolve, sketch on face, holes, sheet metal and plastic picks, primitives,
appearance faces, analyses. Once per command (not again for commands run by commands); the
active-frame fallback stays for commands without a table. Nothing changes while every
placement is the identity.
- component.move_bodies keeps a body where it is in the world (Document.body_offsets: from the
new component's frame to the one it was made in; renames and deleted components follow).
- The UI reads the placed model (Session::world_state) for display, picking, sketch editing and
dialogs; live previews use it once anything is placed.
Tests (engine): sketches and construction planes move (and turn) with the component; world
picks fillet, sketch, hole and make boxes in a moved component; a pick on a pasted instance;
move_bodies keeps the world place there and back; a sketch on a face follows a Move feature.
QA's s34 now runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
c0725ebc86
commit
7a2eb059c4
30 files changed
+669
-72
No files matched your search
@@ -278,7 +278,7 @@ impl Dialog {
|
||||
|
||||
pub fn for_command(app: &SolveApp, id: &str) -> Option<Dialog> {
|
||||
let s = &app.session;
|
||||
let has_bodies = !s.model.state().bodies.is_empty();
|
||||
let has_bodies = !s.world_state().bodies.is_empty();
|
||||
let mut d = match id {
|
||||
"SketchCreate" => Dialog::new(Kind::Sketch, vec![SelInput::new("Plane", PLANES | PLANAR_FACES, false)]),
|
||||
"Extrude" => Dialog::new(
|
||||
@@ -614,7 +614,7 @@ impl Dialog {
|
||||
// Tangent chain: an edge brings the edges that continue it smoothly.
|
||||
if chain
|
||||
&& let Sel::Edge { body, index, .. } = &sel
|
||||
&& let Some(b) = s.model.state().body(body)
|
||||
&& let Some(b) = s.world_state().body(body)
|
||||
{
|
||||
let m = b.mesh();
|
||||
picked = m
|
||||
@@ -660,7 +660,7 @@ impl Dialog {
|
||||
|
||||
/// Profiles of the sketch a feature would use (active, else the last), when there is just one.
|
||||
fn default_profiles(s: &Session) -> Vec<Sel> {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let sid = s.active_sketch.or_else(|| s.doc.features.iter().rev().find(|f| matches!(f.kind, FeatureKind::Sketch { .. })).map(|f| f.id));
|
||||
match sid.and_then(|id| st.sketch(id).map(|ss| (id, ss.profiles.len()))) {
|
||||
Some((id, 1)) => vec![Sel::Profile { sketch: id, index: 0 }],
|
||||
@@ -670,7 +670,7 @@ fn default_profiles(s: &Session) -> Vec<Sel> {
|
||||
|
||||
/// The plane of a planar body face: (point on it, unit normal).
|
||||
pub fn planar_face(s: &Session, body: &str, face: usize) -> Option<(Vec3, Vec3)> {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let b = st.body(body)?;
|
||||
let tol = (b.body.size() * 1e-3).max(1e-3);
|
||||
let f = b.body.faces(tol).ok()?.into_iter().find(|f| f.index == face)?;
|
||||
@@ -1670,7 +1670,7 @@ pub fn start_sketch(app: &mut SolveApp, sel: &Sel) {
|
||||
|
||||
/// Animate the camera to look straight at the active sketch plane.
|
||||
pub fn look_at_sketch(app: &mut SolveApp) {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
if let Some(ss) = app.session.active_sketch.and_then(|id| st.sketch(id)) {
|
||||
let mut to = app.cam.looking_from(ss.plane.normal());
|
||||
// Keep the sketch's own x axis to the right when looking straight down or up.
|
||||
@@ -1781,7 +1781,7 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
|
||||
Kind::Fillet { radius, chamfer, .. } => {
|
||||
need(0, "edges")?;
|
||||
// Faces stand for all their edges.
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let mut pts: Vec<Vec3> = Vec::new();
|
||||
for x in sels(d, 0) {
|
||||
match x {
|
||||
@@ -2165,7 +2165,7 @@ fn pattern_features(s: &Session, objects: &[Sel]) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn source_features(s: &Session, bodies: &[String]) -> Vec<String> {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let mut features: Vec<String> = Vec::new();
|
||||
for n in bodies {
|
||||
if let Some(f) = st.body(n).and_then(|b| s.doc.feature(b.feature)).map(|f| f.name.clone())
|
||||
@@ -2179,7 +2179,7 @@ fn source_features(s: &Session, bodies: &[String]) -> Vec<String> {
|
||||
|
||||
/// The sketch holding all these curve ids (the active sketch first, never `not`).
|
||||
fn curves_sketch(app: &SolveApp, ids: &[String], not: Option<u64>) -> Option<u64> {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let has =
|
||||
|sid: u64| st.sketch(sid).is_some_and(|ss| ids.iter().all(|id| ss.sketch.curve_index(id).is_some() || ss.sketch.wire_index(id).is_some()));
|
||||
if let Some(a) = app.session.active_sketch
|
||||
@@ -2204,7 +2204,7 @@ pub fn axis_of(app: &SolveApp, sel: &Sel) -> Option<(Vec3, Vec3)> {
|
||||
},
|
||||
)),
|
||||
Sel::SketchCurve { id } => {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let sid = curves_sketch(app, std::slice::from_ref(id), None)?;
|
||||
let ss = st.sketch(sid)?;
|
||||
let c = ss.sketch.curves.get(ss.sketch.curve_index(id)?)?;
|
||||
@@ -2240,7 +2240,7 @@ pub(crate) fn profile_indices(ss: &solvecraft_engine::doc::SolvedSketch, sel: &P
|
||||
|
||||
/// The edge of a visible body through (or nearest) a point.
|
||||
pub(crate) fn edge_sel(s: &Session, p: Vec3) -> Option<Sel> {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let mut best: Option<(f64, Sel)> = None;
|
||||
for b in &st.bodies {
|
||||
let m = b.mesh();
|
||||
@@ -2256,7 +2256,7 @@ pub(crate) fn edge_sel(s: &Session, p: Vec3) -> Option<Sel> {
|
||||
|
||||
/// The body face containing a point (nearest triangle).
|
||||
pub(crate) fn face_sel(s: &Session, p: Vec3) -> Option<Sel> {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let mut best: Option<(f64, Sel)> = None;
|
||||
for b in &st.bodies {
|
||||
let m = b.mesh();
|
||||
@@ -2305,7 +2305,7 @@ fn pt3(v: Vec3) -> Value {
|
||||
pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dialog> {
|
||||
let s = &app.session;
|
||||
let f = s.doc.feature(id)?.clone();
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let start = |cmd: &str| Dialog::for_command(app, cmd);
|
||||
let mut d = match &f.kind {
|
||||
FeatureKind::Extrude { sketch, profiles, extent, operation, targets } => {
|
||||
|
||||
Reference in new issue
Block a user