mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-26 20:06:50 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d156dd0c24 |
@@ -963,6 +963,57 @@ impl AreaM {
|
|||||||
AreaMode::Subtract => self.total - v,
|
AreaMode::Subtract => self.total - v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Input while a height is pending: typed, or given by two points (their distance).
|
||||||
|
fn height_input(&mut self, s: &mut Session, i: Input) -> Step {
|
||||||
|
match (i, self.height_from) {
|
||||||
|
(Input::Point(p), None) => self.height_from = Some(p),
|
||||||
|
(Input::Point(p), Some(from)) => {
|
||||||
|
self.height_from = None;
|
||||||
|
return self.height(s, p.dist(from));
|
||||||
|
}
|
||||||
|
(Input::Text(t), None) => match crate::units::parse_distance(&t).filter(|v| v.is_finite()) {
|
||||||
|
Some(h) => return self.height(s, h),
|
||||||
|
None => s.echo("Requires a distance."),
|
||||||
|
},
|
||||||
|
(Input::Enter, None) => s.echo("Requires a height."),
|
||||||
|
(Input::Enter, Some(_)) => s.echo("Requires a second point."),
|
||||||
|
(Input::Text(_), Some(_)) => s.echo("Requires a point."),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Step::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input while the objects to measure are picked.
|
||||||
|
fn object_input(&mut self, s: &mut Session, i: Input) -> Step {
|
||||||
|
match i {
|
||||||
|
Input::Pick(hs) => {
|
||||||
|
for h in hs {
|
||||||
|
match object_area(s, h) {
|
||||||
|
Ok((a, per)) => {
|
||||||
|
if self.measured(s, a, per) == Step::Done {
|
||||||
|
return Step::Done;
|
||||||
|
}
|
||||||
|
// A single volume takes the first object with an area.
|
||||||
|
if self.volume && self.mode == AreaMode::Single {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => s.echo("The selected object has no area."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Input::Enter => {
|
||||||
|
self.object = false;
|
||||||
|
if self.mode == AreaMode::Single {
|
||||||
|
return Step::Done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Input::Text(_) | Input::Keyword(_) => s.echo("*Invalid selection*"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Step::Continue
|
||||||
|
}
|
||||||
|
|
||||||
/// Measure the oldest pending area's volume with height `h`.
|
/// Measure the oldest pending area's volume with height `h`.
|
||||||
fn height(&mut self, s: &mut Session, h: f64) -> Step {
|
fn height(&mut self, s: &mut Session, h: f64) -> Step {
|
||||||
match self.pending.first().map(|a| a * h.abs()) {
|
match self.pending.first().map(|a| a * h.abs()) {
|
||||||
@@ -1042,52 +1093,10 @@ impl Interactive for AreaM {
|
|||||||
}
|
}
|
||||||
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
|
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
|
||||||
if !self.pending.is_empty() {
|
if !self.pending.is_empty() {
|
||||||
// A height is typed, or given by two points (their distance).
|
return Ok(self.height_input(s, i));
|
||||||
match (i, self.height_from) {
|
|
||||||
(Input::Point(p), None) => self.height_from = Some(p),
|
|
||||||
(Input::Point(p), Some(from)) => {
|
|
||||||
self.height_from = None;
|
|
||||||
return Ok(self.height(s, p.dist(from)));
|
|
||||||
}
|
|
||||||
(Input::Text(t), None) => match crate::units::parse_distance(&t).filter(|v| v.is_finite()) {
|
|
||||||
Some(h) => return Ok(self.height(s, h)),
|
|
||||||
None => s.echo("Requires a distance."),
|
|
||||||
},
|
|
||||||
(Input::Enter, None) => s.echo("Requires a height."),
|
|
||||||
(Input::Enter, Some(_)) => s.echo("Requires a second point."),
|
|
||||||
(Input::Text(_), Some(_)) => s.echo("Requires a point."),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
return Ok(Step::Continue);
|
|
||||||
}
|
}
|
||||||
if self.object {
|
if self.object {
|
||||||
match i {
|
return Ok(self.object_input(s, i));
|
||||||
Input::Pick(hs) => {
|
|
||||||
for h in hs {
|
|
||||||
match object_area(s, h) {
|
|
||||||
Ok((a, per)) => {
|
|
||||||
if self.measured(s, a, per) == Step::Done {
|
|
||||||
return Ok(Step::Done);
|
|
||||||
}
|
|
||||||
// A single volume takes the first object with an area.
|
|
||||||
if self.volume && self.mode == AreaMode::Single {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => s.echo("The selected object has no area."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Input::Enter => {
|
|
||||||
self.object = false;
|
|
||||||
if self.mode == AreaMode::Single {
|
|
||||||
return Ok(Step::Done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Input::Text(_) | Input::Keyword(_) => s.echo("*Invalid selection*"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
return Ok(Step::Continue);
|
|
||||||
}
|
}
|
||||||
if self.length {
|
if self.length {
|
||||||
if let Some(p) = length_input(&self.pts, i)? {
|
if let Some(p) = length_input(&self.pts, i)? {
|
||||||
|
|||||||
@@ -1296,33 +1296,7 @@ fn run_chamfer(s: &mut Session, p: &Value) -> Result<Value> {
|
|||||||
|
|
||||||
// ---------------- explode / array / order / break / join ----------------
|
// ---------------- explode / array / order / break / join ----------------
|
||||||
|
|
||||||
pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<Entity>> {
|
fn explode_block_ref(d: &cadcraft_doc::Drawing, e: &Entity, ins: &cadcraft_doc::Insert) -> Option<Vec<Entity>> {
|
||||||
// An annotative object explodes into plain objects as it draws at the current annotation scale.
|
|
||||||
if cadcraft_doc::is_annotative(e) {
|
|
||||||
let mut se = match d.anno_draw(e) {
|
|
||||||
cadcraft_doc::AnnoDraw::Scaled(f) => cadcraft_doc::anno_scaled(e, f),
|
|
||||||
_ => e.clone(),
|
|
||||||
};
|
|
||||||
se.common.anno_scales.clear();
|
|
||||||
return explode_kind(d, &se);
|
|
||||||
}
|
|
||||||
match &e.kind {
|
|
||||||
EntityKind::LwPolyline(p) => {
|
|
||||||
let pl = Polyline { vertices: p.vertices.clone(), closed: p.closed };
|
|
||||||
Some(
|
|
||||||
pl.segments()
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| {
|
|
||||||
let k = match s {
|
|
||||||
Segment::Line(l) => line(l.a, l.b),
|
|
||||||
Segment::Arc { arc: a, .. } => arc(&a),
|
|
||||||
};
|
|
||||||
Entity { handle: Handle(0), common: e.common.clone(), kind: k }
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
EntityKind::Insert(ins) => {
|
|
||||||
if let Some(items) = super::array::explode_array(d, e) {
|
if let Some(items) = super::array::explode_array(d, e) {
|
||||||
return Some(items);
|
return Some(items);
|
||||||
}
|
}
|
||||||
@@ -1345,19 +1319,8 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
|
|||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// A helix explodes into its spline (seen from above).
|
|
||||||
EntityKind::Helix(h) => Some(vec![Entity { handle: Handle(0), common: e.common.clone(), kind: EntityKind::Spline(h.spline()) }]),
|
fn explode_mtext(e: &Entity, m: &cadcraft_doc::MText) -> Vec<Entity> {
|
||||||
EntityKind::Polyline3d(p) => Some(
|
|
||||||
p.points
|
|
||||||
.windows(2)
|
|
||||||
.filter_map(|w| {
|
|
||||||
let kind = EntityKind::Line(cadcraft_doc::Line { a: *w.first()?, b: *w.get(1)? });
|
|
||||||
Some(Entity { handle: Handle(0), common: e.common.clone(), kind })
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
EntityKind::MLine(m) => Some(super::draw2::mline::explode(e, m)),
|
|
||||||
EntityKind::MText(m) => Some(
|
|
||||||
cadcraft_fonts::plain_mtext(&m.contents)
|
cadcraft_fonts::plain_mtext(&m.contents)
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -1380,12 +1343,12 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect()
|
||||||
),
|
}
|
||||||
// A region explodes into its boundary's lines and arcs.
|
|
||||||
EntityKind::Region(r) => Some(
|
/// The entities of a boundary's lines and arcs, on the object's layer and style.
|
||||||
r.segments()
|
fn segment_entities(e: &Entity, segs: impl IntoIterator<Item = Segment>) -> Vec<Entity> {
|
||||||
.into_iter()
|
segs.into_iter()
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
let k = match s {
|
let k = match s {
|
||||||
Segment::Line(l) => line(l.a, l.b),
|
Segment::Line(l) => line(l.a, l.b),
|
||||||
@@ -1393,9 +1356,10 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
|
|||||||
};
|
};
|
||||||
Entity { handle: Handle(0), common: e.common.clone(), kind: k }
|
Entity { handle: Handle(0), common: e.common.clone(), kind: k }
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect()
|
||||||
),
|
}
|
||||||
EntityKind::Dimension(dm) => {
|
|
||||||
|
fn explode_dimension(d: &cadcraft_doc::Drawing, e: &Entity, dm: &cadcraft_doc::Dimension) -> Vec<Entity> {
|
||||||
// The dimension as drawn: its overrides, DIMSCALE (0 = the drawing's) and text font.
|
// The dimension as drawn: its overrides, DIMSCALE (0 = the drawing's) and text font.
|
||||||
let style = d.dim_style(&dm.style).cloned().unwrap_or_default().with_overrides(&dm.overrides);
|
let style = d.dim_style(&dm.style).cloned().unwrap_or_default().with_overrides(&dm.overrides);
|
||||||
let g = cadcraft_render::dimension_in(d, dm);
|
let g = cadcraft_render::dimension_in(d, dm);
|
||||||
@@ -1451,8 +1415,41 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(v)
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<Entity>> {
|
||||||
|
// An annotative object explodes into plain objects as it draws at the current annotation scale.
|
||||||
|
if cadcraft_doc::is_annotative(e) {
|
||||||
|
let mut se = match d.anno_draw(e) {
|
||||||
|
cadcraft_doc::AnnoDraw::Scaled(f) => cadcraft_doc::anno_scaled(e, f),
|
||||||
|
_ => e.clone(),
|
||||||
|
};
|
||||||
|
se.common.anno_scales.clear();
|
||||||
|
return explode_kind(d, &se);
|
||||||
|
}
|
||||||
|
match &e.kind {
|
||||||
|
EntityKind::LwPolyline(p) => {
|
||||||
|
let pl = Polyline { vertices: p.vertices.clone(), closed: p.closed };
|
||||||
|
Some(segment_entities(e, pl.segments()))
|
||||||
|
}
|
||||||
|
EntityKind::Insert(ins) => explode_block_ref(d, e, ins),
|
||||||
|
// A helix explodes into its spline (seen from above).
|
||||||
|
EntityKind::Helix(h) => Some(vec![Entity { handle: Handle(0), common: e.common.clone(), kind: EntityKind::Spline(h.spline()) }]),
|
||||||
|
EntityKind::Polyline3d(p) => Some(
|
||||||
|
p.points
|
||||||
|
.windows(2)
|
||||||
|
.filter_map(|w| {
|
||||||
|
let kind = EntityKind::Line(cadcraft_doc::Line { a: *w.first()?, b: *w.get(1)? });
|
||||||
|
Some(Entity { handle: Handle(0), common: e.common.clone(), kind })
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
EntityKind::MLine(m) => Some(super::draw2::mline::explode(e, m)),
|
||||||
|
EntityKind::MText(m) => Some(explode_mtext(e, m)),
|
||||||
|
// A region explodes into its boundary's lines and arcs.
|
||||||
|
EntityKind::Region(r) => Some(segment_entities(e, r.segments())),
|
||||||
|
EntityKind::Dimension(dm) => Some(explode_dimension(d, e, dm)),
|
||||||
EntityKind::Tolerance(t) => Some(super::tolerance::explode(d, e, t)),
|
EntityKind::Tolerance(t) => Some(super::tolerance::explode(d, e, t)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-26
@@ -465,6 +465,26 @@ impl Family<'_> {
|
|||||||
self.origin + p.rotate(self.ang)
|
self.origin + p.rotate(self.ang)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The range of the lines swept, in this frame: the boundary's extent, cut to the view's
|
||||||
|
/// when there is one, and the view's x extent that spans are cut to. Spans are cut to the
|
||||||
|
/// view's extent only: they already lie within the boundary, and cutting them to its extent
|
||||||
|
/// too would move an end that sits on a boundary corner by a rounding error, so a view
|
||||||
|
/// covering the hatch would change its lines. `None` when the view misses the boundary.
|
||||||
|
fn lines_range(&self, bb: &Bounds2, view: Option<&Bounds2>) -> Option<(Vec2, Vec2, Option<(f64, f64)>)> {
|
||||||
|
let (mut lo, mut hi) = (bb.min, bb.max);
|
||||||
|
let mut span_cut = None;
|
||||||
|
if let Some(v) = view {
|
||||||
|
let vb = Bounds2::from_points(v.corners().iter().map(|p| self.to_local(*p)));
|
||||||
|
if vb.is_empty() || !vb.intersects(bb) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
lo = Vec2::new(lo.x.max(vb.min.x), lo.y.max(vb.min.y));
|
||||||
|
hi = Vec2::new(hi.x.min(vb.max.x), hi.y.min(vb.max.y));
|
||||||
|
span_cut = Some((vb.min.x, vb.max.x));
|
||||||
|
}
|
||||||
|
Some((lo, hi, span_cut))
|
||||||
|
}
|
||||||
|
|
||||||
/// Intersect a pattern family with boundary loops, respecting view, output, cancellation, and shared dash-work limits.
|
/// Intersect a pattern family with boundary loops, respecting view, output, cancellation, and shared dash-work limits.
|
||||||
fn sweep(&self, loops: &[Vec<Vec2>], px: f64, view: Option<&Bounds2>, cancel: Option<&AtomicBool>, out: &mut Vec<Vec<Vec2>>, work: &mut u64) {
|
fn sweep(&self, loops: &[Vec<Vec2>], px: f64, view: Option<&Bounds2>, cancel: Option<&AtomicBool>, out: &mut Vec<Vec<Vec2>>, work: &mut u64) {
|
||||||
let local: Vec<Vec<Vec2>> = loops.iter().map(|l| l.iter().map(|p| self.to_local(*p)).collect()).collect();
|
let local: Vec<Vec<Vec2>> = loops.iter().map(|l| l.iter().map(|p| self.to_local(*p)).collect()).collect();
|
||||||
@@ -472,21 +492,7 @@ impl Family<'_> {
|
|||||||
if bb.is_empty() {
|
if bb.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The lines swept: the boundary's extent, cut to the view's (its corners in this frame)
|
let Some((lo, hi, span_cut)) = self.lines_range(&bb, view) else { return };
|
||||||
// when there is one. Spans are cut to the view's extent only: they already lie within the
|
|
||||||
// boundary, and cutting them to its extent too would move an end that sits on a boundary
|
|
||||||
// corner by a rounding error, so a view covering the hatch would change its lines.
|
|
||||||
let (mut lo, mut hi) = (bb.min, bb.max);
|
|
||||||
let mut span_cut = None;
|
|
||||||
if let Some(v) = view {
|
|
||||||
let vb = Bounds2::from_points(v.corners().iter().map(|p| self.to_local(*p)));
|
|
||||||
if vb.is_empty() || !vb.intersects(&bb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lo = Vec2::new(lo.x.max(vb.min.x), lo.y.max(vb.min.y));
|
|
||||||
hi = Vec2::new(hi.x.min(vb.max.x), hi.y.min(vb.max.y));
|
|
||||||
span_cut = Some((vb.min.x, vb.max.x));
|
|
||||||
}
|
|
||||||
let shift_per_line = self.delta.rotate(-self.ang).x;
|
let shift_per_line = self.delta.rotate(-self.ang).x;
|
||||||
let k0 = (lo.y / self.spacing).floor() as i64;
|
let k0 = (lo.y / self.spacing).floor() as i64;
|
||||||
let k1 = (hi.y / self.spacing).ceil() as i64;
|
let k1 = (hi.y / self.spacing).ceil() as i64;
|
||||||
@@ -504,17 +510,7 @@ impl Family<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let y = k as f64 * self.spacing;
|
let y = k as f64 * self.spacing;
|
||||||
let mut xs: Vec<f64> = Vec::new();
|
let xs = crossings(&local, y);
|
||||||
for l in &local {
|
|
||||||
let n = l.len();
|
|
||||||
for i in 0..n {
|
|
||||||
let (Some(a), Some(b)) = (l.get(i), l.get((i + 1) % n)) else { continue };
|
|
||||||
if (a.y > y) != (b.y > y) {
|
|
||||||
xs.push(a.x + (b.x - a.x) * (y - a.y) / (b.y - a.y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xs.sort_by(f64::total_cmp);
|
|
||||||
let offset = shift_per_line * k as f64;
|
let offset = shift_per_line * k as f64;
|
||||||
for span in xs.chunks(2) {
|
for span in xs.chunks(2) {
|
||||||
let [x0, x1] = span else { continue };
|
let [x0, x1] = span else { continue };
|
||||||
@@ -565,6 +561,22 @@ impl Family<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The x positions where the boundary loops cross the horizontal line at `y`, in order.
|
||||||
|
fn crossings(local: &[Vec<Vec2>], y: f64) -> Vec<f64> {
|
||||||
|
let mut xs: Vec<f64> = Vec::new();
|
||||||
|
for l in local {
|
||||||
|
let n = l.len();
|
||||||
|
for i in 0..n {
|
||||||
|
let (Some(a), Some(b)) = (l.get(i), l.get((i + 1) % n)) else { continue };
|
||||||
|
if (a.y > y) != (b.y > y) {
|
||||||
|
xs.push(a.x + (b.x - a.x) * (y - a.y) / (b.y - a.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xs.sort_by(f64::total_cmp);
|
||||||
|
xs
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cadcraft_engine::doc::Handle;
|
use cadcraft_engine::cmd::file::compare::Comparison;
|
||||||
|
use cadcraft_engine::doc::{Drawing, Handle};
|
||||||
use cadcraft_render::DisplayList;
|
use cadcraft_render::DisplayList;
|
||||||
use egui::{Rect, RichText, Stroke, vec2};
|
use egui::{Rect, RichText, Stroke, vec2};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -29,6 +30,23 @@ fn id() -> egui::Id {
|
|||||||
egui::Id::unique("cadcraft-compare-overlay")
|
egui::Id::unique("cadcraft-compare-overlay")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The display lists of the differences for this frame: the stored ones when they still match
|
||||||
|
/// `key`, otherwise rebuilt at zoom `scale` and stored.
|
||||||
|
fn cache_for(ctx: &egui::Context, key: (u64, u64, usize, usize, i64), doc: &Drawing, c: &Comparison, scale: f64) -> Cache {
|
||||||
|
let mut cache: Cache = ctx.data_mut(|d| d.get_temp(id())).unwrap_or_default();
|
||||||
|
if cache.key != key || cache.ours.is_none() {
|
||||||
|
let opts = cadcraft_render::Options { tolerance: 0.5 / scale.max(1e-12), ..Default::default() };
|
||||||
|
let ours: Vec<Handle> = c.added.iter().copied().chain(c.changed.iter().map(|(a, _)| *a)).take(MAX_DRAWN).collect();
|
||||||
|
let theirs: Vec<Handle> = c.removed.iter().copied().chain(c.changed.iter().map(|(_, b)| *b)).take(MAX_DRAWN).collect();
|
||||||
|
let list = |d: &Drawing, hs: &[Handle]| {
|
||||||
|
Arc::new(cadcraft_render::build_entities(d, hs.iter().filter_map(|h| d.entity(*h)).map(|e| e.as_ref()), &opts))
|
||||||
|
};
|
||||||
|
cache = Cache { key, ours: Some(list(doc, &ours)), theirs: Some(list(&c.other, &theirs)) };
|
||||||
|
ctx.data_mut(|d| d.insert_temp(id(), cache.clone()));
|
||||||
|
}
|
||||||
|
cache
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the comparison over the canvas when it was made for the drawing and tab shown.
|
/// Draw the comparison over the canvas when it was made for the drawing and tab shown.
|
||||||
pub(super) fn paint(app: &mut CadApp, ctx: &egui::Context, p: &egui::Painter, xf: &Xf, rect: Rect) {
|
pub(super) fn paint(app: &mut CadApp, ctx: &egui::Context, p: &egui::Painter, xf: &Xf, rect: Rect) {
|
||||||
let t = Tokens::get();
|
let t = Tokens::get();
|
||||||
@@ -38,17 +56,7 @@ pub(super) fn paint(app: &mut CadApp, ctx: &egui::Context, p: &egui::Painter, xf
|
|||||||
p.rect_filled(rect, 0.0, t.canvas.gamma_multiply(0.75));
|
p.rect_filled(rect, 0.0, t.canvas.gamma_multiply(0.75));
|
||||||
let band = xf.scale.max(1e-300).log2().round() as i64;
|
let band = xf.scale.max(1e-300).log2().round() as i64;
|
||||||
let key = (st.uid, st.revision, Arc::as_ptr(&c.other) as usize, c.differences(), band);
|
let key = (st.uid, st.revision, Arc::as_ptr(&c.other) as usize, c.differences(), band);
|
||||||
let mut cache: Cache = ctx.data_mut(|d| d.get_temp(id())).unwrap_or_default();
|
let cache = cache_for(ctx, key, &st.doc, c, xf.scale);
|
||||||
if cache.key != key || cache.ours.is_none() {
|
|
||||||
let opts = cadcraft_render::Options { tolerance: 0.5 / xf.scale.max(1e-12), ..Default::default() };
|
|
||||||
let ours: Vec<Handle> = c.added.iter().copied().chain(c.changed.iter().map(|(a, _)| *a)).take(MAX_DRAWN).collect();
|
|
||||||
let theirs: Vec<Handle> = c.removed.iter().copied().chain(c.changed.iter().map(|(_, b)| *b)).take(MAX_DRAWN).collect();
|
|
||||||
let list = |d: &cadcraft_engine::doc::Drawing, hs: &[Handle]| {
|
|
||||||
Arc::new(cadcraft_render::build_entities(d, hs.iter().filter_map(|h| d.entity(*h)).map(|e| e.as_ref()), &opts))
|
|
||||||
};
|
|
||||||
cache = Cache { key, ours: Some(list(&st.doc, &ours)), theirs: Some(list(&c.other, &theirs)) };
|
|
||||||
ctx.data_mut(|d| d.insert_temp(id(), cache.clone()));
|
|
||||||
}
|
|
||||||
for (list, color) in [(&cache.theirs, t.compare_other), (&cache.ours, t.compare_this)] {
|
for (list, color) in [(&cache.theirs, t.compare_other), (&cache.ours, t.compare_this)] {
|
||||||
if let Some(l) = list {
|
if let Some(l) = list {
|
||||||
let all: Vec<usize> = (0..l.prims.len()).collect();
|
let all: Vec<usize> = (0..l.prims.len()).collect();
|
||||||
|
|||||||
@@ -217,6 +217,23 @@ fn display(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
|||||||
/// Files tab: save format and default version, new-drawing units, autosave interval and autosave file location; edits accumulate in `changes`.
|
/// Files tab: save format and default version, new-drawing units, autosave interval and autosave file location; edits accumulate in `changes`.
|
||||||
fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
||||||
grid(ui, "options_files", |ui| {
|
grid(ui, "options_files", |ui| {
|
||||||
|
save_format_row(ui, app, changes);
|
||||||
|
ui.end_row();
|
||||||
|
save_version_row(ui, app, changes);
|
||||||
|
ui.end_row();
|
||||||
|
units_row(ui, app, changes);
|
||||||
|
ui.end_row();
|
||||||
|
autosave_row(ui, changes);
|
||||||
|
ui.end_row();
|
||||||
|
savefile_row(ui, changes);
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
|
hint(ui, crate::tl!("New and the first drawing at startup use these units unless a default template is set (QNEWTEMPLATE)."));
|
||||||
|
hint(ui, crate::tl!("A drawing opened from a file keeps that file's format."));
|
||||||
|
hint(ui, crate::tl!("Drawings with no file yet are saved in the default version. 0 minutes turns automatic save off."));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_format_row(ui: &mut egui::Ui, app: &CadApp, changes: &mut Map<String, Value>) {
|
||||||
ui.label(crate::tl!("Save new drawings as"));
|
ui.label(crate::tl!("Save new drawings as"));
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
for f in crate::SaveFormat::ALL {
|
for f in crate::SaveFormat::ALL {
|
||||||
@@ -229,8 +246,9 @@ fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.end_row();
|
}
|
||||||
|
|
||||||
|
fn save_version_row(ui: &mut egui::Ui, app: &CadApp, changes: &mut Map<String, Value>) {
|
||||||
let version_caption = ui.label(crate::tl!("Default save version"));
|
let version_caption = ui.label(crate::tl!("Default save version"));
|
||||||
let current = crate::SaveVersion::parse(&app.ui.save_version).unwrap_or_default();
|
let current = crate::SaveVersion::parse(&app.ui.save_version).unwrap_or_default();
|
||||||
let mut version = current;
|
let mut version = current;
|
||||||
@@ -243,8 +261,9 @@ fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
|||||||
if version != current {
|
if version != current {
|
||||||
changes.insert("saveVersion".into(), json!(version.year()));
|
changes.insert("saveVersion".into(), json!(version.year()));
|
||||||
}
|
}
|
||||||
ui.end_row();
|
}
|
||||||
|
|
||||||
|
fn units_row(ui: &mut egui::Ui, app: &CadApp, changes: &mut Map<String, Value>) {
|
||||||
ui.label(crate::tl!("Units of new drawings"));
|
ui.label(crate::tl!("Units of new drawings"));
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let metric = app.session.settings.measureinit;
|
let metric = app.session.settings.measureinit;
|
||||||
@@ -255,20 +274,18 @@ fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
|||||||
changes.insert("MEASUREINIT".into(), json!(1));
|
changes.insert("MEASUREINIT".into(), json!(1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.end_row();
|
}
|
||||||
|
|
||||||
|
fn autosave_row(ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
||||||
let minutes_caption = ui.label(crate::tl!("Automatic save every"));
|
let minutes_caption = ui.label(crate::tl!("Automatic save every"));
|
||||||
let mut minutes = cadcraft_engine::autosave::savetime();
|
let mut minutes = cadcraft_engine::autosave::savetime();
|
||||||
let max = cadcraft_engine::autosave::MAX_SAVETIME;
|
let max = cadcraft_engine::autosave::MAX_SAVETIME;
|
||||||
if ui
|
if ui.add(egui::DragValue::new(&mut minutes).range(0..=max).suffix(format!(" {}", crate::tl!("min")))).labelled_by(minutes_caption.id).changed() {
|
||||||
.add(egui::DragValue::new(&mut minutes).range(0..=max).suffix(format!(" {}", crate::tl!("min"))))
|
|
||||||
.labelled_by(minutes_caption.id)
|
|
||||||
.changed()
|
|
||||||
{
|
|
||||||
changes.insert("SAVETIME".into(), json!(minutes));
|
changes.insert("SAVETIME".into(), json!(minutes));
|
||||||
}
|
}
|
||||||
ui.end_row();
|
}
|
||||||
|
|
||||||
|
fn savefile_row(ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
||||||
let savefile_label = ui.label(crate::tl!("Automatic save file location"));
|
let savefile_label = ui.label(crate::tl!("Automatic save file location"));
|
||||||
// Edited as text; the folder changes when the field loses focus.
|
// Edited as text; the folder changes when the field loses focus.
|
||||||
let id = egui::Id::unique("cc_options_savefilepath");
|
let id = egui::Id::unique("cc_options_savefilepath");
|
||||||
@@ -284,11 +301,6 @@ fn files(app: &CadApp, ui: &mut egui::Ui, changes: &mut Map<String, Value>) {
|
|||||||
changes.insert("SAVEFILEPATH".into(), json!(path.trim()));
|
changes.insert("SAVEFILEPATH".into(), json!(path.trim()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.end_row();
|
|
||||||
});
|
|
||||||
hint(ui, crate::tl!("New and the first drawing at startup use these units unless a default template is set (QNEWTEMPLATE)."));
|
|
||||||
hint(ui, crate::tl!("A drawing opened from a file keeps that file's format."));
|
|
||||||
hint(ui, crate::tl!("Drawings with no file yet are saved in the default version. 0 minutes turns automatic save off."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drafting tab: object-snap marker size and aperture previews plus the Drafting Settings entry; edits accumulate in `changes` and the entry may request a command through `start`.
|
/// Drafting tab: object-snap marker size and aperture previews plus the Drafting Settings entry; edits accumulate in `changes` and the entry may request a command through `start`.
|
||||||
|
|||||||
Reference in New Issue
Block a user