fix: sketches on planes built from the model can project, intersect, include and hold text (#459)
Project, Intersect, Include 3D Geometry and Text resolved the active sketch's plane without the model, so on a construction plane that is built from geometry (perpendicular to a plane through a sketch line, through three points, midway between two faces, tangent, along a path) they stopped with "this plane is built from the model: evaluate first". Evaluation resolves the same plane against the model before the sketch, in its component's frame; the sketch commands now do the same, in one place, for adding a link, refreshing the links on an edit, snapping a point onto an edge and the internal face profiles. Tests: a sketch on a plane through a sloped sketch line projects a point and a line of that sketch, follows a parameter that tilts the plane (also after reopen and after another edit), holds text, intersects a body and includes an edge; the same on a three-point plane and a midplane. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1ae6e9beb4
commit
9ab25cfd4a
3 files changed
+183
-23
No files matched your search
@@ -118,15 +118,36 @@ fn source(s: &Session, v: &Value, cmd: &str) -> Result<LinkSource> {
|
||||
Err(bad(cmd, format!("cannot project {v}")))
|
||||
}
|
||||
|
||||
/// The plane of a sketch feature.
|
||||
fn sketch_plane(doc: &Document, id: u64) -> Result<Plane> {
|
||||
/// The plane of a sketch feature, resolved against `st`: the model before the sketch as its
|
||||
/// component sees it, which is what evaluation resolves it against. A construction plane built
|
||||
/// from the model (through a sketch line, three points, between two faces…) exists only there.
|
||||
fn sketch_plane(doc: &Document, id: u64, st: &solidcraft_doc::ModelState) -> Result<Plane> {
|
||||
let (vals, _) = doc.param_values();
|
||||
match doc.feature(id).map(|f| &f.kind) {
|
||||
Some(FeatureKind::Sketch { plane, .. }) => Ok(doc.resolve_plane(&vals, plane, 0)?),
|
||||
Some(FeatureKind::Sketch { plane, .. }) => Ok(doc.resolve_plane_in(&vals, plane, 0, Some(st))?),
|
||||
_ => Err(EngineError::Other(format!("feature {id} is not a sketch"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// The model before sketch `id` in the frame its links and its plane are resolved in: the
|
||||
/// attached face's frame for a sketch on a face, else its component's.
|
||||
fn state_for_sketch<'a>(
|
||||
s: &Session,
|
||||
doc: &Document,
|
||||
id: u64,
|
||||
st: &'a solidcraft_doc::ModelState,
|
||||
) -> std::result::Result<std::borrow::Cow<'a, solidcraft_doc::ModelState>, solidcraft_doc::DocError> {
|
||||
let comp = doc.feature(id).map(|f| f.component).unwrap_or(0);
|
||||
let attachment = doc.feature(id).and_then(|feature| match &feature.kind {
|
||||
solidcraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane),
|
||||
_ => None,
|
||||
});
|
||||
match attachment {
|
||||
Some(anchor) => solidcraft_doc::state_in_face_anchor_frame(doc, st, anchor, comp),
|
||||
None => Ok(solidcraft_doc::state_in_frame_at(doc, st, comp, owner_frame(s, doc, comp))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep candidate feature edits while overlaying uncommitted occurrence placements.
|
||||
fn placed_candidate<'a>(s: &Session, doc: &'a Document) -> std::borrow::Cow<'a, Document> {
|
||||
if s.pending_moves.is_empty() {
|
||||
@@ -152,21 +173,16 @@ pub(super) fn refresh(s: &Session, doc: &Document, id: u64, sk: &mut Sketch) ->
|
||||
return Vec::new();
|
||||
}
|
||||
let doc = placed_candidate(s, doc);
|
||||
let Ok(plane) = sketch_plane(&doc, id) else { return Vec::new() };
|
||||
let (vals, _) = doc.param_values();
|
||||
let st = s.model.state_before(id);
|
||||
let comp = doc.feature(id).map(|f| f.component).unwrap_or(0);
|
||||
let attachment = doc.feature(id).and_then(|feature| match &feature.kind {
|
||||
solidcraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane),
|
||||
_ => None,
|
||||
});
|
||||
let st = match attachment {
|
||||
Some(anchor) => solidcraft_doc::state_in_face_anchor_frame(&doc, &st, anchor, comp),
|
||||
None => Ok(solidcraft_doc::state_in_frame_at(&doc, &st, comp, owner_frame(s, &doc, comp))),
|
||||
};
|
||||
let Ok(st) = st else {
|
||||
let Ok(st) = state_for_sketch(s, &doc, id, &st) else {
|
||||
return vec!["lost face attachment; reattach explicitly".into()];
|
||||
};
|
||||
let Ok(plane) = sketch_plane(&doc, id, &st) else { return Vec::new() };
|
||||
match attachment {
|
||||
Some(anchor) => solidcraft_doc::project::refresh_face_anchor_links(&doc, &vals, &st, &plane, sk, anchor),
|
||||
None => solidcraft_doc::project::refresh_links(&doc, &vals, &st, &plane, sk),
|
||||
@@ -176,20 +192,11 @@ pub(super) fn refresh(s: &Session, doc: &Document, id: u64, sk: &mut Sketch) ->
|
||||
/// Add a link for `src` to sketch `id` in `doc` (not stored in the session). Returns the link id.
|
||||
pub(super) fn add_link(s: &Session, doc: &Document, id: u64, sk: &mut Sketch, kind: LinkKind, src: LinkSource) -> Result<String> {
|
||||
let doc = placed_candidate(s, doc);
|
||||
let plane = sketch_plane(&doc, id)?;
|
||||
let (vals, _) = doc.param_values();
|
||||
let st = s.model.state_before(id);
|
||||
// The model as the sketch's component sees it.
|
||||
let comp = doc.feature(id).map(|f| f.component).unwrap_or(0);
|
||||
let attachment = doc.feature(id).and_then(|feature| match &feature.kind {
|
||||
solidcraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane),
|
||||
_ => None,
|
||||
});
|
||||
let st = match attachment {
|
||||
Some(anchor) => solidcraft_doc::state_in_face_anchor_frame(&doc, &st, anchor, comp),
|
||||
None => Ok(solidcraft_doc::state_in_frame_at(&doc, &st, comp, owner_frame(s, &doc, comp))),
|
||||
};
|
||||
let st = st?;
|
||||
let st = state_for_sketch(s, &doc, id, &st)?;
|
||||
let plane = sketch_plane(&doc, id, &st)?;
|
||||
let r = resolve(&doc, &vals, &st, &plane, kind, &src)?;
|
||||
let name = if let LinkSource::Face { body, at } = &r.source {
|
||||
st.body(body).and_then(|body| {
|
||||
@@ -454,7 +461,8 @@ pub(super) fn snap_points(s: &mut Session, p: &Value, cmd: &str) -> Result<Value
|
||||
sk.points.get(pi).map(|q| q.id.clone()).unwrap_or_default()
|
||||
} else if let Some(at) = edge {
|
||||
let l = add_link(s, d, id, sk, LinkKind::Project, LinkSource::Edge { body, at }).map_err(|e| bad(cmd, format!("edge: {e}")))?;
|
||||
let plane = sketch_plane(d, id)?;
|
||||
let before = s.model.state_before(id);
|
||||
let plane = sketch_plane(d, id, &*state_for_sketch(s, d, id, &before)?)?;
|
||||
let q = plane.to_local(at);
|
||||
let ci = sk.link_curves(&l).first().copied().ok_or_else(|| bad(cmd, "the edge projects to a point"))?;
|
||||
let pi = sk.add_point(q, None)?;
|
||||
@@ -490,10 +498,10 @@ pub(super) fn snap_points(s: &mut Session, p: &Value, cmd: &str) -> Result<Value
|
||||
/// edits, but cannot provide the construction certificate required by public attachments.
|
||||
pub(super) fn project_internal_face(s: &mut Session, id: u64, body: &str, index: usize) -> Result<String> {
|
||||
let mut doc = (*s.doc).clone();
|
||||
let plane = sketch_plane(&doc, id)?;
|
||||
let raw = s.model.state_before(id);
|
||||
let component = doc.feature(id).map(|feature| feature.component).unwrap_or(0);
|
||||
let state = solidcraft_doc::state_in_frame_at(&doc, &raw, component, owner_frame(s, &doc, component));
|
||||
let plane = sketch_plane(&doc, id, &state)?;
|
||||
let resolved = solidcraft_doc::project::resolve_face_indices(&state, &plane, LinkKind::Project, body, &[index])?;
|
||||
let face_name = state.body(body).and_then(|body| solidcraft_doc::naming::face_names(body).get(index).cloned());
|
||||
let mut sk = doc.sketch(id)?.clone();
|
||||
@@ -538,3 +546,7 @@ pub(super) fn auto_project_face(s: &mut Session, id: u64, _at: Vec3) -> Result<O
|
||||
#[cfg(test)]
|
||||
#[path = "sketch_project_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sketch_model_plane_tests.rs"]
|
||||
mod model_plane_tests;
|
||||
Reference in new issue
Block a user