Sketch on every side wall of one sketch extruded up and down (#957)

* Name a side drawn on several sketch curves by all of them

An extrude named each side wall after the one sketch curve its profile
segment lies on. A wall along two coincident lines (squares drawn edge to
edge) or along collinear lines merged into one profile segment had no
single curve, so it was named from geometry and could not carry a sketch
attachment. One sketch extruded up and down and joined (the logo) refused
a sketch on every one of its side walls.

Such a side is now named by every curve it lies on, in sketch order: the
first is its name, the others aliases. The set is fixed by the sketch, so
the name is exact. The refusal message no longer blames merged faces.

Adds the reported logo design and a synthetic up-and-down join to the
action-matrix designs, and a regression test that sketches and extrudes on
every flat face, fresh and after save and reopen.

Fixes #954

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Action matrix: record the up-and-down join designs

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:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-14 01:00:04 -04:00
1 parent 9f76cca8b4
commit ea23401710
9 files changed
+305 -13

No files matched your search

+40
View File
@@ -457,6 +457,46 @@ impl<'a> ExactSketchCurves<'a> {
}
}
impl ExactSketchCurves<'_> {
/// The sketch curves a generated segment is drawn on: [`find_along`](Self::find_along)'s one
/// curve, else every drawn curve that contains it or runs along and overlaps it (projections
/// only where no drawn curve does), in the sketch's order. A segment two coincident curves
/// were drawn over (four squares drawn edge to edge), or one a profile merged from collinear
/// curves (the outline of several adjacent profiles extruded together), is drawn on all of
/// them: the set is exact and fixed by the sketch, so its face is named by it (the first its
/// name, the others aliases) rather than not at all. Empty when it lies on none.
pub(crate) fn find_all_along(&self, generated: &Seg2) -> Vec<String> {
if let Some(id) = self.find_along(generated) {
return vec![id];
}
let runs_along = |original: &Seg2| {
if exact_segment_contains(original, generated) {
return true;
}
let (Seg2::Line { a, b }, Seg2::Line { a: c, b: d }) = (*original, *generated) else { return false };
let direction = b - a;
let scale = direction.len().max((d - c).len()).max(1.);
if direction.len() <= 1e-12
|| direction.cross(c - a).abs() > 1e-10 * scale * scale
|| direction.cross(d - a).abs() > 1e-10 * scale * scale
{
return false;
}
let (tc, td) = ((c - a).dot(direction) / direction.len2(), (d - a).dot(direction) / direction.len2());
tc.min(td).max(0.) < tc.max(td).min(1.) - 1e-9
};
let pick = |linked: bool| -> Vec<String> {
self.segments
.iter()
.filter(|(_, l, originals)| *l == linked && originals.iter().any(runs_along))
.map(|(id, _, _)| (*id).to_owned())
.collect()
};
let drawn = pick(false);
if drawn.is_empty() { pick(true) } else { drawn }
}
}
fn exact_segment_contains(original: &Seg2, generated: &Seg2) -> bool {
match (original, generated) {
(Seg2::Line { a, b }, Seg2::Line { a: c, b: d }) => {
+16 -9
View File
@@ -207,23 +207,30 @@ impl<'a> ProfileRoles<'a> {
let segments: Vec<&solidcraft_geom::Seg2> =
self.region.map(|r| std::iter::once(&r.outer).chain(&r.holes).flat_map(|l| &l.segs).collect()).unwrap_or_default();
let curves = self.sketch.map(crate::config::ExactSketchCurves::new);
let curve = |segment: usize| -> Option<String> { curves.as_ref()?.find_along(segments.get(segment)?).map(|id| format!("side:{id}")) };
// Every curve the segment is drawn on (one, or several coincident or collinear ones).
let curve = |segment: usize| -> Vec<String> {
let (Some(curves), Some(segment)) = (curves.as_ref(), segments.get(segment)) else { return Vec::new() };
curves.find_all_along(segment).into_iter().map(|id| format!("side:{id}")).collect()
};
let roles = history
.faces
.iter()
.map(|face| {
let mut roles: Vec<String> = Vec::new();
for origin in &face.origins {
let role = match *origin {
let found = match *origin {
FaceOrigin::Profile { segment } => curve(segment),
FaceOrigin::Cap { end } => self.caps.get(usize::from(end)).cloned().flatten(),
_ => None,
FaceOrigin::Cap { end } => self.caps.get(usize::from(end)).cloned().flatten().into_iter().collect(),
_ => Vec::new(),
};
match role {
Some(role) if !roles.contains(&role) => roles.push(role),
Some(_) => {}
// One unknown origin leaves the face unknown: never a partial guess.
None => return Vec::new(),
// One unknown origin leaves the face unknown: never a partial guess.
if found.is_empty() {
return Vec::new();
}
for role in found {
if !roles.contains(&role) {
roles.push(role);
}
}
}
roles
+2 -1
View File
@@ -192,7 +192,8 @@ fn capture_impl(s: &Session, value: &Value, cmd: &str, internal: bool) -> Result
{
return Err(bad(
cmd,
"this face can't keep an attachment: it was merged from more than one feature's faces or reshaped by an edit. \
"this face can't keep an attachment: its history can't tell it apart from other faces (an edit reshaped it, \
or more than one face could claim its name). \
Pick another face, or use a fixed plane here (fixed:true), which won't follow later changes to the face",
));
}
@@ -582,3 +582,98 @@ fn offset_rectangle_pocket_has_one_logical_outer_wall_and_closed_face_projection
assert!((mesh.measure().volume - expected).abs() < 1e-6, "actual {}, expected {expected}", mesh.measure().volume);
}
}
/// Every flat face of every body: (body, face index, a point well inside it, its normal). The
/// middle of the face's largest triangle, so an L-shaped face is picked on itself.
fn flat_faces(s: &Session) -> Vec<(String, usize, Vec3, Vec3)> {
let world = s.world_state();
let mut out = Vec::new();
for body in &world.bodies {
let mesh = body.mesh();
let mut inside: std::collections::BTreeMap<usize, (Vec3, f64)> = std::collections::BTreeMap::new();
for (triangle, face) in mesh.triangles.iter().zip(&mesh.tri_face) {
if let Some([a, b, c]) = mesh.tri(triangle) {
let area = (b - a).cross(c - a).len();
let entry = inside.entry(*face as usize).or_insert(((a + b + c) * (1. / 3.), -1.));
if area > entry.1 {
*entry = ((a + b + c) * (1. / 3.), area);
}
}
}
for (index, (point, _)) in inside {
if let Some(plane) = body.body.planar_face(index) {
out.push((body.name.clone(), index, point, plane.normal()));
}
}
}
out
}
fn timeline_errors(s: &Session) -> Vec<String> {
s.model.results.iter().filter_map(|r| r.error.as_ref().map(|e| format!("{}: {e}", r.name))).collect()
}
fn save_and_reopen(s: &mut Session, dir: &std::path::Path, name: &str) -> Session {
let path = dir.join(name);
run(s, "file.save_as", json!({"path": path.to_string_lossy()}));
let mut reopened = Session::default();
run(&mut reopened, "doc.open", json!({"path": path.to_string_lossy()}));
reopened
}
/// A sketch on every flat face of `s`'s bodies (as the desktop picks it: body, index, a point on
/// it) stays attached to that face, and a square drawn on it extrudes out of the face, fresh and
/// after the result is saved and opened again.
fn sketch_and_extrude_on_every_flat_face(s: &Session, dir: &std::path::Path, label: &str) -> usize {
use solidcraft_doc::FaceAttachmentStatus;
let faces = flat_faces(s);
let before = timeline_errors(s);
for (body, index, point, normal) in &faces {
let what = format!("{label}: {body} face {index} at {point:?}");
let mut t = s.scratch();
let pick = json!({"body": body, "index": index, "face": [point.x, point.y, point.z]});
let sketch =
t.execute("sketch.create", &json!({"plane": pick, "project_edges": false})).unwrap_or_else(|error| panic!("{what}: {error}"))["sketch"]
.as_u64()
.unwrap();
let plane = t.model.state().sketch(sketch).unwrap().plane;
assert!(plane.normal().dot(*normal) > 1. - 1e-9, "{what}: the sketch faces out of the face");
let local = [(*point - plane.origin).dot(plane.x), (*point - plane.origin).dot(plane.y)];
run(&mut t, "sketch.rectangle.two_point", json!({"p0": [local[0] - 0.5, local[1] - 0.5], "p1": [local[0] + 0.5, local[1] + 0.5]}));
run(&mut t, "sketch.finish", json!({}));
t.execute("solid.extrude", &json!({"sketch": sketch, "distance": 1, "operation": "join"})).unwrap_or_else(|error| panic!("{what}: {error}"));
let check = |t: &Session, when: &str| {
assert!(
matches!(t.placed_document().sketch_face_attachment(sketch, &t.model), FaceAttachmentStatus::Healthy { .. }),
"{what} ({when}): the sketch must stay attached to its face"
);
assert_eq!(timeline_errors(t), before, "{what} ({when}): no new timeline errors");
};
check(&t, "fresh");
let reopened = save_and_reopen(&mut t, dir, &format!("{label}-{index}.solidcraft"));
check(&reopened, "after save and reopen");
}
faces.len()
}
/// The logo: one sketch extruded up and the same sketch extruded down, joined. Its side walls
/// are merged from both extrudes' sides, or run along two coincident sketch lines (squares drawn
/// edge to edge); each still takes a sketch that follows it, as the tops and bottoms do.
#[test]
fn every_flat_face_of_one_sketch_extruded_up_and_down_takes_an_attached_sketch() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/action_matrix/designs");
let dir = std::env::temp_dir().join(format!("solidcraft-up-down-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let script: Value = serde_json::from_str(&std::fs::read_to_string(root.join("up-down-join.json")).unwrap()).unwrap();
let mut s = Session::default();
s.run_script(&script).unwrap();
assert_eq!(s.world_state().bodies.len(), 1, "the two extrudes join into one body");
assert_eq!(sketch_and_extrude_on_every_flat_face(&s, &dir, "synthetic"), 12, "8 side walls, top, bottom, 2 at the sketch plane");
let reopened = save_and_reopen(&mut s, &dir, "synthetic.solidcraft");
assert_eq!(sketch_and_extrude_on_every_flat_face(&reopened, &dir, "synthetic-reopened"), 12);
// The logo design as it was reported (format 17, with a removed shelled body before it).
let mut logo = Session::default();
run(&mut logo, "doc.open", json!({"path": root.join("logo-up-down-join.solidcraft").to_string_lossy()}));
assert_eq!(sketch_and_extrude_on_every_flat_face(&logo, &dir, "logo"), 12);
let _ = std::fs::remove_dir_all(&dir);
}
+5 -2
View File
@@ -44,11 +44,14 @@ lists which still fail; skip `action_matrix_` to leave out the matrix itself).
`demo/pi-case`: the Raspberry Pi 4 case of the 0.6 README demo, built through the desktop UI
and saved by `examples/demo/enclosure_ui_mcp.py --story pi-0.6 --save`;
`demo/pi-case-0.8`: the 0.8 README demo's case (`--story pi`), with a rounded rim, engraved
text and a PETG material.
text and a PETG material;
`demo/logo-up-down-join`: the SolidCraft logo as it was reported in #954, one sketch extruded
up and down and joined.
- `typical/*`: [`tests/action_matrix/designs`](../tests/action_matrix/designs/), parts made the
ordinary way: a stacked extrude, a filleted and shelled box, a revolved knob, a plate with a
counterbored hole patterned 3 × 2 and a chamfered outline, a sheet metal L bracket with holes, a
join and a cut of three bodies, a loft on a plate, and a sweep.
join and a cut of three bodies, a loft on a plate, a sweep, and one sketch of squares drawn edge
to edge extruded up and down and joined (`up-down-join`).
## The actions
+6 -1
View File
@@ -36,7 +36,12 @@ keep the history of the body they go on and name their own faces by the part the
(`F4:post:top`, `F5:rest:side:0`, `F6:arm:top`), so those take attachments too. A side an extrude grew to
overlap a flush body before joining it (its neighbours lengthened past the sketch's corners) is
still named by the sketch line it runs along, so a wall a Join merged from two extrudes' walls
attaches by the first one's name and answers to the other's.
attaches by the first one's name and answers to the other's. A side drawn on several sketch
curves at once (two squares drawn edge to edge leave two coincident lines along the shared edge;
several adjacent profiles extruded together leave one wall along collinear lines) is named by
every curve it lies on, the first in the sketch's order its name and the others aliases: the set
is fixed by the sketch, so one sketch extruded up and down and joined (a logo) takes an attached
sketch on every wall, not only on its tops and bottoms.
Faces whose history is not exact (faces a feature rebuilt without reporting its history),
ambiguous sources, and the pieces of a face split on purpose with Split Face (each piece is its
+130
View File
@@ -1662,6 +1662,71 @@
"Enclosure/v[F4:inner:F2:side:l3 & F4:inner:F3:blend:F2:side:l2|F2:side:l3] (2)": "vertex.point",
"design": "load"
},
"demo/logo-up-down-join": {
"Body2/F6:side:l1": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch face.sketch+edit",
"Body2/F6:side:l10": "face.angle_plane face.angle_plane+edit face.draft_neutral face.extrude face.hole face.midplane face.midplane+edit face.offset_plane face.press_pull !face.shell face.sketch face.sketch+edit",
"Body2/F6:side:l10|F6:side:l7": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.fillet+edit edge.normal_plane edge.project",
"Body2/F6:side:l10|F6:side:l9": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l10|F6:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l10|F7:bottom": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.normal_plane+edit edge.project",
"Body2/F6:side:l10|F7:start": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l1|F6:side:l10": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.fillet+edit edge.normal_plane edge.normal_plane+edit edge.project",
"Body2/F6:side:l1|F6:side:l4": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l1|F6:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l1|F7:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l4": "face.angle_plane face.draft_neutral face.extrude face.extrude+edit face.hole face.midplane face.offset_plane face.press_pull face.press_pull+edit face.shell face.sketch",
"Body2/F6:side:l4|F6:side:l7": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l4|F6:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l4|F7:start": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.normal_plane edge.normal_plane+edit edge.project",
"Body2/F6:side:l7": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.midplane+edit face.offset_plane face.press_pull !face.shell face.sketch face.sketch+edit",
"Body2/F6:side:l7|F6:side:l8": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l7|F6:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l7|F7:bottom": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.normal_plane edge.normal_plane+edit edge.project",
"Body2/F6:side:l7|F7:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l8": "face.angle_plane face.draft_neutral face.draft_neutral+edit face.extrude face.extrude+edit face.hole face.offset_plane face.press_pull face.press_pull+edit !face.shell face.sketch",
"Body2/F6:side:l8|F6:side:l9": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l8|F6:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l8|F6:top": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.fillet+edit edge.normal_plane edge.project",
"Body2/F6:side:l8|F7:bottom": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l8|F7:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l9": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull !face.shell face.sketch",
"Body2/F6:side:l9|F6:start": "edge.axis edge.chamfer edge.chamfer+edit edge.fillet edge.fillet+edit edge.normal_plane edge.project",
"Body2/F6:side:l9|F6:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F6:side:l9|F7:bottom": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:side:l9|F7:side:l12": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:start": "face.angle_plane face.angle_plane+edit face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch",
"Body2/F6:start|F7:side:l12": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:start|F7:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F6:top": "face.angle_plane face.draft_neutral face.draft_neutral+edit face.extrude face.hole face.offset_plane face.press_pull !face.shell face.sketch",
"Body2/F7:bottom": "face.angle_plane face.angle_plane+edit face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull !face.shell face.sketch",
"Body2/F7:bottom|F7:side:l12": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F7:bottom|F7:side:l5": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project edge.project+edit",
"Body2/F7:side:l12": "face.angle_plane face.draft_neutral face.draft_neutral+edit face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch",
"Body2/F7:side:l12|F7:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body2/F7:side:l5": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch",
"Body2/F7:start": "face.angle_plane face.draft_neutral face.draft_neutral+edit face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch",
"Body2/body": "body.copy body.mirror body.mirror+edit body.move body.move+edit body.pattern",
"Body2/v[F6:side:l1 & F6:side:l10 & F6:top]": "vertex.plane_three_points vertex.point vertex.point+edit",
"Body2/v[F6:side:l1 & F6:side:l10 & F7:start]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l1 & F6:side:l4 & F6:start & F7:side:l12 & F7:side:l5 & F7:start]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l1 & F6:side:l4 & F6:top]": "vertex.plane_three_points vertex.plane_three_points+edit vertex.point",
"Body2/v[F6:side:l10 & F6:side:l7 & F7:bottom]": "vertex.plane_three_points vertex.plane_three_points+edit vertex.point",
"Body2/v[F6:side:l10 & F6:side:l7 & F7:start]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l10 & F6:side:l9 & F6:top]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l10 & F6:side:l9 & F7:bottom]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l4 & F6:side:l7 & F6:top]": "vertex.plane_three_points vertex.plane_three_points+edit vertex.point",
"Body2/v[F6:side:l4 & F6:side:l7 & F7:start]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l7 & F6:side:l8 & F6:top]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l7 & F6:side:l8 & F7:bottom]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l8 & F6:side:l9 & F6:start]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l8 & F6:side:l9 & F6:top]": "vertex.plane_three_points vertex.plane_three_points+edit vertex.point",
"Body2/v[F6:side:l8 & F6:start & F7:side:l5]": "vertex.plane_three_points vertex.plane_three_points+edit vertex.point",
"Body2/v[F6:side:l8 & F7:bottom & F7:side:l5]": "vertex.plane_three_points vertex.point",
"Body2/v[F6:side:l9 & F6:start & F7:side:l12]": "vertex.plane_three_points vertex.point vertex.point+edit",
"Body2/v[F6:side:l9 & F7:bottom & F7:side:l12]": "vertex.plane_three_points vertex.point",
"Body2/v[F7:bottom & F7:side:l12 & F7:side:l5]": "vertex.plane_three_points vertex.plane_three_points+edit vertex.point",
"design": "load"
},
"demo/pi-case": {
"Body1/F11:side:l5": "face.angle_plane !face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch face.sketch+edit",
"Body1/F11:side:l5|F11:side:l6": "edge.axis edge.axis+edit edge.chamfer edge.fillet edge.normal_plane edge.project",
@@ -11275,6 +11340,71 @@
"Body1/v[F3:side:l3#2 & F3:side:l3#[F3:end] & F3:side:l4#2 & F3:side:l4#[F3:end]]": "vertex.plane_three_points vertex.point",
"Body1/v[F3:side:l3#[F3:start] & F3:side:l4#[F3:start] & F3:start]": "vertex.plane_three_points vertex.point",
"design": "load"
},
"typical/up-down-join": {
"Body1/F2:side:l1": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch",
"Body1/F2:side:l10": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull !face.shell face.sketch",
"Body1/F2:side:l10|F2:side:l7": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l10|F2:side:l9": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l10|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l10|F3:bottom": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l10|F3:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l1|F2:side:l10": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l1|F2:side:l4": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l1|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l1|F3:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l4": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch",
"Body1/F2:side:l4|F2:side:l7": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l4|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l4|F3:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l7": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull !face.shell face.sketch",
"Body1/F2:side:l7|F2:side:l8": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l7|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l7|F3:bottom": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l7|F3:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l8": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull !face.shell face.sketch",
"Body1/F2:side:l8|F2:side:l9": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l8|F2:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l8|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l8|F3:bottom": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l8|F3:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l9": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull !face.shell face.sketch",
"Body1/F2:side:l9|F2:start": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l9|F2:top": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l9|F3:bottom": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:side:l9|F3:side:l12": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:start": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull face.shell face.sketch",
"Body1/F2:start|F3:side:l12": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:start|F3:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F2:top": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull !face.shell face.sketch",
"Body1/F3:bottom": "face.angle_plane face.draft_neutral face.extrude face.hole face.midplane face.offset_plane face.press_pull !face.shell face.sketch",
"Body1/F3:bottom|F3:side:l12": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F3:bottom|F3:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F3:side:l12": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch",
"Body1/F3:side:l12|F3:side:l5": "edge.axis edge.chamfer edge.fillet edge.normal_plane edge.project",
"Body1/F3:side:l5": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch",
"Body1/F3:start": "face.angle_plane face.draft_neutral face.extrude face.hole face.offset_plane face.press_pull face.shell face.sketch",
"Body1/body": "body.copy body.mirror body.move body.pattern",
"Body1/v[F2:side:l1 & F2:side:l10 & F2:top]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l1 & F2:side:l10 & F3:start]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l1 & F2:side:l4 & F2:start & F3:side:l12 & F3:side:l5 & F3:start]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l1 & F2:side:l4 & F2:top]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l10 & F2:side:l7 & F3:bottom]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l10 & F2:side:l7 & F3:start]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l10 & F2:side:l9 & F2:top]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l10 & F2:side:l9 & F3:bottom]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l4 & F2:side:l7 & F2:top]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l4 & F2:side:l7 & F3:start]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l7 & F2:side:l8 & F2:top]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l7 & F2:side:l8 & F3:bottom]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l8 & F2:side:l9 & F2:start]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l8 & F2:side:l9 & F2:top]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l8 & F2:start & F3:side:l5]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l8 & F3:bottom & F3:side:l5]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l9 & F2:start & F3:side:l12]": "vertex.plane_three_points vertex.point",
"Body1/v[F2:side:l9 & F3:bottom & F3:side:l12]": "vertex.plane_three_points vertex.point",
"Body1/v[F3:bottom & F3:side:l12 & F3:side:l5]": "vertex.plane_three_points vertex.point",
"design": "load"
}
}
}
Binary file not shown.
@@ -0,0 +1,11 @@
{"description": "One sketch of four squares drawn edge to edge, three extruded up and three down from it and joined: every side wall spans both extrudes or runs along two coincident sketch lines.",
"commands": [
{"command": "sketch.create", "params": {"plane": "XY", "name": "Squares"}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [10, 10]}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [-10, 0], "p1": [0, 10]}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [0, -10], "p1": [10, 0]}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [-10, -10], "p1": [0, 0]}},
{"command": "sketch.finish", "params": {}},
{"command": "solid.extrude", "params": {"sketch": "Squares", "profiles": [{"point": [-5, 5]}, {"point": [-5, -5]}, {"point": [5, -5]}], "distance": 10}},
{"command": "solid.extrude", "params": {"sketch": "Squares", "profiles": [{"point": [5, 5]}, {"point": [5, -5]}, {"point": [-5, 5]}], "distance": 10, "direction": "negative", "operation": "join"}}
]}