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
@@ -90,7 +90,7 @@ fn find_group(app: &SolveApp, id: u64) -> Option<&solvecraft_engine::doc::Browse
|
||||
|
||||
/// The bodies in a group (none for sketch and plane groups).
|
||||
pub fn group_bodies(app: &SolveApp, id: u64) -> Vec<String> {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
find_group(app, id)
|
||||
.filter(|g| g.folder == "bodies")
|
||||
.map(|g| g.items.iter().filter(|b| st.body(b).is_some()).cloned().collect())
|
||||
@@ -172,7 +172,7 @@ pub fn toggle_folder(app: &mut SolveApp, component: u64, folder: &str) {
|
||||
pub fn selection_group(app: &SolveApp, target: &Target) -> Option<Value> {
|
||||
let (comp, folder) = match target {
|
||||
Target::Body { name } => {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let b = st.body(name)?;
|
||||
(app.session.doc.body_component(name, b.feature), "bodies")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::viewport::Proj;
|
||||
/// Where the dialog's geometry is: a base point and, for values that can be dragged, the
|
||||
/// direction a growing value points.
|
||||
pub fn anchor(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Option<Vec3>)> {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let first = d.inputs.first()?.items.first()?;
|
||||
let (at, normal) = match first {
|
||||
Sel::Profile { sketch, index } => {
|
||||
|
||||
@@ -212,7 +212,7 @@ fn touched_bodies(app: &SolveApp) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn body_feature(app: &SolveApp, body: &str) -> Option<u64> {
|
||||
app.session.model.state().body(body).map(|b| b.feature)
|
||||
app.session.world_state().body(body).map(|b| b.feature)
|
||||
}
|
||||
|
||||
/// The bodies a target stands for.
|
||||
@@ -451,7 +451,7 @@ fn sketch_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
let profiles = !app.ui.hidden_profiles.contains(&id);
|
||||
let dims = app.ui.shown_dims.contains(&id);
|
||||
let editing = app.session.active_sketch == Some(id);
|
||||
let has_profiles = app.session.model.state().sketch(id).is_some_and(|s| !s.profiles.is_empty());
|
||||
let has_profiles = app.session.world_state().sketch(id).is_some_and(|s| !s.profiles.is_empty());
|
||||
vec![
|
||||
act("ui.extrudeSketch", "Extrude", "extrude").key("E").with(json!({ "sketch": id })).on(has_profiles && !editing),
|
||||
Item::sep(),
|
||||
@@ -540,7 +540,7 @@ fn component_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
fn sketch_entity_items(app: &SolveApp) -> Vec<Item> {
|
||||
use solvecraft_engine::sketch::CurveKind;
|
||||
let Some(sid) = app.session.active_sketch else { return Vec::new() };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = st.sketch(sid) else { return Vec::new() };
|
||||
let mut lines = Vec::new();
|
||||
let mut rounds = Vec::new();
|
||||
@@ -665,7 +665,7 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
app.ui.hidden_sketches.clear();
|
||||
}
|
||||
"ui.isolate" => {
|
||||
app.ui.hidden_bodies = app.session.model.state().bodies.iter().map(|b| b.name.clone()).filter(|b| !bodies.contains(b)).collect();
|
||||
app.ui.hidden_bodies = app.session.world_state().bodies.iter().map(|b| b.name.clone()).filter(|b| !bodies.contains(b)).collect();
|
||||
}
|
||||
"ui.lock" => {
|
||||
let locked = bodies.iter().all(|b| app.ui.locked_bodies.contains(b));
|
||||
@@ -844,7 +844,7 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
}
|
||||
"ui.extrudeSketch" => {
|
||||
if let Some(id) = id_of(&p, "sketch") {
|
||||
let n = app.session.model.state().sketch(id).map_or(0, |s| s.profiles.len());
|
||||
let n = app.session.world_state().sketch(id).map_or(0, |s| s.profiles.len());
|
||||
let items: Vec<Value> = (0..n).map(|i| json!({"type": "profile", "sketch": id, "index": i})).collect();
|
||||
let _ = app.run("select.set", json!({ "items": items }));
|
||||
app.start("Extrude");
|
||||
@@ -949,7 +949,7 @@ pub fn rename_selection(app: &mut SolveApp, at: Pos2) -> bool {
|
||||
|
||||
/// Add the edges that continue the selected edges smoothly to the selection.
|
||||
fn tangent_chain(app: &mut SolveApp) {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let mut out: Vec<Sel> = Vec::new();
|
||||
for s in &app.session.selection {
|
||||
let Sel::Edge { body, index, .. } = s else { continue };
|
||||
|
||||
@@ -90,7 +90,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
let Some(id) = s("command").or(s("id")) else { return err("missing `command`") };
|
||||
let params = p.get("params").cloned().filter(|v| !v.is_null()).unwrap_or(json!({}));
|
||||
let r = app.run(id, params);
|
||||
if r.is_ok() && app.session.active_sketch.is_none() && app.session.model.state().bodies.len() == 1 && app.session.undo.len() <= 2 {
|
||||
if r.is_ok() && app.session.active_sketch.is_none() && app.session.world_state().bodies.len() == 1 && app.session.undo.len() <= 2 {
|
||||
app.fit_view();
|
||||
}
|
||||
wrap(r)
|
||||
@@ -269,7 +269,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
let world = if let Some(w) = p.get("world") {
|
||||
Some(solvecraft_engine::geom::Vec3::new(num(w, 0), num(w, 1), num(w, 2)))
|
||||
} else if let Some(s) = p.get("sketch") {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
app.session
|
||||
.active_sketch
|
||||
.and_then(|id| st.sketch(id))
|
||||
@@ -311,7 +311,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
}
|
||||
"ui.sketchToScreen" => {
|
||||
// Screen points of active-sketch coordinates (for driving sketch interaction).
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let (Some(rect), Some(ss)) = (app.viewport.rect, app.session.active_sketch.and_then(|s| st.sketch(s))) else {
|
||||
return err("no active sketch in a drawn viewport");
|
||||
};
|
||||
|
||||
@@ -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 } => {
|
||||
|
||||
@@ -240,7 +240,7 @@ fn pt(v: Vec3) -> Value {
|
||||
|
||||
/// The occurrence placing a body (0: the root design), when the body is its primary instance.
|
||||
fn occurrence_of_body(s: &Session, body: &str) -> Option<u64> {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let b = st.body(body)?;
|
||||
let comp = s.doc.body_component(&b.name, b.feature);
|
||||
if comp == 0 { Some(0) } else { s.doc.occurrence_of(comp).map(|o| o.id) }
|
||||
@@ -769,7 +769,10 @@ fn next_joint_id(s: &Session) -> u64 {
|
||||
/// after is compared, so the components that move show where they go (`None`: not such
|
||||
/// commands).
|
||||
pub fn world_preview(s: &Session, cmds: &[(String, Value)], colors: Colors) -> Option<Result<Built, String>> {
|
||||
if cmds.is_empty() || !cmds.iter().all(|(c, _)| MOVES.contains(&c.as_str())) {
|
||||
// Any feature, when components are placed away from their frames: the preview must show
|
||||
// the world the viewport shows.
|
||||
let moves = cmds.iter().all(|(c, _)| MOVES.contains(&c.as_str()));
|
||||
if cmds.is_empty() || !(moves || solvecraft_engine::frames::placed(s)) {
|
||||
return None;
|
||||
}
|
||||
let mut scratch = s.scratch();
|
||||
|
||||
@@ -101,7 +101,10 @@ fn joint_and_drive_preview_on_the_placed_model() {
|
||||
assert_eq!(cmds[0].0, "FusionMoveJointsCommand");
|
||||
let b = world_preview(&app.session, &cmds, colors).unwrap().unwrap();
|
||||
assert_eq!(b.replaced, vec!["Body2".to_string()]);
|
||||
assert!(world_preview(&app.session, &[("Extrude".into(), json!({}))], colors).is_none(), "features preview as before");
|
||||
// Features preview as before while nothing is placed away from its frame, and on the
|
||||
// placed model once something is (B moved by its joint).
|
||||
assert!(world_preview(&two_boxes().session, &[("Extrude".into(), json!({}))], colors).is_none());
|
||||
assert!(world_preview(&app.session, &[("Extrude".into(), json!({}))], colors).is_some());
|
||||
}
|
||||
|
||||
/// Edit Joint changes type, alignment and limits of the joint in place.
|
||||
|
||||
@@ -637,7 +637,7 @@ pub fn commands(app: &SolveApp, k: &Mo, inputs: &[SelInput]) -> Result<Vec<(Stri
|
||||
let mut occs: Vec<u64> = Vec::new();
|
||||
for x in inputs.first().map(|i| i.items.as_slice()).unwrap_or(&[]) {
|
||||
if let solvecraft_engine::Sel::Body { name } = x
|
||||
&& let Some(b) = app.session.model.state().body(name)
|
||||
&& let Some(b) = app.session.world_state().body(name)
|
||||
{
|
||||
let comp = app.session.doc.body_component(&b.name, b.feature);
|
||||
if let Some(o) = app.session.doc.occurrence_of(comp).map(|o| o.id)
|
||||
|
||||
@@ -434,7 +434,7 @@ pub fn commands(app: &SolveApp, k: &Sm, inputs: &[SelInput], extra: &Map<String,
|
||||
2 => {
|
||||
let ids: Vec<String> =
|
||||
items(inputs, 0).iter().filter_map(|x| if let Sel::SketchCurve { id } = x { Some(id.clone()) } else { None }).collect();
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let sketch = st
|
||||
.sketches
|
||||
.iter()
|
||||
@@ -475,7 +475,7 @@ pub fn commands(app: &SolveApp, k: &Sm, inputs: &[SelInput], extra: &Map<String,
|
||||
let mut p = json!({"angle": angle, "position": FOLD_POSITIONS.get(*position).copied().unwrap_or("centerline"), "flip": flip});
|
||||
match items(inputs, 0).first() {
|
||||
Some(Sel::SketchCurve { id }) => {
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let sketch = st
|
||||
.sketches
|
||||
.iter()
|
||||
@@ -543,7 +543,7 @@ pub fn commands(app: &SolveApp, k: &Sm, inputs: &[SelInput], extra: &Map<String,
|
||||
/// The dialog editing a sheet metal feature, filled from it.
|
||||
pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelInput>, Map<String, Value>)> {
|
||||
let s = &app.session;
|
||||
let st = s.model.state();
|
||||
let st = s.world_state();
|
||||
let rule_index = |r: &Option<String>| r.as_ref().and_then(|n| rule_list(app).iter().position(|x| &x.name == n)).map_or(0, |i| i + 1);
|
||||
let mut extra = Map::new();
|
||||
let (k, items): (Sm, Vec<Sel>) = match kind {
|
||||
@@ -654,7 +654,7 @@ pub fn arrow(app: &SolveApp, d: &Dialog) -> Option<Vec3> {
|
||||
_ => return None,
|
||||
};
|
||||
let Sel::Edge { body, index, point } = d.inputs.first()?.items.first()? else { return None };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let m = st.body(body)?.mesh();
|
||||
let faces = m.edge_faces.get(*index)?;
|
||||
let area = |f: u32| -> f64 {
|
||||
|
||||
@@ -174,7 +174,7 @@ pub fn label(k: &ConstraintKind, expr: Option<&str>, driven: bool) -> String {
|
||||
/// Draw the active sketch's dimensions and remember where they are for picking.
|
||||
pub fn show(app: &mut SolveApp, ui: &egui::Ui, painter: &egui::Painter, proj: &Proj) {
|
||||
let mut drawn = Vec::new();
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else {
|
||||
DRAWN.with(|d| d.borrow_mut().clear());
|
||||
EDIT.with(|e| *e.borrow_mut() = None);
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn diagnostics(app: &SolveApp) -> String {
|
||||
if app.custom_titlebar { "custom title bar" } else { "system title bar" },
|
||||
app.session.doc.name,
|
||||
app.session.doc.features.len(),
|
||||
app.session.model.state().bodies.len(),
|
||||
app.session.world_state().bodies.len(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ pub fn show(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
return;
|
||||
}
|
||||
let Some(i) = current() else { return };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return };
|
||||
let to = |p: Vec2| proj.to_screen(ss.plane.to_world(p));
|
||||
let tk = Tokens::get();
|
||||
|
||||
@@ -32,7 +32,7 @@ const ARROW: f32 = 60.0;
|
||||
|
||||
/// Is the sketch being edited a 3D sketch?
|
||||
pub fn active(app: &SolveApp) -> bool {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
app.session.active_sketch.and_then(|s| st.sketch(s)).is_some_and(|ss| ss.sketch.view.three_d)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ fn takes(cmd: &str) -> bool {
|
||||
|
||||
/// Points drawn in 3D: (wire id, fit index, point).
|
||||
fn drawn_points(app: &SolveApp) -> Vec<(String, usize, Vec3)> {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return Vec::new() };
|
||||
ss.sketch.wires.iter().flat_map(|w| w.fit.iter().enumerate().map(|(i, p)| (w.id.clone(), i, *p))).collect()
|
||||
}
|
||||
@@ -66,7 +66,7 @@ pub fn point_at(app: &SolveApp, proj: &Proj, pos: Pos2) -> Option<(Vec3, &'stati
|
||||
return Some((p, "Pt"));
|
||||
}
|
||||
}
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let ss = app.session.active_sketch.and_then(|s| st.sketch(s))?;
|
||||
let (o, d) = proj.ray(pos);
|
||||
for h in pick(app, proj, pos) {
|
||||
@@ -92,7 +92,7 @@ pub fn point_at(app: &SolveApp, proj: &Proj, pos: Pos2) -> Option<(Vec3, &'stati
|
||||
|
||||
/// The sketch-plane coordinates of `p` when it lies on the plane.
|
||||
fn on_plane(app: &SolveApp, p: Vec3) -> Option<Vec2> {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let ss = app.session.active_sketch.and_then(|s| st.sketch(s))?;
|
||||
let q = ss.plane.to_local(p);
|
||||
(ss.plane.to_world(q).dist(p) < 1e-6).then_some(q)
|
||||
@@ -305,7 +305,7 @@ fn target(app: &SolveApp, proj: &Proj, pos: Pos2, axis: Option<Vec3>, p: Vec3) -
|
||||
Some(p + u * ((b * d.dot(w0) - c * u.dot(w0)) / den))
|
||||
}
|
||||
None => {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let n = app.session.active_sketch.and_then(|s| st.sketch(s))?.plane.normal();
|
||||
let den = d.dot(n);
|
||||
(den.abs() > 1e-12).then(|| o + d * ((p - o).dot(n) / den))
|
||||
|
||||
@@ -233,7 +233,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui, proj: &Proj) {
|
||||
fn boxes(app: &SolveApp, ui: &mut egui::Ui, proj: &Proj, t: &mut Tool) -> Option<Vec2> {
|
||||
let tk = Tokens::get();
|
||||
let sid = app.session.active_sketch?;
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let plane = st.sketch(sid)?.plane;
|
||||
let h = t.hover.as_ref().map(|x| x.0)?;
|
||||
let e = effective(app, t, h);
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn set_linetype(app: &mut SolveApp, kind: u8) {
|
||||
return;
|
||||
}
|
||||
let lines: Vec<String> = {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return };
|
||||
curves
|
||||
.iter()
|
||||
@@ -96,7 +96,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
return;
|
||||
}
|
||||
let (opts, dof_line, ok) = {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return };
|
||||
(ss.sketch.view.clone(), dof_text(ss.report.dof, ss.report.ok()), ss.report.ok() && ss.report.dof == 0)
|
||||
};
|
||||
|
||||
@@ -476,14 +476,14 @@ fn arg(p: &(Vec2, Option<String>)) -> Value {
|
||||
|
||||
/// Length of a curve (for default sizes).
|
||||
fn curve_len(app: &SolveApp, id: &str) -> f64 {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = active_sketch(app).and_then(|s| st.sketch(s)) else { return 10.0 };
|
||||
ss.sketch.curve_index(id).map(|c| ss.sketch.polyline(c).windows(2).map(|w| w[0].dist(w[1])).sum::<f64>()).unwrap_or(10.0)
|
||||
}
|
||||
|
||||
/// Lines meeting at a sketch point.
|
||||
fn corner_len(app: &SolveApp, point: &str) -> f64 {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = active_sketch(app).and_then(|s| st.sketch(s)) else { return 10.0 };
|
||||
let sk = &ss.sketch;
|
||||
let Some(q) = sk.resolve_point(point) else { return 10.0 };
|
||||
@@ -571,7 +571,7 @@ pub fn on_click(app: &mut SolveApp, tool: &mut Tool, proj: &Proj, pos: Pos2) {
|
||||
});
|
||||
// Empty space while sketching: the sketch plane.
|
||||
let target = target.or_else(|| {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let ss = st.sketch(app.session.active_sketch?)?;
|
||||
let (o, d) = proj.ray(pos);
|
||||
let w = ss.plane.intersect_ray(o, d)?;
|
||||
@@ -850,7 +850,7 @@ fn offset_side(app: &mut SolveApp, tool: &mut Tool, p: Vec2) -> bool {
|
||||
if tool.cmd != "Offset" || tool.picks.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let d = active_sketch(app)
|
||||
.and_then(|s| st.sketch(s))
|
||||
.and_then(|ss| ss.sketch.curve_index(&tool.picks[0]).and_then(|c| ss.sketch.shape(c)))
|
||||
@@ -866,7 +866,7 @@ fn offset_side(app: &mut SolveApp, tool: &mut Tool, p: Vec2) -> bool {
|
||||
/// Rubber band for point tools and pick markers.
|
||||
pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
let Some(sid) = active_sketch(app) else { return };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = st.sketch(sid) else { return };
|
||||
let to = |p: Vec2| proj.to_screen(ss.plane.to_world(p));
|
||||
let tk = crate::theme::Tokens::get();
|
||||
@@ -898,7 +898,7 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
/// (conflicting ones red).
|
||||
fn glyphs(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let Some(sid) = active_sketch(app) else { return };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = st.sketch(sid) else { return };
|
||||
if ss.sketch.view.hide_constraints {
|
||||
GLYPHS.with(|g| g.borrow_mut().clear());
|
||||
@@ -995,7 +995,7 @@ fn snap_hint(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
return;
|
||||
}
|
||||
let Some((label, p)) = SNAP.with(|s| *s.borrow()) else { return };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = active_sketch(app).and_then(|s| st.sketch(s)) else { return };
|
||||
let Some(sp) = proj.to_screen(ss.plane.to_world(p)) else { return };
|
||||
let tk = crate::theme::Tokens::get();
|
||||
|
||||
@@ -112,7 +112,7 @@ pub fn timeline(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
.map(|f| (f.id, f.name.clone(), icon_of(&f.kind), f.suppressed, f.kind.type_name(), f.kind.clone()))
|
||||
.collect();
|
||||
// The feature whose body is under the cursor in the viewport.
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let from_view = match &app.viewport.hover {
|
||||
Some(crate::viewport::Hit::Face { body, .. } | crate::viewport::Hit::Edge { body, .. } | crate::viewport::Hit::Vertex { body, .. }) => {
|
||||
st.body(body).map(|b| b.feature)
|
||||
@@ -298,7 +298,7 @@ pub fn timeline(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
|
||||
/// Select what a feature made, so the browser shows it.
|
||||
fn find_in_browser(app: &mut SolveApp, id: u64) {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let bodies: Vec<serde_json::Value> = st.bodies.iter().filter(|b| b.feature == id).map(|b| json!({"type": "body", "name": b.name})).collect();
|
||||
let items = if bodies.is_empty() { vec![json!({"type": "feature", "id": id})] } else { bodies };
|
||||
let _ = app.run("select.set", json!({ "items": items }));
|
||||
|
||||
@@ -149,7 +149,7 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2) {
|
||||
});
|
||||
if let Some((id, is_point)) = hit {
|
||||
tool.picks.push(id.clone());
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let is_line = app
|
||||
.session
|
||||
.active_sketch
|
||||
@@ -252,7 +252,7 @@ fn run_shape(app: &mut SolveApp, t: &Tool) -> Vec<String> {
|
||||
fn tie_snaps(app: &mut SolveApp, t: &Tool, curves: &[String]) {
|
||||
let mut ties = Vec::new();
|
||||
{
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)) else { return };
|
||||
let sk = &ss.sketch;
|
||||
let new_pts: Vec<usize> =
|
||||
@@ -319,7 +319,7 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
return crate::sketch_tools::preview(app, t, painter, proj);
|
||||
}
|
||||
let Some(sid) = app.session.active_sketch else { return };
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = st.sketch(sid) else { return };
|
||||
let to = |p: Vec2| proj.to_screen(ss.plane.to_world(p));
|
||||
let tk = crate::theme::Tokens::get();
|
||||
|
||||
@@ -147,7 +147,7 @@ pub fn projection(app: &SolveApp, rect: Rect) -> Proj {
|
||||
|
||||
/// Sketches drawn in the viewport: the active one, and finished ones not yet used by a feature.
|
||||
fn visible_sketches(app: &SolveApp) -> Vec<u64> {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
st.sketches
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
@@ -696,7 +696,7 @@ pub fn pick(app: &SolveApp, proj: &Proj, pos: Pos2) -> Vec<Hit> {
|
||||
/// The section cut shown now: the open Section Analysis dialog's plane, else the session's.
|
||||
/// The active sketch's display options (defaults when not sketching).
|
||||
pub fn active_view(app: &SolveApp) -> solvecraft_engine::sketch::SketchView {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
app.session.active_sketch.and_then(|s| st.sketch(s)).map(|ss| ss.sketch.view.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -710,7 +710,7 @@ pub fn section_plane(app: &SolveApp) -> Option<(Vec3, Vec3)> {
|
||||
}
|
||||
// Slice (Sketch Palette): cut the model at the plane of the sketch being edited.
|
||||
if app.session.section.is_none() {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
if let Some(ss) = app.session.active_sketch.and_then(|s| st.sketch(s)).filter(|ss| ss.sketch.view.slice) {
|
||||
return Some((ss.plane.origin, ss.plane.normal()));
|
||||
}
|
||||
@@ -873,7 +873,7 @@ fn edge_lines(sc: &mut GpuScene, e: &[Vec3], core: Color32, halo: Color32, w: f3
|
||||
}
|
||||
|
||||
fn profile_fill(sc: &mut GpuScene, app: &SolveApp, sketch: u64, index: usize, fill: Color32, edge: Color32) {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = st.sketch(sketch) else { return };
|
||||
let Some(p) = ss.profiles.get(index) else { return };
|
||||
// Lift toward the eye so a profile on a face shows over it.
|
||||
@@ -1100,7 +1100,7 @@ fn body_color(doc: &solvecraft_engine::doc::Document, b: &solvecraft_engine::doc
|
||||
|
||||
/// A body's display colour (else the theme's) as RGB.
|
||||
fn body_rgb(app: &SolveApp, name: &str) -> (u8, u8, u8) {
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let c = st.body(name).and_then(|b| body_color(&app.session.doc, b)).unwrap_or(Tokens::get().body);
|
||||
(c.r(), c.g(), c.b())
|
||||
}
|
||||
@@ -1130,7 +1130,7 @@ fn seg_dist(p: Pos2, a: Pos2, b: Pos2) -> f32 {
|
||||
/// id of the sketch point it snapped to.
|
||||
pub fn sketch_point_at(app: &SolveApp, proj: &Proj, pos: Pos2) -> Option<(Vec2, Option<String>)> {
|
||||
let sid = app.session.active_sketch?;
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let ss = st.sketch(sid)?;
|
||||
for h in pick(app, proj, pos) {
|
||||
if let Hit::SketchPoint { id, at, .. } = h {
|
||||
@@ -1362,7 +1362,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
&& app.tool.is_none()
|
||||
&& app.dialog.is_none()
|
||||
&& let Some(Hit::Edge { body, index, .. }) = app.viewport.hover.clone()
|
||||
&& let Some(b) = app.session.model.state().body(&body).cloned()
|
||||
&& let Some(b) = app.session.world_state().body(&body).cloned()
|
||||
{
|
||||
let m = b.mesh();
|
||||
let items: Vec<Sel> = m
|
||||
@@ -1383,7 +1383,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// Dragging a sketch point of the active sketch moves it; the solver keeps the constraints.
|
||||
if app.tool.is_none() && app.dialog.is_none() && app.viewport.nav.is_none() && app.session.active_sketch.is_some() {
|
||||
let on_plane = |app: &SolveApp, p: Pos2| {
|
||||
let ss = app.session.active_sketch.and_then(|sid| app.session.model.state().sketch(sid).cloned())?;
|
||||
let ss = app.session.active_sketch.and_then(|sid| app.session.world_state().sketch(sid).cloned())?;
|
||||
let (o, d) = proj.ray(p);
|
||||
ss.plane.intersect_ray(o, d).map(|w| ss.plane.to_local(w))
|
||||
};
|
||||
@@ -1652,7 +1652,7 @@ fn points_2d(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
/// Hover feedback for the active sketch's points and curves (drawn on top of everything).
|
||||
fn hover_highlight(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let col = Tokens::get().hover_profile_edge;
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
match &app.viewport.hover {
|
||||
Some(Hit::SketchCurve { sketch, id, .. }) => {
|
||||
if let Some(ss) = st.sketch(*sketch)
|
||||
@@ -1678,7 +1678,7 @@ fn hover_highlight(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
/// Sketch points, dimension labels and constraint glyphs of the active sketch.
|
||||
fn overlays(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let t = Tokens::get();
|
||||
let st = app.session.model.state();
|
||||
let st = app.session.world_state();
|
||||
let Some(sid) = app.session.active_sketch else { return };
|
||||
let Some(ss) = st.sketch(sid) else { return };
|
||||
let sk = &ss.sketch;
|
||||
|
||||
Reference in new issue
Block a user