Sketch Offset on two sides, or symmetric about the source (#807)
Sketch Offset can place the copy on both sides of the source, or symmetric about it.
This commit is contained in:
1 parent
5d661fa2af
commit
53238fba5f
6 files changed
+567
-144
No files matched your search
@@ -53,9 +53,10 @@ pub fn anchor(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Option<Vec3>)> {
|
||||
let (mid, tangent) = poly_mid(&pts)?;
|
||||
// An offset's drag arrow: the in-plane direction the distance grows along. The
|
||||
// command offsets a picked curve to its left, and the window's Flip row reverses it.
|
||||
// Symmetric hides Flip and offsets both ways, so the arrow keeps the left side.
|
||||
let dir = match &d.kind {
|
||||
Kind::SketchModify(crate::sketch_modify_ui::Mod::Offset { flip, .. }) => {
|
||||
let n = if *flip { -tangent.perp() } else { tangent.perp() };
|
||||
Kind::SketchModify(crate::sketch_modify_ui::Mod::Offset { flip, mode, .. }) => {
|
||||
let n = if *flip && !crate::sketch_modify_ui::offset_symmetric(*mode) { -tangent.perp() } else { tangent.perp() };
|
||||
Some(ss.plane.to_world(mid + n) - ss.plane.to_world(mid))
|
||||
}
|
||||
_ => None,
|
||||
|
||||
@@ -937,6 +937,23 @@ pub fn paint(p: &Painter, r: Rect, name: &str, ink: Color32, fill: Color32, acce
|
||||
pen.arc(8.0, 15.0, 5.0, std::f32::consts::PI, std::f32::consts::PI * 1.5, a);
|
||||
pen.arc(8.0, 9.0, 5.0, std::f32::consts::FRAC_PI_2, std::f32::consts::PI, a);
|
||||
}
|
||||
// Offset mode icons, drawn with the house `dashed` helper. The source is the solid
|
||||
// line and each result a dashed one. One Side has a single result; Two Sides has one
|
||||
// each way at two different gaps; Symmetric has one each way at the same gap.
|
||||
"offset_one_side" => {
|
||||
pen.line(&[(8.0, 4.0), (8.0, 20.0)], ink);
|
||||
pen.dashed(&[(18.0, 4.0), (18.0, 20.0)], a);
|
||||
}
|
||||
"offset_two_sides" => {
|
||||
pen.line(&[(13.0, 4.0), (13.0, 20.0)], ink);
|
||||
pen.dashed(&[(3.0, 4.0), (3.0, 20.0)], a);
|
||||
pen.dashed(&[(20.0, 4.0), (20.0, 20.0)], a);
|
||||
}
|
||||
"offset_symmetric" => {
|
||||
pen.line(&[(12.0, 4.0), (12.0, 20.0)], ink);
|
||||
pen.dashed(&[(4.0, 4.0), (4.0, 20.0)], a);
|
||||
pen.dashed(&[(20.0, 4.0), (20.0, 20.0)], a);
|
||||
}
|
||||
"trim" => {
|
||||
pen.line(&[(12.0, 3.0), (12.0, 21.0)], ink);
|
||||
pen.line(&[(3.0, 12.0), (12.0, 12.0)], ink);
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
//! reached no tool and no dialog at all, so a press ran `sketch.scale` with no parameters and
|
||||
//! the engine answered "`factor` must be a positive number". Offset joins it here (upstream
|
||||
//! issue #162 item 1: "Offset does not create a constraint, the size of the offset seems
|
||||
//! random"). Mirror joins them here, which ran as a bare click flow: entities until a
|
||||
//! random"), and it now offers the same One Side, Two Sides and Symmetric directions
|
||||
//! `solid.extrude` already names. Mirror joins them here, which ran as a bare click flow: entities until a
|
||||
//! right-click, then one more click for the line, with nothing on screen saying what was
|
||||
//! picked. Move joins them too, which had no toolbar placement, no tool and no dialog: its only
|
||||
//! route into the UI was the sketch context menu, and that went through the same
|
||||
@@ -39,6 +40,21 @@ const CHAMFER_TYPE_ICONS: [&str; 3] = ["sketch_chamfer_equal", "sketch_chamfer_t
|
||||
/// The engine's own cap on the copies one sketch pattern may make.
|
||||
const MAX_COPIES: f64 = 500.0;
|
||||
|
||||
/// The offset directions, in the words and the order `solid.extrude` already uses for them.
|
||||
/// One Side offsets the way the distance points. Two Sides adds an independent distance the
|
||||
/// other way. Symmetric applies the one distance each way.
|
||||
pub(crate) const OFFSET_MODES: [&str; 3] = ["One Side", "Two Sides", "Symmetric"];
|
||||
const OFFSET_MODE_ICONS: [&str; 3] = ["offset_one_side", "offset_two_sides", "offset_symmetric"];
|
||||
/// Index of Two Sides in [`OFFSET_MODES`] (the mode with a second distance).
|
||||
const TWO_SIDES: usize = 1;
|
||||
/// Index of Symmetric in [`OFFSET_MODES`].
|
||||
const SYMMETRIC: usize = 2;
|
||||
|
||||
/// Whether an offset mode index is Symmetric (which hides Flip and offsets both ways).
|
||||
pub(crate) fn offset_symmetric(mode: usize) -> bool {
|
||||
mode == SYMMETRIC
|
||||
}
|
||||
|
||||
/// A sketch modify tool window's values.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Mod {
|
||||
@@ -48,8 +64,9 @@ pub enum Mod {
|
||||
Scale { factor: String },
|
||||
/// `sketch.offset`: the curves to offset, the distance typed and whether the connected
|
||||
/// chain follows. A positive distance is left of the chain, as the command defines it;
|
||||
/// `flip` reverses whatever sign the field holds.
|
||||
Offset { distance: String, chain: bool, flip: bool },
|
||||
/// `flip` reverses whatever sign the field holds. `mode` indexes [`OFFSET_MODES`], and
|
||||
/// `distance2` is the other side's distance that Two Sides asks for.
|
||||
Offset { distance: String, distance2: String, mode: usize, chain: bool, flip: bool },
|
||||
/// `sketch.mirror`: the entities and the line they reflect in. The command has no number of
|
||||
/// its own, so the window's whole job is to show what is picked and let a pick be changed
|
||||
/// before anything is committed.
|
||||
@@ -126,7 +143,7 @@ pub fn start(_app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
|
||||
};
|
||||
let k = match id {
|
||||
"sketch.scale" => Mod::Scale { factor: "2".into() },
|
||||
"sketch.offset" => Mod::Offset { distance: "2 mm".into(), chain: true, flip: false },
|
||||
"sketch.offset" => Mod::Offset { distance: "2 mm".into(), distance2: "2 mm".into(), mode: 0, chain: true, flip: false },
|
||||
"sketch.mirror" => Mod::Mirror,
|
||||
"sketch.move" => Mod::Move { x: "0 mm".into(), y: "0 mm".into(), angle: "0 deg".into(), copy: false },
|
||||
"sketch.pattern.rectangular" => Mod::PatternRect { count: "3".into(), spacing: "20 mm".into(), count2: "1".into(), spacing2: "20 mm".into() },
|
||||
@@ -149,13 +166,24 @@ pub fn rows(ui: &mut egui::Ui, k: &mut Mod) -> bool {
|
||||
enter |= field(ui, factor);
|
||||
ui.end_row();
|
||||
}
|
||||
Mod::Offset { distance, chain, flip } => {
|
||||
Mod::Offset { distance, distance2, mode, chain, flip } => {
|
||||
row_label(ui, "Direction");
|
||||
icon_choice(ui, &OFFSET_MODES, &OFFSET_MODE_ICONS, mode);
|
||||
ui.end_row();
|
||||
row_label(ui, "Distance");
|
||||
enter |= field(ui, distance);
|
||||
ui.end_row();
|
||||
row_label(ui, "Flip");
|
||||
check(ui, flip);
|
||||
ui.end_row();
|
||||
if *mode == TWO_SIDES {
|
||||
row_label(ui, "Distance 2");
|
||||
enter |= field(ui, distance2);
|
||||
ui.end_row();
|
||||
}
|
||||
// Symmetric offsets both sides, so there is no side left for Flip to swap.
|
||||
if *mode != SYMMETRIC {
|
||||
row_label(ui, "Flip");
|
||||
check(ui, flip);
|
||||
ui.end_row();
|
||||
}
|
||||
row_label(ui, "Chain");
|
||||
check(ui, chain);
|
||||
ui.end_row();
|
||||
@@ -330,11 +358,11 @@ fn value(app: &SolveApp, text: &str, kind: ValueKind, label: &str) -> Result<f64
|
||||
/// A typed distance as the command wants it: the field evaluated against the document's
|
||||
/// parameters, with the sign reversed when Flip is set. A bare number means the design's units,
|
||||
/// as a typed sketch value does everywhere else.
|
||||
fn signed_length(app: &SolveApp, text: &str, flip: bool) -> Result<f64, String> {
|
||||
fn signed_length(app: &SolveApp, label: &str, text: &str, flip: bool) -> Result<f64, String> {
|
||||
let doc = &app.session.doc;
|
||||
let v = doc.eval(&doc.with_design_unit(text.trim(), ValueKind::Length), ValueKind::Length).map_err(|e| format!("Distance: {e}"))?;
|
||||
let v = doc.eval(&doc.with_design_unit(text.trim(), ValueKind::Length), ValueKind::Length).map_err(|e| format!("{label}: {e}"))?;
|
||||
if !v.is_finite() || v.abs() <= 1e-9 {
|
||||
return Err("Distance must be a non-zero number".into());
|
||||
return Err(format!("{label} must be a non-zero number"));
|
||||
}
|
||||
Ok(if flip { -v } else { v })
|
||||
}
|
||||
@@ -430,13 +458,23 @@ pub fn commands(app: &SolveApp, k: &Mod, inputs: &[SelInput], extra: &Map<String
|
||||
}
|
||||
("sketch.scale", p)
|
||||
}
|
||||
Mod::Offset { distance, chain, flip } => {
|
||||
Mod::Offset { distance, distance2, mode, chain, flip } => {
|
||||
let curves = curve_ids(inputs, 0);
|
||||
if curves.is_empty() {
|
||||
return Err("Select the curves to offset first".into());
|
||||
}
|
||||
let d = signed_length(app, distance, *flip)?;
|
||||
("sketch.offset", json!({"curves": curves, "distance": d, "chain": chain}))
|
||||
// Symmetric hides Flip, so a flip left over from another mode does not travel with it.
|
||||
let d = signed_length(app, "Distance", distance, *flip && *mode != SYMMETRIC)?;
|
||||
let mut params = json!({"curves": curves, "distance": d, "chain": chain});
|
||||
if let Value::Object(m) = &mut params {
|
||||
if *mode == SYMMETRIC {
|
||||
m.insert("symmetric".into(), json!(true));
|
||||
}
|
||||
if *mode == TWO_SIDES {
|
||||
m.insert("distance2".into(), json!(signed_length(app, "Distance 2", distance2, false)?.abs()));
|
||||
}
|
||||
}
|
||||
("sketch.offset", params)
|
||||
}
|
||||
Mod::Mirror => {
|
||||
let entities = entity_ids(inputs, 0);
|
||||
|
||||
Reference in new issue
Block a user