fix: open-issue sweep — drawing, plot, snap, layout and UI fixes

Fixes:
- Font prompt no longer lists bundled ltypeshp/qcadshp shape files (#1626)
- Wide polylines step at a discontinuous segment width; PEDIT marks the
  current vertex; keyword steps refresh the command preview (#1628)
- Radius-0 fillet drops the zero-length segment it left, so the corner
  fillets again (#1422)
- FILLET Trim / No trim option (#1558)
- True-colour wires plot in their own colour; colour picker stores the
  value it shows (#1621)
- Model tab keeps its page setup; "Save changes to layout" in Model (#1620)
- QNEW default template in Options > Files; .dwt in the open dialog
  (#1134, #1113)
- GRIDUNIT / GRIDMODE / PERSPECTIVE variables (#1618, #1524)
- PTYPE (#1433)
- ZOOM nX / nXP (#1465)
- CHSPACE between model and paper space through a viewport (#1428)
- Moving a dimension keeps its automatic text placement
- Shell icon; "&&" button labels (#1554)
- LINETYPE Load from a .lin file (#1588)
- XLINE / RAY hide the crosshair arms (#1437)
- Dynamic-input boxes keep clear of the crosshair and each other (#1546)
- Snap marker colour setting, dark default on light canvases
  (#1497, #1229, #1128)
- Dimension style precision as a 0.00 pattern dropdown (#1426)
- Node graph nodes left of the origin no longer clip away (#1592)
- Collapse button in the command history header (#1094)
- Freeze-in-current-viewport toggle in the ribbon layer list (#1466)
- Geometric Center object snap for closed polylines (#1369)

New strings translated in all 21 locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-06 15:59:30 +03:00
1 parent abf302eba5
commit 178c7dff41
70 files changed
+1227 -145

No files matched your search

+70 -1
View File
@@ -239,6 +239,12 @@ pub struct CrosshairOptions {
/// the crosshair arms disappear and a blue pickbox remains — the
/// screen-level "the client wants YOU to pick" signal.
pub pick_pending: bool,
/// Draw only the pickbox: the active command previews a line that the
/// full-length arms would cover.
pub hide_arms: bool,
/// Explicit object-snap marker colour; `None` picks one that reads on
/// the canvas.
pub snap_color: Option<[u8; 3]>,
}
/// Rendering style for the viewport grid.
@@ -1517,6 +1523,10 @@ impl canvas::Program<Message> for SelectionCanvas {
if let Some((sp, snap_type)) = self.snap {
let (r, g, b) = if snap_type == SnapType::ObjectPick {
(0.95_f32, 0.50, 0.08) // orange object-snap marker
} else if let Some([r, g, b]) = self.crosshair.snap_color {
(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)
} else if crate::ui::style::common::canvas_is_light(self.crosshair_bg) {
(0.80, 0.45, 0.0) // dark amber: yellow vanishes on a light canvas
} else {
(1.0, 0.9, 0.1) // classic yellow OSNAP
};
@@ -1569,6 +1579,25 @@ impl canvas::Program<Message> for SelectionCanvas {
let path = canvas::Path::circle(sp, r);
frame.stroke(&path, stroke);
}
SnapType::GeometricCenter => {
// Pentagon around a centre dot: the area centroid.
let r = 6.5_f32;
let path = canvas::Path::new(|b| {
for k in 0..5 {
let a = -std::f32::consts::FRAC_PI_2
+ k as f32 * std::f32::consts::TAU / 5.0;
let p = Point::new(sp.x + r * a.cos(), sp.y + r * a.sin());
if k == 0 {
b.move_to(p);
} else {
b.line_to(p);
}
}
b.close();
});
frame.stroke(&path, stroke.clone());
frame.fill(&canvas::Path::circle(sp, 1.6), marker);
}
SnapType::Node => {
// Circle with an inscribed X.
let r = 5.5_f32;
@@ -1915,7 +1944,7 @@ impl canvas::Program<Message> for SelectionCanvas {
// The arms stay only for the normal (non-pending) cursor: a
// waiting pick shows the box alone, regardless of the UCS
// rotation, so the square is unmistakable.
if !pick_pending {
if !pick_pending && !self.crosshair.hide_arms {
for angle in base_angles {
let rad = (angle + self.crosshair.snap_angle_deg as f64).to_radians();
let dir = Point::new(rad.cos() as f32, -rad.sin() as f32);
@@ -3363,6 +3392,37 @@ impl DynInputCanvas {
(Self::box_content(b).len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0
}
/// Slide a box centred at `center` straight away from the crosshair until
/// it neither covers the aim point nor a box already placed (#1546).
fn clear_of(center: Point, w: f32, cursor: Point, placed: &[iced::Rectangle]) -> Point {
const KEEP: f32 = 18.0;
let hits = |c: Point| {
let r = iced::Rectangle {
x: c.x - w * 0.5,
y: c.y - DYN_BOX_H * 0.5,
width: w,
height: DYN_BOX_H,
};
r.expand(KEEP).contains(cursor) || placed.iter().any(|p| p.intersects(&r))
};
let (mut dx, mut dy) = (center.x - cursor.x, center.y - cursor.y);
let len = (dx * dx + dy * dy).sqrt();
if len < 1.0 {
(dx, dy) = (0.0, 1.0);
} else {
(dx, dy) = (dx / len, dy / len);
}
let mut c = center;
for _ in 0..60 {
if !hits(c) {
break;
}
c.x += dx * 4.0;
c.y += dy * 4.0;
}
c
}
/// Draw a value box centred at `center`, clamped inside `bounds`.
fn draw_box(
frame: &mut canvas::Frame,
@@ -3647,6 +3707,7 @@ impl DynInputCanvas {
}
// ── Box placement by role ──
let mut placed: Vec<iced::Rectangle> = Vec::with_capacity(self.boxes.len());
for b in &self.boxes {
let center = b.center.unwrap_or_else(|| match b.role {
DynRole::Angle => self.label_screen.unwrap_or_else(|| {
@@ -3690,6 +3751,14 @@ impl DynInputCanvas {
y: base.y + dy * len * 0.5 + ny * 16.0,
},
});
let w = Self::box_width(b);
let center = Self::clear_of(center, w, cursor, &placed);
placed.push(iced::Rectangle {
x: center.x - w * 0.5,
y: center.y - DYN_BOX_H * 0.5,
width: w,
height: DYN_BOX_H,
});
Self::draw_box(frame, b, center, bounds, theme);
}
// Keep the tracking hint near the crosshair in guided layouts.