feat(points): point style commands + DDPTYPE dialog, viewport-aware size
Points could only render as a 1px dot — no way to change PDMODE/PDSIZE. - PDMODE / PDSIZE commands set the header value and rebuild the glyphs. - DDPTYPE opens a Point Style dialog: a 5x4 grid of rendered glyphs (shape x enclosure), a size field, and relative/absolute radios. - Relative (<=0) PDSIZE now sizes from the current world-per-pixel so the marker stays a roughly constant on-screen size across zoom, rebuilding on zoom via the existing camera-generation cache key. Absolute (>0) scales with the world as before. Switching to absolute with a zero size seeds a positive value from the on-screen size so it stays representable. Part of the #100 extensibility epic (point style surfaced in #106). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a3b8acb59e
commit
96ec672d5b
14 changed files with 456 additions and 45 deletions
|
|
@ -4801,6 +4801,50 @@ impl OpenCADStudio {
|
|||
self.command_line.push_error("Usage: LTSCALE [value]");
|
||||
}
|
||||
}
|
||||
cmd if cmd == "PDMODE" || cmd.starts_with("PDMODE ") => {
|
||||
let val_str = cmd.trim_start_matches("PDMODE").trim();
|
||||
if val_str.is_empty() {
|
||||
let v = self.tabs[i].scene.document.header.point_display_mode;
|
||||
self.command_line.push_output(&format!("PDMODE = {v}"));
|
||||
} else if let Ok(v) = val_str.parse::<i16>() {
|
||||
self.push_undo_snapshot(i, "PDMODE");
|
||||
self.tabs[i].scene.document.header.point_display_mode = v;
|
||||
// Point glyphs are built at tessellation time — rebuild them.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!("PDMODE set to {v}"));
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
"Usage: PDMODE [value] (0=dot 1=none 2=+ 3=x 4=tick; +32 circle, +64 square)",
|
||||
);
|
||||
}
|
||||
}
|
||||
cmd if cmd == "PDSIZE" || cmd.starts_with("PDSIZE ") => {
|
||||
let val_str = cmd.trim_start_matches("PDSIZE").trim();
|
||||
if val_str.is_empty() {
|
||||
let v = self.tabs[i].scene.document.header.point_display_size;
|
||||
self.command_line.push_output(&format!("PDSIZE = {v:.4}"));
|
||||
} else if let Ok(v) = val_str.parse::<f64>() {
|
||||
self.push_undo_snapshot(i, "PDSIZE");
|
||||
self.tabs[i].scene.document.header.point_display_size = v;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line.push_output(&format!("PDSIZE set to {v:.4}"));
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
"Usage: PDSIZE [value] (>0 absolute size, <0 percent of viewport, 0 default)",
|
||||
);
|
||||
}
|
||||
}
|
||||
cmd if cmd == "DDPTYPE" => {
|
||||
// The dialog shows the magnitude; the sign (relative/absolute)
|
||||
// is driven by the radio buttons. A positive PDSIZE is absolute;
|
||||
// zero or negative is relative.
|
||||
let pdsize = self.tabs[i].scene.document.header.point_display_size;
|
||||
self.point_size_relative = pdsize <= 0.0;
|
||||
self.point_size_buf = format!("{}", pdsize.abs());
|
||||
self.active_modal = Some(super::ModalKind::PointStyle);
|
||||
}
|
||||
cmd if cmd == "LWDISPLAY" || cmd.starts_with("LWDISPLAY ") => {
|
||||
let val_str = cmd.trim_start_matches("LWDISPLAY").trim();
|
||||
let parsed: Result<Option<bool>, ()> =
|
||||
|
|
|
|||
|
|
@ -297,6 +297,12 @@ pub(super) struct OpenCADStudio {
|
|||
/// keep their manifest listed but drop their ribbon tab and command
|
||||
/// dispatch. Persisted via [`settings::UserSettings::disabled_plugins`].
|
||||
disabled_plugins: rustc_hash::FxHashSet<String>,
|
||||
/// PDSIZE text buffer for the Point Style (DDPTYPE) dialog.
|
||||
point_size_buf: String,
|
||||
/// Point Style size mode: `true` = relative to screen, `false` = absolute.
|
||||
/// Tracked separately from the PDSIZE sign so a size of 0 (sign-less) still
|
||||
/// remembers which radio is active.
|
||||
point_size_relative: bool,
|
||||
/// New-release notification window — opened on startup when the
|
||||
/// GitHub releases API reports a newer version than this build.
|
||||
/// First-launch "make Open CAD Studio the default for .dwg/.dxf?" prompt
|
||||
|
|
@ -603,6 +609,7 @@ pub enum ModalKind {
|
|||
Unsaved,
|
||||
SaveDialog,
|
||||
AssocPrompt,
|
||||
PointStyle,
|
||||
}
|
||||
|
||||
/// Identifies a DimStyle field that can be edited in the dialog.
|
||||
|
|
@ -1031,6 +1038,18 @@ pub enum Message {
|
|||
PluginManagerClose,
|
||||
/// Enable (`true`) or disable (`false`) the plugin with this id.
|
||||
SetPluginEnabled(String, bool),
|
||||
// ── Point Style (DDPTYPE) dialog ──────────────────────────────────────
|
||||
/// Set the full PDMODE value from a glyph-grid cell.
|
||||
PointStyleSetMode(i16),
|
||||
/// Choose size mode: `true` = relative to screen (PDSIZE < 0), `false` =
|
||||
/// absolute units (PDSIZE > 0).
|
||||
PointStyleSizeRelative(bool),
|
||||
/// Edit the point-size text field (magnitude only).
|
||||
PointStyleSizeInput(String),
|
||||
/// Commit the point-size text field to the header with the current sign.
|
||||
PointStyleApplySize,
|
||||
/// Apply the point-size field and close the dialog (OK button).
|
||||
PointStyleOk,
|
||||
// ── Quick Select / Select Similar ───────────────────────────────────
|
||||
/// Extend the current selection with every entity in the active
|
||||
/// layout that matches a selected entity by (type, layer).
|
||||
|
|
@ -1442,6 +1461,8 @@ impl OpenCADStudio {
|
|||
modal_drag_last: None,
|
||||
modal_dragging: false,
|
||||
disabled_plugins: rustc_hash::FxHashSet::default(),
|
||||
point_size_buf: String::new(),
|
||||
point_size_relative: true,
|
||||
default_assoc_prompted: false,
|
||||
update_notice_version: None,
|
||||
update_notice_body: None,
|
||||
|
|
|
|||
|
|
@ -408,6 +408,7 @@ impl super::OpenCADStudio {
|
|||
1.0,
|
||||
woff,
|
||||
anno,
|
||||
None,
|
||||
);
|
||||
// Per-character boxes in the same offset-subtracted frame as the
|
||||
// preview wires (tessellate removes world_offset; mirror that).
|
||||
|
|
|
|||
|
|
@ -178,6 +178,50 @@ impl OpenCADStudio {
|
|||
self.disabled_plugins.clone()
|
||||
}
|
||||
|
||||
/// Write PDSIZE from the dialog buffer with the current relative/absolute
|
||||
/// sign. A relative size is stored negative; absolute positive. Switching to
|
||||
/// absolute with an empty/zero size seeds a positive value from the current
|
||||
/// on-screen size so the point stays representable (PDSIZE 0 always reads as
|
||||
/// relative, so absolute needs a non-zero magnitude).
|
||||
fn apply_point_size(&mut self) {
|
||||
let i = self.active_tab;
|
||||
let mut mag = self
|
||||
.point_size_buf
|
||||
.trim()
|
||||
.parse::<f64>()
|
||||
.unwrap_or(0.0)
|
||||
.abs();
|
||||
if !self.point_size_relative && mag == 0.0 {
|
||||
let wpp = self.tabs[i].scene.world_per_pixel().unwrap_or(0.0);
|
||||
mag = if wpp > 0.0 {
|
||||
crate::entities::point::relative_world_size(0.0, wpp)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
self.point_size_buf = format!("{mag:.4}");
|
||||
}
|
||||
let next = if self.point_size_relative { -mag } else { mag };
|
||||
self.push_undo_snapshot(i, "PDSIZE");
|
||||
self.tabs[i].scene.document.header.point_display_size = next;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
/// Replace the `mask` bits of PDMODE with `value`, rebuild the point glyphs
|
||||
/// and mark the document dirty. Used by the Point Style (DDPTYPE) dialog.
|
||||
fn set_point_mode_bits(&mut self, mask: i16, value: i16) {
|
||||
let i = self.active_tab;
|
||||
let cur = self.tabs[i].scene.document.header.point_display_mode;
|
||||
let next = (cur & !mask) | (value & mask);
|
||||
if next == cur {
|
||||
return;
|
||||
}
|
||||
self.push_undo_snapshot(i, "PDMODE");
|
||||
self.tabs[i].scene.document.header.point_display_mode = next;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
/// Write preferences to disk only when they differ from the last write,
|
||||
/// so a toggle persists immediately without thrashing the file.
|
||||
fn persist_settings_if_changed(&mut self) {
|
||||
|
|
@ -5499,6 +5543,28 @@ impl OpenCADStudio {
|
|||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
Message::PointStyleSetMode(mode) => {
|
||||
self.set_point_mode_bits(!0, mode);
|
||||
Task::none()
|
||||
}
|
||||
Message::PointStyleSizeRelative(relative) => {
|
||||
self.point_size_relative = relative;
|
||||
self.apply_point_size();
|
||||
Task::none()
|
||||
}
|
||||
Message::PointStyleSizeInput(s) => {
|
||||
self.point_size_buf = s;
|
||||
Task::none()
|
||||
}
|
||||
Message::PointStyleApplySize => {
|
||||
self.apply_point_size();
|
||||
Task::none()
|
||||
}
|
||||
Message::PointStyleOk => {
|
||||
self.apply_point_size();
|
||||
self.close_active_modal();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::EnterViewport(handle) => {
|
||||
let i = self.active_tab;
|
||||
|
|
|
|||
|
|
@ -1100,6 +1100,7 @@ impl OpenCADStudio {
|
|||
AssocPrompt => (440, 210),
|
||||
Unsaved => (420, 160),
|
||||
SaveDialog => (560, 480),
|
||||
PointStyle => (360, 470),
|
||||
};
|
||||
Some((w as f32 + EXTRA_W, h as f32 + EXTRA_H))
|
||||
}
|
||||
|
|
@ -1572,6 +1573,15 @@ impl OpenCADStudio {
|
|||
};
|
||||
sized(unsaved_changes_dialog_window(&tab_name), 420, 160)
|
||||
}
|
||||
super::ModalKind::PointStyle => sized(
|
||||
crate::ui::point_style::view_window(
|
||||
self.tabs[self.active_tab].scene.document.header.point_display_mode,
|
||||
self.point_size_relative,
|
||||
&self.point_size_buf,
|
||||
),
|
||||
360,
|
||||
470,
|
||||
),
|
||||
super::ModalKind::SaveDialog => sized(
|
||||
save_as_dialog_window(
|
||||
&self.save_dialog_filename,
|
||||
|
|
|
|||
Loading…
Reference in a new issue