Feature fields: a type for each role (Expr, BodyName, FacePick…) (#1273)

Thanks @jijinbei.
This commit is contained in:
JIJINBEI authored and GitHub committed 2026-10-16 00:28:12 -04:00
1 parent 3333f8a62b
commit 44968dbfe6
59 files changed
+1505 -1043

No files matched your search

+100 -84
View File
@@ -637,7 +637,7 @@ impl Dialog {
let preselection: &[Sel] = if matches!(d.kind, Kind::Text(_)) { &[] } else { &s.selection };
for sel in preselection {
let as_body = match sel {
Sel::Face { body, .. } | Sel::Edge { body, .. } | Sel::Vertex { body, .. } => Some(Sel::Body { name: body.clone() }),
Sel::Face { body, .. } | Sel::Edge { body, .. } | Sel::Vertex { body, .. } => Some(Sel::Body { name: body.to_string() }),
_ => None,
};
if let Some(b) = as_body
@@ -3343,7 +3343,7 @@ pub fn apply_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>
};
if let Some(sketch) = original_all
&& !d.profile_selection_changed
&& let Some(ss) = app.session.world_state().sketch(sketch)
&& let Some(ss) = app.session.world_state().sketch(sketch.0)
{
let mut selected: Vec<usize> = sels(d, 0)
.iter()
@@ -3483,7 +3483,7 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
_ => None,
});
let has_all = |id: u64| curves.iter().all(|c| st.sketch(id).is_some_and(|ss| ss.sketch.curve_index(c).is_some()));
let sketch = match sketch.or(edited) {
let sketch = match sketch.or(edited.map(u64::from)) {
Some(id) if has_all(id) => id,
_ => curves_sketch(app, &curves, None).ok_or("the curves must be in one sketch")?,
};
@@ -4248,14 +4248,15 @@ fn emboss_face_sel(s: &Session, component: u64, face: &solidcraft_engine::doc::E
let owner = s.doc.model_body_component(body);
let world_name = s.doc.world_body_name(&source, &face.body, owner, &face.path);
let author = solidcraft_engine::doc::EmbossFace::component_frame(&s.doc, component, &face.author_path).ok()?;
let point = solidcraft_engine::doc::apply_point(&author, face.at);
let point = solidcraft_engine::doc::apply_point(&author, face.at.0);
s.world_state().body(&world_name)?;
Some(Sel::Face { body: world_name, index, point })
}
pub(crate) fn plane_sel(s: &Session, pl: &PlaneRef) -> Option<Sel> {
match pl {
PlaneRef::Origin { name } | PlaneRef::Construction { name } => Some(Sel::Plane { name: name.clone() }),
PlaneRef::Origin { name } => Some(Sel::Plane { name: name.clone() }),
PlaneRef::Construction { name } => Some(Sel::Plane { name: name.to_string() }),
PlaneRef::Custom { plane } | PlaneRef::Face { plane, .. } => face_sel(s, plane.origin),
_ => None,
}
@@ -4299,7 +4300,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
FeatureKind::Extrude { sketch, profiles, extent, operation, targets, thin } => {
let mut d = start("solid.extrude")?;
d.kind = Kind::Extrude {
distance: extent.distance.clone(),
distance: extent.distance.to_string(),
direction: match (extent.direction, &extent.distance2) {
(_, Some(_)) => TWO_SIDES,
(Direction::Positive, None) => 0,
@@ -4309,16 +4310,16 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
operation: op_index(operation),
auto_op: false,
smart: crate::smart_extrude::Smart { manual_dir: true, ..Default::default() },
distance2: extent.distance2.clone().unwrap_or_else(|| "10 mm".into()),
taper: extent.taper.clone().unwrap_or_else(|| "0 deg".into()),
distance2: extent.distance2.as_deref().map(str::to_string).unwrap_or_else(|| "10 mm".into()),
taper: extent.taper.as_deref().map(str::to_string).unwrap_or_else(|| "0 deg".into()),
taper_present: extent.taper.is_some(),
to_offset: extent.to_offset.clone().unwrap_or_default(),
start: extent.start_offset.clone().unwrap_or_default(),
to_offset: extent.to_offset.as_deref().map(str::to_string).unwrap_or_default(),
start: extent.start_offset.as_deref().map(str::to_string).unwrap_or_default(),
all: extent.through_all,
thin: thin.is_some(),
wall: thin.as_ref().map_or_else(|| "2 mm".into(), |t| t.thickness.clone()),
wall: thin.as_ref().map_or_else(|| "2 mm".into(), |t| t.thickness.to_string()),
wall_side: thin.as_ref().map_or(0, |t| WALL_SIDES.iter().position(|w| ThinSide::parse(w) == Some(t.side)).unwrap_or(0)),
wall2: thin.as_ref().and_then(|t| t.thickness2.clone()).unwrap_or_else(|| "1 mm".into()),
wall2: thin.as_ref().and_then(|t| t.thickness2.as_deref().map(str::to_string)).unwrap_or_else(|| "1 mm".into()),
};
d.extra.insert("convention".into(), json!(extent.convention));
// Extent references belong to the extrude's authoring frame, including free
@@ -4328,7 +4329,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
if let Some(point) = point {
set_object(&mut d.inputs, &mut d.active, label, true);
if let Some(input) = d.inputs.iter_mut().find(|i| i.label == label) {
input.items = vec![Sel::Vertex { body: String::new(), point: solidcraft_engine::doc::apply_point(&frame, point) }];
input.items = vec![Sel::Vertex { body: String::new(), point: solidcraft_engine::doc::apply_point(&frame, point.0) }];
}
}
}
@@ -4338,9 +4339,9 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
inp.items = thin.curves.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect();
}
} else {
let items = st.sketch(*sketch).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
let items = st.sketch(sketch.0).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
if let Some(inp) = d.inputs.first_mut() {
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: *sketch, index }).collect();
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: sketch.0, index }).collect();
}
}
if !targets.is_empty() {
@@ -4353,12 +4354,12 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
let frame = feature_frame(s, f.component);
let stored_axis = match axis {
AxisRef::Line { origin, dir } => Some(json!({
"origin": pt(solidcraft_engine::doc::apply_point(&frame, *origin)),
"dir": pt(solidcraft_engine::doc::apply_vector(&frame, *dir))
"origin": pt(solidcraft_engine::doc::apply_point(&frame, origin.0)),
"dir": pt(solidcraft_engine::doc::apply_vector(&frame, dir.0))
})),
_ => None,
};
d.kind = Kind::Revolve { angle: angle.clone(), operation: op_index(operation), axis: stored_axis };
d.kind = Kind::Revolve { angle: angle.to_string(), operation: op_index(operation), axis: stored_axis };
d.extra.insert("convention".into(), json!(convention));
if let Some(a2) = angle2 {
d.extra.insert("angle2".into(), json!(a2));
@@ -4369,12 +4370,12 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
if let Some(point) = to {
set_to_object(&mut d.inputs, &mut d.active, true);
if let Some(input) = d.inputs.iter_mut().find(|i| i.label == TO_OBJECT) {
input.items = vec![Sel::Vertex { body: String::new(), point: solidcraft_engine::doc::apply_point(&frame, *point) }];
input.items = vec![Sel::Vertex { body: String::new(), point: solidcraft_engine::doc::apply_point(&frame, point.0) }];
}
}
let items = st.sketch(*sketch).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
let items = st.sketch(sketch.0).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
if let Some(inp) = d.inputs.get_mut(0) {
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: *sketch, index }).collect();
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: sketch.0, index }).collect();
}
let ax = match axis {
AxisRef::World { axis } => Some(Sel::Axis { name: axis.to_ascii_uppercase() }),
@@ -4400,10 +4401,10 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
if let FeatureKind::Chamfer { distance2: d2, angle: a, flip: f, .. } = &f.kind {
if let Some(v) = d2 {
*ctype = 1;
*distance2 = v.clone();
*distance2 = v.to_string();
} else if let Some(v) = a {
*ctype = 2;
*angle = v.clone();
*angle = v.to_string();
}
*flip = *f;
}
@@ -4446,51 +4447,51 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
FilletStyle::Constant | FilletStyle::Chord | FilletStyle::Curvature => {}
}
// The start was the first edge's last point: ends swapped.
let first_end = edges.first().and_then(|p| edge_sel(s, *p)).and_then(|x| match x {
let first_end = edges.first().and_then(|p| edge_sel(s, p.0)).and_then(|x| match x {
Sel::Edge { body, index, .. } => {
s.world_state().body(&body).and_then(|b| b.mesh().edges.get(index).and_then(|e| e.last().copied()))
}
_ => None,
});
let swapped = first_end.is_some_and(|e| e.dist(*start) < 1e-6);
let swapped = first_end.is_some_and(|e| e.dist(start.0) < 1e-6);
d.extra.insert("swap_ends".into(), json!(swapped));
}
}
}
if let Some(inp) = d.inputs.first_mut() {
inp.items = edges.iter().filter_map(|p| edge_sel(s, *p)).collect();
inp.items = edges.iter().filter_map(|p| edge_sel(s, p.0)).collect();
}
d
}
FeatureKind::Shell { faces, thickness, direction, tangent_chain, .. } => {
let mut d = start("solid.shell")?;
d.kind = Kind::Shell { thickness: thickness.clone() };
d.kind = Kind::Shell { thickness: thickness.to_string() };
d.extra.insert("tangent_chain".into(), json!(tangent_chain));
if !direction.is_empty() && direction != "inside" {
d.extra.insert("direction".into(), json!(direction));
}
if let Some(inp) = d.inputs.first_mut() {
inp.items = faces.iter().filter_map(|p| face_sel(s, *p)).collect();
inp.items = faces.iter().filter_map(|p| face_sel(s, p.0)).collect();
}
d
}
FeatureKind::Draft { faces, angle, neutral, pull, .. } => {
let mut d = start("solid.draft")?;
d.kind = Kind::Draft { angle: angle.clone() };
d.kind = Kind::Draft { angle: angle.to_string() };
if let Some(inp) = d.inputs.get_mut(0) {
inp.items = faces.iter().filter_map(|p| face_sel(s, *p)).collect();
inp.items = faces.iter().filter_map(|p| face_sel(s, p.0)).collect();
}
if let Some(inp) = d.inputs.get_mut(1) {
inp.items = plane_sel(s, neutral).into_iter().collect();
}
d.extra.insert("pull".into(), pt3(*pull));
d.extra.insert("pull".into(), pt3(pull.0));
d
}
FeatureKind::Hole { position, direction, diameter, depth, hole, points, thread, to } => {
let mut d = start("solid.hole")?;
let mut k = hole_defaults();
if let Kind::Hole { diameter: dia, depth: dep, kind, cb_diameter, cb_depth, cs_diameter, cs_angle, opts } = &mut k {
*dia = diameter.clone();
*dia = diameter.to_string();
opts.all = depth.is_none();
opts.angled = matches!(hole, HoleKind::Drilled { .. });
opts.multiple = points.is_some();
@@ -4499,21 +4500,21 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
opts.thread = th.clone();
}
if let Some(dd) = depth {
*dep = dd.clone();
*dep = dd.to_string();
}
match hole {
HoleKind::Counterbore { cb_diameter: a, cb_depth: b } => {
*kind = 1;
*cb_diameter = a.clone();
*cb_depth = b.clone();
*cb_diameter = a.to_string();
*cb_depth = b.to_string();
}
HoleKind::Countersink { cs_diameter: a, cs_angle: b } => {
*kind = 2;
*cs_diameter = a.clone();
*cs_angle = b.clone();
*cs_diameter = a.to_string();
*cs_angle = b.to_string();
}
HoleKind::Drilled { tip_angle } => {
opts.tip_angle = tip_angle.clone();
opts.tip_angle = tip_angle.to_string();
}
HoleKind::Simple => {}
}
@@ -4531,16 +4532,16 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
None => {
if let Some(inp) = d.inputs.first_mut() {
inp.items = vec![face_sel(s, *position).unwrap_or(Sel::Face { body: String::new(), index: 0, point: *position })];
inp.items = vec![face_sel(s, position.0).unwrap_or(Sel::Face { body: String::new(), index: 0, point: position.0 })];
}
d.extra.insert("direction".into(), pt3(*direction));
d.extra.insert("direction".into(), pt3(direction.0));
}
}
if to.is_some() {
if let Kind::Hole { opts, .. } = &mut d.kind {
opts.all = false;
}
with_to_object(&mut d, s, *to);
with_to_object(&mut d, s, to.map(Vec3::from));
}
// A hole at every point of a sketch.
if points.as_ref().is_some_and(|sp| sp.ids.is_empty()) {
@@ -4568,7 +4569,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
at: None,
};
d.extra.insert("base".into(), pt_expr(base));
d.extra.insert("axis".into(), pt3(*axis));
d.extra.insert("axis".into(), pt3(axis.0));
d
}
FeatureKind::Sphere { center, radius, operation } => {
@@ -4593,10 +4594,10 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
let mut d = start("solid.boundary_fill")?;
// The cells again, ticked where a stored cell point lies in the same cell.
let before = s.model.state_before(f.id);
let found = fill_cells(&before, tools);
let found = fill_cells(&before, &solidcraft_engine::doc::plain(tools));
let filled: Vec<String> =
cells.iter().filter_map(|c| found.iter().min_by(|a, b| a.0.dist(*c).total_cmp(&b.0.dist(*c))).map(|x| x.1.clone())).collect();
let items: Vec<Sel> = tools.iter().map(|n| Sel::Body { name: n.clone() }).collect();
cells.iter().filter_map(|c| found.iter().min_by(|a, b| a.0.dist(c.0).total_cmp(&b.0.dist(c.0))).map(|x| x.1.clone())).collect();
let items: Vec<Sel> = tools.iter().map(|n| Sel::Body { name: n.to_string() }).collect();
d.kind = Kind::BoundaryFill {
operation: op_index(operation),
cells: found.into_iter().map(|(p, l)| (p, l.clone(), filled.contains(&l))).collect(),
@@ -4614,10 +4615,10 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
let mut d = start("solid.combine")?;
d.kind = Kind::Combine { operation: op_index(operation).max(1), keep_tools: *keep_tools };
if let Some(inp) = d.inputs.get_mut(0) {
inp.items = vec![Sel::Body { name: target.clone() }];
inp.items = vec![Sel::Body { name: target.to_string() }];
}
if let Some(inp) = d.inputs.get_mut(1) {
inp.items = tools.iter().map(|n| Sel::Body { name: n.clone() }).collect();
inp.items = tools.iter().map(|n| Sel::Body { name: n.to_string() }).collect();
}
d
}
@@ -4628,7 +4629,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
let as_features = ids.iter().any(|id| !st.bodies.iter().any(|b| b.feature == *id));
if let Some(inp) = d.inputs.get_mut(0) {
if !bodies.is_empty() {
inp.items = bodies.iter().map(|name| Sel::Body { name: name.clone() }).collect();
inp.items = bodies.iter().map(|name| Sel::Body { name: name.to_string() }).collect();
} else if as_features {
inp.accept = selection::FEATURES;
inp.label = objects_label(inp.accept);
@@ -4647,28 +4648,28 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
let bodies: Vec<Sel> = if copied.is_empty() {
st.bodies.iter().filter(|b| ids.contains(&b.feature)).map(|b| Sel::Body { name: b.name.clone() }).collect()
} else {
copied.iter().map(|name| Sel::Body { name: name.clone() }).collect()
copied.iter().map(|name| Sel::Body { name: name.to_string() }).collect()
};
let mut d = match pattern {
PatternKind::Rectangular { dir1, count1, spacing1, dir2, count2, spacing2 } => {
let mut d = start("solid.pattern.rectangular")?;
d.kind = Kind::PatternRect {
count: count1.clone(),
spacing: spacing1.clone(),
count2: count2.clone().unwrap_or_else(|| "1".into()),
spacing2: spacing2.clone().unwrap_or_else(|| "20 mm".into()),
count: count1.to_string(),
spacing: spacing1.to_string(),
count2: count2.as_deref().map(str::to_string).unwrap_or_else(|| "1".into()),
spacing2: spacing2.as_deref().map(str::to_string).unwrap_or_else(|| "20 mm".into()),
};
// Directions along the origin axes show as those axes; others are kept as is.
for (i, (key, dir)) in [("dir1", Some(*dir1)), ("dir2", *dir2)].into_iter().enumerate() {
let Some(dir) = dir else { continue };
match world_axis(dir) {
match world_axis(dir.0) {
Some(name) => {
if let Some(inp) = d.inputs.get_mut(i + 1) {
inp.items = vec![Sel::Axis { name }];
}
}
None => {
d.extra.insert(key.into(), pt3(dir));
d.extra.insert(key.into(), pt3(dir.0));
}
}
}
@@ -4676,15 +4677,15 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
PatternKind::Circular { origin, axis, count, angle } => {
let mut d = start("solid.pattern.circular")?;
d.kind = Kind::PatternCirc { count: count.clone(), angle: angle.clone() };
match world_axis(*axis).filter(|_| origin.len() < 1e-9) {
d.kind = Kind::PatternCirc { count: count.to_string(), angle: angle.to_string() };
match world_axis(axis.0).filter(|_| origin.len() < 1e-9) {
Some(name) => {
if let Some(inp) = d.inputs.get_mut(1) {
inp.items = vec![Sel::Axis { name }];
}
}
None => {
d.extra.insert("axis".into(), json!({"origin": pt3(*origin), "dir": pt3(*axis)}));
d.extra.insert("axis".into(), json!({"origin": pt3(origin.0), "dir": pt3(axis.0)}));
}
}
d
@@ -4712,8 +4713,8 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
inp.items = sections
.iter()
.flat_map(|sec| {
let idx = st.sketch(sec.sketch).map(|ss| profile_indices(ss, &sec.profiles)).unwrap_or_default();
idx.into_iter().map(move |index| Sel::Profile { sketch: sec.sketch, index })
let idx = st.sketch(sec.sketch.0).map(|ss| profile_indices(ss, &sec.profiles)).unwrap_or_default();
idx.into_iter().map(move |index| Sel::Profile { sketch: sec.sketch.0, index })
})
.collect();
}
@@ -4729,9 +4730,9 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
if let Some(inp) = d.inputs.get_mut(2) {
inp.items = direction.into_iter().collect();
}
let items = st.sketch(*sketch).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
let items = st.sketch(sketch.0).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
if let Some(inp) = d.inputs.get_mut(0) {
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: *sketch, index }).collect();
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: sketch.0, index }).collect();
}
if let Some(inp) = d.inputs.get_mut(1) {
inp.items = path.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect();
@@ -4743,7 +4744,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
FeatureKind::ConstructionPlane { plane: PlaneRef::Offset { base, distance } } => {
let mut d = start("construct.plane.offset")?;
d.kind = Kind::OffsetPlane { offset: distance.clone() };
d.kind = Kind::OffsetPlane { offset: distance.to_string() };
if let Some(inp) = d.inputs.first_mut() {
inp.items = plane_sel(s, base).into_iter().collect();
}
@@ -4760,7 +4761,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
_ => return None,
};
d.kind = Kind::PathPlane { t: t.clone(), sketch };
d.kind = Kind::PathPlane { t: t.to_string(), sketch };
if let Some(input) = d.inputs.first_mut() {
input.items = vec![item];
}
@@ -4768,7 +4769,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
FeatureKind::ConstructionPlane { plane: PlaneRef::AtAngle { base, axis_origin, axis_dir, angle } } => {
let mut d = start("construct.plane.at_angle")?;
d.kind = Kind::AnglePlane { angle: angle.clone() };
d.kind = Kind::AnglePlane { angle: angle.to_string() };
if let Some(inp) = d.inputs.first_mut() {
inp.items = plane_sel(s, base).into_iter().collect();
}
@@ -4787,11 +4788,11 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
FeatureKind::Split { body, plane, tool } => {
let mut d = start("solid.split_body")?;
if let Some(inp) = d.inputs.first_mut() {
inp.items = vec![Sel::Body { name: body.clone() }];
inp.items = vec![Sel::Body { name: body.to_string() }];
}
if let Some(inp) = d.inputs.get_mut(1) {
inp.items = match tool {
Some(t) => face_sel(s, t.point).into_iter().collect(),
Some(t) => face_sel(s, t.point.0).into_iter().collect(),
None => plane_sel(s, plane).into_iter().collect(),
};
}
@@ -4799,11 +4800,11 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
FeatureKind::Scale { bodies, origin, factor, factors } => {
let mut d = start("solid.scale")?;
d.kind = Kind::Scale { factor: factor.clone() };
d.kind = Kind::Scale { factor: factor.to_string() };
if let Some(inp) = d.inputs.first_mut() {
inp.items = bodies.iter().map(|n| Sel::Body { name: n.clone() }).collect();
inp.items = bodies.iter().map(|n| Sel::Body { name: n.to_string() }).collect();
}
d.extra.insert("origin".into(), pt3(*origin));
d.extra.insert("origin".into(), pt3(origin.0));
if let Some(f) = factors {
d.extra.insert("factors".into(), json!(f));
}
@@ -4811,9 +4812,9 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
FeatureKind::OffsetFace { faces, distance, body } => {
let mut d = start("solid.offset_face")?;
d.kind = Kind::OffsetFaces { distance: distance.clone() };
d.kind = Kind::OffsetFaces { distance: distance.to_string() };
if let Some(inp) = d.inputs.first_mut() {
inp.items = faces.iter().filter_map(|p| face_sel(s, *p)).collect();
inp.items = faces.iter().filter_map(|p| face_sel(s, p.0)).collect();
}
if let Some(b) = body {
d.extra.insert("body".into(), json!(b));
@@ -4825,23 +4826,26 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
if *modeled {
d.extra.insert("modeled".into(), json!(true));
}
d.kind = Kind::Thread { designation: designation.clone().unwrap_or_default(), length: length.clone().unwrap_or_default() };
d.kind = Kind::Thread {
designation: designation.clone().unwrap_or_default(),
length: length.as_deref().map(str::to_string).unwrap_or_default(),
};
if let Some(inp) = d.inputs.first_mut() {
inp.items = face_sel(s, *face).into_iter().collect();
inp.items = face_sel(s, face.0).into_iter().collect();
}
d
}
FeatureKind::BoundingSolid { bodies, margin } => {
let mut d = start("solid.bounding_solid")?;
d.kind = Kind::Stock { margin: margin.clone() };
d.kind = Kind::Stock { margin: margin.to_string() };
if let Some(inp) = d.inputs.first_mut() {
inp.items = bodies.iter().map(|n| Sel::Body { name: n.clone() }).collect();
inp.items = bodies.iter().map(|n| Sel::Body { name: n.to_string() }).collect();
}
d
}
FeatureKind::Pipe { path, diameter, wall, operation, targets, .. } => {
let mut d = start("solid.pipe")?;
d.kind = Kind::Pipe { diameter: diameter.clone(), wall: wall.clone().unwrap_or_default() };
d.kind = Kind::Pipe { diameter: diameter.to_string(), wall: wall.as_deref().map(str::to_string).unwrap_or_default() };
if let Some(inp) = d.inputs.first_mut() {
inp.items = path.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect();
}
@@ -4853,10 +4857,10 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
FeatureKind::Emboss { sketch, profiles, depth, deboss, targets, face } => {
let mut d = start("solid.emboss")?;
d.kind = Kind::Emboss { depth: depth.clone(), deboss: *deboss };
let items = st.sketch(*sketch).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
d.kind = Kind::Emboss { depth: depth.to_string(), deboss: *deboss };
let items = st.sketch(sketch.0).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
if let Some(inp) = d.inputs.first_mut() {
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: *sketch, index }).collect();
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: sketch.0, index }).collect();
}
if !targets.is_empty() {
d.extra.insert("targets".into(), json!(targets));
@@ -4870,7 +4874,12 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
}
FeatureKind::Rib { curves, thickness, depth, flip, web, .. } => {
let mut d = start(if *web { "solid.web" } else { "solid.rib" })?;
d.kind = Kind::Rib { web: *web, thickness: thickness.clone(), depth: depth.clone().unwrap_or_default(), flip: *flip };
d.kind = Kind::Rib {
web: *web,
thickness: thickness.to_string(),
depth: depth.as_deref().map(str::to_string).unwrap_or_default(),
flip: *flip,
};
if let Some(inp) = d.inputs.first_mut() {
inp.items = curves.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect();
}
@@ -4879,7 +4888,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
FeatureKind::ReplaceFace { faces, target, body } => {
let mut d = start("solid.replace_face")?;
if let Some(inp) = d.inputs.first_mut() {
inp.items = faces.iter().filter_map(|p| face_sel(s, *p)).collect();
inp.items = faces.iter().filter_map(|p| face_sel(s, p.0)).collect();
}
if let Some(inp) = d.inputs.get_mut(1) {
inp.items = plane_sel(s, target).into_iter().collect();
@@ -4892,13 +4901,20 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
FeatureKind::Remove { bodies } => {
let mut d = start("solid.remove")?;
if let Some(inp) = d.inputs.first_mut() {
inp.items = bodies.iter().map(|n| Sel::Body { name: n.clone() }).collect();
inp.items = bodies.iter().map(|n| Sel::Body { name: n.to_string() }).collect();
}
d
}
FeatureKind::Move { bodies, translate, rotate_axis, angle, copy } => {
let mut d = start("solid.move")?;
crate::dialogs_move::for_feature(&mut d, bodies, translate, *rotate_axis, angle.as_ref(), *copy);
crate::dialogs_move::for_feature(
&mut d,
&solidcraft_engine::doc::plain(bodies),
translate,
rotate_axis.map(Vec3::from),
angle.as_ref(),
*copy,
);
d
}
other => {
+12 -5
View File
@@ -288,8 +288,15 @@ pub fn drag(app: &SolveApp, d: &mut Dialog, g: Drag) {
/// The dialog for editing a move: Free Move with its translation and, about a world axis
/// through the origin, its angle (another axis is kept as it is).
pub fn for_feature(d: &mut Dialog, bodies: &[String], translate: &[String; 3], axis: Option<Vec3>, angle: Option<&String>, copy: bool) {
let [x, y, z] = translate.clone();
pub fn for_feature(
d: &mut Dialog,
bodies: &[String],
translate: &[solidcraft_engine::doc::Expr; 3],
axis: Option<Vec3>,
angle: Option<&solidcraft_engine::doc::Expr>,
copy: bool,
) {
let [x, y, z] = translate.clone().map(String::from);
let mut k = kind(FREE);
if let Kind::Move { x: kx, y: ky, z: kz, rx, ry, rz, .. } = &mut k {
(*kx, *ky, *kz) = (x, y, z);
@@ -297,9 +304,9 @@ pub fn for_feature(d: &mut Dialog, bodies: &[String], translate: &[String; 3], a
let dir = axis.unwrap_or(Vec3::Z);
let world = [Vec3::X, Vec3::Y, Vec3::Z].iter().position(|w| dir.cross(*w).len() < 1e-9 && dir.dot(*w) > 0.0);
match world {
Some(0) => *rx = a.clone(),
Some(1) => *ry = a.clone(),
Some(_) => *rz = a.clone(),
Some(0) => *rx = a.to_string(),
Some(1) => *ry = a.to_string(),
Some(_) => *rz = a.to_string(),
None => {
d.extra.insert("axis".into(), json!([dir.x, dir.y, dir.z]));
d.extra.insert("angle".into(), json!(a));
+24 -24
View File
@@ -410,8 +410,8 @@ pub fn commands(app: &SolveApp, k: &Pl, inputs: &[SelInput], extra: &Map<String,
Ok(vec![(cmd.to_string(), p)])
}
fn s(o: &Option<String>) -> String {
o.clone().unwrap_or_default()
fn s(o: &Option<solidcraft_engine::doc::Expr>) -> String {
o.as_deref().map(str::to_string).unwrap_or_default()
}
/// The dialog editing a plastic feature, filled from it.
@@ -434,7 +434,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
rib_offset,
body,
} => {
extra.insert("direction".into(), pt(*direction));
extra.insert("direction".into(), pt(direction.0));
if let Some(b) = body {
extra.insert("body".into(), json!(b));
}
@@ -452,63 +452,63 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
rib_offset: ro,
} = &mut b
{
*d = diameter.clone();
*h = height.clone();
*d = diameter.to_string();
*h = height.to_string();
*hd = s(hole_diameter);
*hp = s(hole_depth);
*dr = s(draft);
*f = s(fillet);
*r = ribs.clone().unwrap_or_else(|| "0".into());
*r = ribs.as_deref().map(str::to_string).unwrap_or_else(|| "0".into());
for (slot, v) in [(rt, rib_thickness), (rl, rib_length), (ro, rib_offset)] {
if let Some(v) = v {
*slot = v.clone();
*slot = v.to_string();
}
}
}
(b, "plastic.boss", *position)
(b, "plastic.boss", position.0)
}
FeatureKind::Lip { face, width, height, groove, gap, outside, body } => {
if let Some(b) = body {
extra.insert("body".into(), json!(b));
}
(Pl::Lip { groove: *groove, width: width.clone(), height: height.clone(), gap: s(gap), outside: *outside }, "plastic.lip", *face)
(Pl::Lip { groove: *groove, width: width.to_string(), height: height.to_string(), gap: s(gap), outside: *outside }, "plastic.lip", face.0)
}
FeatureKind::SnapFit { position, direction, hook, length, thickness, width, catch_depth, catch_length, body } => {
extra.insert("direction".into(), pt(*direction));
extra.insert("direction".into(), pt(direction.0));
if let Some(b) = body {
extra.insert("body".into(), json!(b));
}
let h = hook_dirs(*direction).iter().position(|(_, d)| d.dist(*hook) < 1e-6);
let h = hook_dirs(direction.0).iter().position(|(_, d)| d.dist(hook.0) < 1e-6);
if h.is_none() {
extra.insert("hook".into(), pt(*hook));
extra.insert("hook".into(), pt(hook.0));
}
let f = Pl::SnapFit {
hook: h.unwrap_or(0),
length: length.clone(),
thickness: thickness.clone(),
width: width.clone(),
catch_depth: catch_depth.clone(),
catch_length: catch_length.clone(),
length: length.to_string(),
thickness: thickness.to_string(),
width: width.to_string(),
catch_depth: catch_depth.to_string(),
catch_length: catch_length.to_string(),
};
(f, "plastic.snap_fit", *position)
(f, "plastic.snap_fit", position.0)
}
FeatureKind::Rest { position, direction, along, width, length, height, draft, thickness, body } => {
extra.insert("direction".into(), pt(*direction));
extra.insert("direction".into(), pt(direction.0));
if let Some(a) = along {
extra.insert("along".into(), pt(*a));
extra.insert("along".into(), pt(a.0));
}
if let Some(b) = body {
extra.insert("body".into(), json!(b));
}
let r = Pl::Rest {
round: length.is_none(),
width: width.clone(),
length: length.clone().unwrap_or_else(|| "15 mm".into()),
height: height.clone(),
width: width.to_string(),
length: length.as_deref().map(str::to_string).unwrap_or_else(|| "15 mm".into()),
height: height.to_string(),
draft: s(draft),
thickness: s(thickness),
};
(r, "plastic.rest", *position)
(r, "plastic.rest", position.0)
}
_ => return None,
};
+24 -18
View File
@@ -673,20 +673,20 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
let mut extra = Map::new();
let (k, items): (Sm, Vec<Sel>) = match kind {
FeatureKind::SheetBase { sketch, profiles, rule, flip } => {
let ps = st.sketch(*sketch).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
let ps = st.sketch(sketch.0).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
let mut f = new_flange(0);
if let Sm::Flange { flip: fl, rule: r, .. } = &mut f {
*fl = *flip;
*r = rule_index(rule);
}
(f, ps.into_iter().map(|index| Sel::Profile { sketch: *sketch, index }).collect())
(f, ps.into_iter().map(|index| Sel::Profile { sketch: sketch.0, index }).collect())
}
FeatureKind::SheetContour { curves, distance, rule, flip, reverse, .. } => {
let mut f = new_flange(2);
if let Sm::Flange { flip: fl, rule: r, distance: d, reverse: rv, .. } = &mut f {
*fl = *flip;
*r = rule_index(rule);
*d = distance.clone();
*d = distance.to_string();
*rv = *reverse;
}
(f, curves.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect())
@@ -699,17 +699,21 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
// edge's selection carries).
let (mut picked, mut own) = (Vec::new(), Vec::new());
for e in edges {
let Some(sel) = edge_sel(s, e.point) else { continue };
let Some(sel) = edge_sel(s, e.point.0) else { continue };
if let (Sel::Edge { point, .. }, true) = (&sel, e.angle.is_some() || e.height.is_some()) {
own.push((*point, e.angle.clone().unwrap_or_default(), e.height.clone().unwrap_or_default()));
own.push((
*point,
e.angle.as_deref().map(str::to_string).unwrap_or_default(),
e.height.as_deref().map(str::to_string).unwrap_or_default(),
));
}
picked.push(sel);
}
let f = Sm::Flange {
ty: 1,
height: height.clone(),
angle: angle.clone(),
radius: radius.clone().unwrap_or_default(),
height: height.to_string(),
angle: angle.to_string(),
radius: radius.as_deref().map(str::to_string).unwrap_or_default(),
position: POSITIONS.iter().position(|p| p == position).unwrap_or(0),
flip: *flip,
distance: "20 mm".into(),
@@ -720,7 +724,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
.as_deref()
.and_then(|name| RELIEF_SHAPES.iter().position(|s| s.eq_ignore_ascii_case(name.trim())))
.map_or(0, |i| i + 1),
relief_size: corner_relief.clone().unwrap_or_default(),
relief_size: corner_relief.as_deref().map(str::to_string).unwrap_or_default(),
};
(f, picked)
}
@@ -729,8 +733,8 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
extra.insert("body".into(), json!(b));
}
(
Sm::Hem { length: length.clone(), gap: gap.clone().unwrap_or_default(), flip: *flip },
edges.iter().filter_map(|p| edge_sel(s, *p)).collect(),
Sm::Hem { length: length.to_string(), gap: gap.as_deref().map(str::to_string).unwrap_or_default(), flip: *flip },
edges.iter().filter_map(|p| edge_sel(s, p.0)).collect(),
)
}
FeatureKind::SheetFold { curve, a, b, angle, radius, position, flip, fixed, body, .. } => {
@@ -738,8 +742,8 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
extra.insert("body".into(), json!(bd));
}
let k = Sm::Fold {
angle: angle.clone(),
radius: radius.clone().unwrap_or_default(),
angle: angle.to_string(),
radius: radius.as_deref().map(str::to_string).unwrap_or_default(),
position: FOLD_POSITIONS.iter().position(|x| x == position || (*x == "mould" && position == "mold")).unwrap_or(0),
flip: *flip,
};
@@ -752,22 +756,24 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind) -> Option<(Kind, Vec<SelI
}
// A fold given by two points keeps them.
None => {
extra.insert("points".into(), json!([pt(*a), pt(*b)]));
extra.insert("points".into(), json!([pt(a.0), pt(b.0)]));
}
}
if let Some(i) = ins.get_mut(1) {
i.items = fixed.and_then(|f| face_sel(s, f)).into_iter().collect();
i.items = fixed.and_then(|f| face_sel(s, f.0)).into_iter().collect();
}
return Some((Kind::Sheet(k), ins, extra));
}
FeatureKind::SheetUnfold { body, refold } => (Sm::Unfold { refold: *refold }, body.iter().map(|n| Sel::Body { name: n.clone() }).collect()),
FeatureKind::SheetUnfold { body, refold } => {
(Sm::Unfold { refold: *refold }, body.iter().map(|n| Sel::Body { name: n.to_string() }).collect())
}
FeatureKind::SheetConvert { face, rule, bends, .. } => {
let (_, mut ins) = start(app, "sheet.convert")?;
if let Some(i) = ins.first_mut() {
i.items = face_sel(s, *face).into_iter().collect();
i.items = face_sel(s, face.0).into_iter().collect();
}
if let Some(i) = ins.get_mut(1) {
i.items = bends.iter().filter_map(|p| edge_sel(s, *p)).collect();
i.items = bends.iter().filter_map(|p| edge_sel(s, p.0)).collect();
}
return Some((Kind::Sheet(Sm::Convert { rule: rule_index(rule) }), ins, extra));
}
+38 -34
View File
@@ -575,7 +575,7 @@ pub fn commands(app: &SolveApp, k: &Surf, inputs: &[SelInput], extra: &Map<Strin
/// A tool as a selection again: a plane, else the face at the stored point.
fn tool_sel(app: &SolveApp, plane: &PlaneRef, tool: Option<&FaceAt>) -> Option<Sel> {
match tool {
Some(t) => crate::dialogs::face_sel(&app.session, t.point),
Some(t) => crate::dialogs::face_sel(&app.session, t.point.0),
None => crate::dialogs::plane_sel(&app.session, plane),
}
}
@@ -584,7 +584,7 @@ fn tool_sel(app: &SolveApp, plane: &PlaneRef, tool: Option<&FaceAt>) -> Option<S
/// `frame` maps the feature's component frame to the world, for an axis stored as a line.
pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(Kind, Vec<SelInput>, Map<String, Value>)> {
let s = &app.session;
let edge = |p: &Vec3| crate::dialogs::edge_sel(s, *p);
let edge = |p: &solidcraft_engine::doc::Position| crate::dialogs::edge_sel(s, p.0);
let (k, fill): (Surf, Vec<Vec<Sel>>) = match kind {
FeatureKind::SurfaceRevolve { sketch, profiles, curves, axis, angle, angle2, symmetric } => {
let (pick, stored) = match axis {
@@ -594,8 +594,8 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
AxisRef::Line { origin, dir } => (
None,
Some(json!({
"origin": pt(solidcraft_engine::doc::apply_point(frame, *origin)),
"dir": pt(solidcraft_engine::doc::apply_vector(frame, *dir))
"origin": pt(solidcraft_engine::doc::apply_point(frame, origin.0)),
"dir": pt(solidcraft_engine::doc::apply_vector(frame, dir.0))
})),
),
};
@@ -603,9 +603,9 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
if let Some(a2) = angle2 {
extra.insert("angle2".into(), json!(a2));
}
let k = Surf::Revolve { angle: angle.clone(), symmetric: *symmetric, axis: stored };
let k = Surf::Revolve { angle: angle.to_string(), symmetric: *symmetric, axis: stored };
let mut ins = inputs(&k);
for (inp, items) in ins.iter_mut().zip([sheet_sels(app, *sketch, profiles.as_ref(), curves), pick.into_iter().collect()]) {
for (inp, items) in ins.iter_mut().zip([sheet_sels(app, sketch.0, profiles.as_ref(), curves), pick.into_iter().collect()]) {
inp.items = items;
}
return Some((Kind::Surface(k), ins, extra));
@@ -615,23 +615,23 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
(
Surf::Sweep(opts),
vec![
sheet_sels(app, *sketch, profiles.as_ref(), curves),
sheet_sels(app, sketch.0, profiles.as_ref(), curves),
path.iter().map(|id| Sel::SketchCurve { id: id.clone() }).collect(),
direction.into_iter().collect(),
],
)
}
FeatureKind::SurfaceLoft { sections } => {
(Surf::Loft, vec![sections.iter().flat_map(|sec| sheet_sels(app, sec.sketch, sec.profiles.as_ref(), &sec.curves)).collect()])
(Surf::Loft, vec![sections.iter().flat_map(|sec| sheet_sels(app, sec.sketch.0, sec.profiles.as_ref(), &sec.curves)).collect()])
}
FeatureKind::SurfaceExtrude { sketch, profiles, curves, extent } => {
let sels = sheet_sels(app, *sketch, profiles.as_ref(), curves);
let sels = sheet_sels(app, sketch.0, profiles.as_ref(), curves);
let direction = match extent.direction {
Direction::Positive => 0,
Direction::Symmetric => 1,
Direction::Negative => 2,
};
let k = Surf::Extrude { distance: extent.distance.clone(), direction };
let k = Surf::Extrude { distance: extent.distance.to_string(), direction };
let mut ins = inputs(&k);
if let Some(input) = ins.first_mut() {
input.items = sels;
@@ -644,14 +644,14 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
let mut sels: Vec<Sel> = edges.iter().filter_map(edge).collect();
if let Some(sk) = sketch {
let st = s.world_state();
if let Some(ss) = st.sketch(*sk) {
sels.extend((0..ss.profiles.len()).map(|index| Sel::Profile { sketch: *sk, index }));
if let Some(ss) = st.sketch(sk.0) {
sels.extend((0..ss.profiles.len()).map(|index| Sel::Profile { sketch: sk.0, index }));
}
}
let index = PATCH_CONTINUITY.iter().position(|(_, w)| *w == continuity.all.as_str()).unwrap_or(0);
// What the dialog does not show is kept as it was: the crown, edges with a
// continuity of their own, and points that are not on a face any more.
let through: Vec<Sel> = points.iter().filter_map(|p| crate::dialogs::face_sel(s, *p)).collect();
let through: Vec<Sel> = points.iter().filter_map(|p| crate::dialogs::face_sel(s, p.0)).collect();
let mut extra = Map::new();
if *crown != 0.0 {
extra.insert("crown".into(), json!(crown));
@@ -660,7 +660,7 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
extra.insert("continuity".into(), serde_json::to_value(continuity).unwrap_or(Value::Null));
}
if through.len() != points.len() {
extra.insert("points".into(), json!(points.iter().map(|p| pt(*p)).collect::<Vec<_>>()));
extra.insert("points".into(), json!(points.iter().map(|p| pt(p.0)).collect::<Vec<_>>()));
}
let k = Surf::Patch { continuity: index };
let mut ins = inputs(&k);
@@ -669,48 +669,49 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
}
return Some((Kind::Surface(k), ins, extra));
}
FeatureKind::SurfaceOffset { faces, distance, chain, .. } => {
(Surf::Offset { distance: distance.clone(), chain: *chain }, vec![faces.iter().filter_map(|p| crate::dialogs::face_sel(s, *p)).collect()])
}
FeatureKind::SurfaceOffset { faces, distance, chain, .. } => (
Surf::Offset { distance: distance.to_string(), chain: *chain },
vec![faces.iter().filter_map(|p| crate::dialogs::face_sel(s, p.0)).collect()],
),
FeatureKind::SurfaceRuled { edges, distance, mode, angle, direction, flip, corners, face, .. } => {
let k = Surf::Ruled {
distance: distance.clone(),
distance: distance.to_string(),
mode: match mode {
RuledMode::Normal => 0,
RuledMode::Tangent => 1,
RuledMode::Angle => 2,
RuledMode::Direction => 3,
},
angle: angle.clone().unwrap_or_else(|| "5 deg".into()),
angle: angle.as_deref().map(str::to_string).unwrap_or_else(|| "5 deg".into()),
flip: *flip,
round: *corners == RuledCorners::Round,
};
let mut ins = inputs(&k);
let fill = [edges.iter().filter_map(edge).collect(), face.and_then(|p| crate::dialogs::face_sel(s, p)).into_iter().collect()];
let fill = [edges.iter().filter_map(edge).collect(), face.and_then(|p| crate::dialogs::face_sel(s, p.0)).into_iter().collect()];
for (inp, items) in ins.iter_mut().zip(fill) {
inp.items = items;
}
let mut extra = Map::new();
if let Some(d) = direction {
extra.insert("direction".into(), pt(*d));
extra.insert("direction".into(), pt(d.0));
}
return Some((Kind::Surface(k), ins, extra));
}
FeatureKind::Unstitch { body, faces } | FeatureKind::ReverseNormal { body, faces } => {
let sels: Vec<Sel> = if faces.is_empty() {
vec![Sel::Body { name: body.clone() }]
vec![Sel::Body { name: body.to_string() }]
} else {
faces.iter().filter_map(|p| crate::dialogs::face_sel(s, *p)).collect()
faces.iter().filter_map(|p| crate::dialogs::face_sel(s, p.0)).collect()
};
(if matches!(kind, FeatureKind::Unstitch { .. }) { Surf::Unstitch } else { Surf::ReverseNormal }, vec![sels])
}
FeatureKind::Stitch { bodies, tolerance } => (
Surf::Stitch { tolerance: if tolerance.trim() == "0" { String::new() } else { tolerance.clone() } },
vec![bodies.iter().map(|n| Sel::Body { name: n.clone() }).collect()],
Surf::Stitch { tolerance: if tolerance.trim() == "0" { String::new() } else { tolerance.to_string() } },
vec![bodies.iter().map(|n| Sel::Body { name: n.to_string() }).collect()],
),
FeatureKind::Thicken { bodies, thickness, symmetric, operation, .. } => (
Surf::Thicken { thickness: thickness.clone(), symmetric: *symmetric, operation: op_index(operation) },
vec![bodies.iter().map(|n| Sel::Body { name: n.clone() }).collect()],
Surf::Thicken { thickness: thickness.to_string(), symmetric: *symmetric, operation: op_index(operation) },
vec![bodies.iter().map(|n| Sel::Body { name: n.to_string() }).collect()],
),
FeatureKind::SurfaceExtend { edges, distance, mode, up_to, neighbours, .. } => {
// The dialog edits the edges and the distance; the rest is kept as it was.
@@ -726,14 +727,17 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
extra.insert("up_to".into(), json!("bounds"));
}
Some(solidcraft_engine::doc::ExtendTo::Face(t)) => {
extra.insert("up_to".into(), json!({"body": t.body, "point": pt(t.point)}));
extra.insert("up_to".into(), json!({"body": t.body, "point": pt(t.point.0)}));
}
Some(solidcraft_engine::doc::ExtendTo::Plane(PlaneRef::Origin { name } | PlaneRef::Construction { name })) => {
Some(solidcraft_engine::doc::ExtendTo::Plane(PlaneRef::Origin { name })) => {
extra.insert("up_to".into(), json!(name));
}
Some(solidcraft_engine::doc::ExtendTo::Plane(PlaneRef::Construction { name })) => {
extra.insert("up_to".into(), json!(name));
}
_ => {}
}
let k = Surf::Extend { distance: distance.clone() };
let k = Surf::Extend { distance: distance.to_string() };
let mut ins = inputs(&k);
if let Some(inp) = ins.first_mut() {
inp.items = edges.iter().filter_map(edge).collect();
@@ -743,9 +747,9 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
FeatureKind::SurfaceTrim { body, plane, tool, keep } => (
Surf::Trim,
vec![
vec![Sel::Body { name: body.clone() }],
vec![Sel::Body { name: body.to_string() }],
tool_sel(app, plane, tool.as_ref()).into_iter().collect(),
crate::dialogs::face_sel(s, *keep).into_iter().collect(),
crate::dialogs::face_sel(s, keep.0).into_iter().collect(),
],
),
FeatureKind::SurfaceUntrim { face, holes, outer, in_place } => {
@@ -756,12 +760,12 @@ pub fn for_feature(app: &SolveApp, kind: &FeatureKind, frame: &Mat) -> Option<(K
};
(
Surf::Untrim { holes: index, outer: *outer, in_place: *in_place },
vec![crate::dialogs::face_sel(s, face.point).into_iter().collect(), picked],
vec![crate::dialogs::face_sel(s, face.point.0).into_iter().collect(), picked],
)
}
FeatureKind::SplitFace { faces, plane, tool, .. } => (
Surf::SplitFace,
vec![faces.iter().filter_map(|p| crate::dialogs::face_sel(s, *p)).collect(), tool_sel(app, plane, tool.as_ref()).into_iter().collect()],
vec![faces.iter().filter_map(|p| crate::dialogs::face_sel(s, p.0)).collect(), tool_sel(app, plane, tool.as_ref()).into_iter().collect()],
),
_ => return None,
};
+5 -5
View File
@@ -3,7 +3,7 @@
use serde_json::{Value, json};
use solidcraft_engine::Sel;
use solidcraft_engine::doc::{Mat, SweepOrientation};
use solidcraft_engine::doc::{Expr, Mat, SweepOrientation};
use solidcraft_engine::geom::Vec3;
use crate::SolveApp;
@@ -40,10 +40,10 @@ impl SweepOpts {
/// A feature's stored options as dialog values; `frame` maps the feature's component frame to
/// the world (a stored direction is in the component's frame). A direction along an origin
/// axis comes back as a pick of that axis.
pub fn of(orientation: &SweepOrientation, twist: &Option<String>, scale: &Option<String>, frame: &Mat) -> (SweepOpts, Option<Sel>) {
pub fn of(orientation: &SweepOrientation, twist: &Option<Expr>, scale: &Option<Expr>, frame: &Mat) -> (SweepOpts, Option<Sel>) {
let mut o = SweepOpts {
twist: twist.clone().unwrap_or_else(|| NO_TWIST.into()),
scale: scale.clone().unwrap_or_else(|| NO_SCALE.into()),
twist: twist.as_ref().map_or_else(|| NO_TWIST.into(), |t| t.to_string()),
scale: scale.as_ref().map_or_else(|| NO_SCALE.into(), |k| k.to_string()),
..SweepOpts::default()
};
let mut pick = None;
@@ -52,7 +52,7 @@ impl SweepOpts {
SweepOrientation::Parallel => o.orientation = 1,
SweepOrientation::Direction(d) => {
o.orientation = DIRECTION;
let world = solidcraft_engine::doc::apply_vector(frame, *d);
let world = solidcraft_engine::doc::apply_vector(frame, d.0);
match world.normalized().and_then(world_axis) {
Some(name) => pick = Some(Sel::Axis { name: name.into() }),
None => o.direction = Some(world),
@@ -25,7 +25,7 @@ fn arrow(h: &Harness) -> (Vec3, Vec3) {
fn last_hole_direction(h: &Harness) -> Vec3 {
match &h.app.session.doc.features.last().unwrap().kind {
FeatureKind::Hole { direction, .. } => *direction,
FeatureKind::Hole { direction, .. } => direction.0,
other => panic!("the last feature is not a hole: {}", other.type_name()),
}
}
@@ -77,7 +77,7 @@ fn hovered_face_point(h: &mut Harness, at: [f64; 2]) -> Vec3 {
fn hole(h: &Harness) -> (u64, Vec3, bool) {
let f = h.app.session.doc.features.last().unwrap();
match &f.kind {
FeatureKind::Hole { position, points, .. } => (f.id, *position, points.is_some()),
FeatureKind::Hole { position, points, .. } => (f.id, position.0, points.is_some()),
other => panic!("the last feature is not a hole: {}", other.type_name()),
}
}
+4 -4
View File
@@ -156,10 +156,10 @@ fn a_centimetre_design_shows_centimetres() {
fn stored(h: &Harness, id: u64) -> Vec<String> {
use solidcraft_engine::doc::FeatureKind;
match h.app.session.doc.feature(id).map(|f| &f.kind) {
Some(FeatureKind::Box { length, width, height, .. }) => vec![length.clone(), width.clone(), height.clone()],
Some(FeatureKind::Cylinder { radius, height, .. }) => vec![radius.clone(), height.clone()],
Some(FeatureKind::Sphere { radius, .. }) => vec![radius.clone()],
Some(FeatureKind::Torus { major, minor, .. }) => vec![major.clone(), minor.clone()],
Some(FeatureKind::Box { length, width, height, .. }) => vec![length.to_string(), width.to_string(), height.to_string()],
Some(FeatureKind::Cylinder { radius, height, .. }) => vec![radius.to_string(), height.to_string()],
Some(FeatureKind::Sphere { radius, .. }) => vec![radius.to_string()],
Some(FeatureKind::Torus { major, minor, .. }) => vec![major.to_string(), minor.to_string()],
other => panic!("not a primitive: {other:?}"),
}
}
@@ -33,7 +33,7 @@ fn volume(h: &Harness) -> f64 {
}
fn angle(h: &Harness) -> String {
let FeatureKind::Revolve { angle, .. } = &h.app.session.doc.find_feature("Turn").unwrap().kind else { panic!("revolve") };
angle.clone()
angle.to_string()
}
fn expected_volume(degrees: f64) -> f64 {
0.5 * (225.0 - 100.0) * 5.0 * degrees.to_radians()
+3 -3
View File
@@ -39,9 +39,9 @@ fn a_new_sweep_takes_its_options_from_the_dialog() {
assert!(h.app.dialog.is_none(), "{:?}", h.app.session.log);
let f = h.app.session.doc.features.last().unwrap();
let FeatureKind::Sweep { orientation, twist, scale, .. } = &f.kind else { panic!("{:?}", f.kind) };
assert_eq!(*orientation, SweepOrientation::Direction(Vec3::X));
assert_eq!(twist.as_deref(), Some("90 deg"));
assert_eq!(scale.as_deref(), Some("0.5"));
assert_eq!(*orientation, SweepOrientation::Direction(solidcraft_engine::doc::Dir(Vec3::X)));
assert_eq!(twist.as_ref().map(|t| t.as_str()), Some("90 deg"));
assert_eq!(scale.as_ref().map(|k| k.as_str()), Some("0.5"));
}
#[test]
+1 -1
View File
@@ -333,7 +333,7 @@ pub fn timeline(app: &mut SolveApp, ui: &mut egui::Ui) {
if let Some(sk) = sketch_of
&& ui.button(crate::i18n::tr_label("Edit Profile Sketch")).clicked()
{
app.edit_sketch(sk);
app.edit_sketch(sk.0);
ui.close();
}
if ui.button(crate::i18n::tr_label("Rename")).clicked() {
+1 -1
View File
@@ -774,7 +774,7 @@ fn vertical_features(app: &mut SolveApp, ui: &mut egui::Ui) {
if let Some(sketch) = sketch
&& ui.button("Edit Profile Sketch").clicked()
{
app.edit_sketch(sketch);
app.edit_sketch(sketch.0);
ui.close();
}
if ui.button("Rename Feature").clicked() {