Merge main into codex/graphic-attributes-cleanup

Conflicts were additions on both sides, kept together: the Count palette
and Sheet Set toggles next to the Graphic Attributes toggle in the ribbon,
main's new locale groups and catalog entries next to graphic-attributes,
and module declarations. The palette code follows main's crate renames
(acadrust -> codec, cadkernel -> kernel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VgkJN8DEojuPTumAXLHotu
This commit is contained in:
Claude committed 2026-10-07 20:11:05 +00:00
commit 7a03e1c43c
625 files changed
+104397 -28053

No files matched your search

+294 -105
View File
@@ -9,6 +9,7 @@ pub(crate) mod config;
pub use automation::{export_headless, serve};
mod annotation_data;
mod command_driver;
pub(crate) use command_driver::{copy_to_clipboard_kernel, paste_entities_kernel};
pub(crate) mod commands;
pub(crate) mod dim_viewport;
#[cfg(test)]
@@ -25,6 +26,7 @@ mod history;
mod layers;
mod model_ops;
mod navigation;
mod node_graph;
mod mtext_editor;
#[cfg(not(target_arch = "wasm32"))]
pub mod plugin_host;
@@ -75,7 +77,7 @@ pub enum UcsGripKind {
/// threshold the multi-functional menu opens (`grip_popup`).
#[derive(Clone, Debug)]
pub struct GripHover {
pub handle: acadrust::Handle,
pub handle: codec::Handle,
pub grip_id: usize,
pub screen: iced::Point,
pub started: iced::time::Instant,
@@ -115,7 +117,7 @@ pub enum ContextMenuNav {
/// Open multi-functional-grip popup state.
#[derive(Clone, Debug)]
pub struct GripPopup {
pub handle: acadrust::Handle,
pub handle: codec::Handle,
pub grip_id: usize,
pub anchor: iced::Point,
pub items: Vec<crate::scene::model::object::GripMenuItem>,
@@ -130,7 +132,7 @@ pub struct GripPopup {
/// into `apply_grip_menu_value` for `(handle, grip_id, action)`.
#[derive(Clone, Debug)]
pub struct GripPendingValue {
pub handle: acadrust::Handle,
pub handle: codec::Handle,
pub grip_id: usize,
pub action: crate::scene::model::object::GripMenuAction,
pub label: &'static str,
@@ -277,20 +279,20 @@ pub(crate) struct FindReplaceState {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FindMatchKey {
Entity(acadrust::Handle),
Entity(codec::Handle),
BlockEntityInInsert {
entity: acadrust::Handle,
insert: acadrust::Handle,
entity: codec::Handle,
insert: codec::Handle,
},
InsertAttribute {
insert: acadrust::Handle,
insert: codec::Handle,
index: usize,
},
}
use crate::snap::Snapper;
use crate::ui::{CommandLine, Ribbon, StatusBar};
use acadrust::types::{Color as AcadColor, LineWeight};
use acadrust::CadDocument;
use codec::types::{Color as AcadColor, LineWeight};
use codec::CadDocument;
use iced::time::Instant;
use iced::window;
@@ -318,7 +320,7 @@ pub struct OpenProgress {
pub state: Arc<crate::io::OpenProgressState>,
pub started: Instant,
pub recovery_error: Option<String>,
pub recovery_read_stats: Option<acadrust::ReadStats>,
pub recovery_read_stats: Option<codec::ReadStats>,
#[cfg(target_arch = "wasm32")]
pub recovery_bytes: Option<std::sync::Arc<[u8]>>,
/// Disk state captured before parsing starts. If another editor changes the
@@ -334,15 +336,15 @@ pub struct OpenProgress {
/// left unchanged (issue #239).
struct AddSelectedRestore {
layer_name: String,
layer_handle: acadrust::types::Handle,
layer_handle: codec::types::Handle,
color: AcadColor,
transparency: acadrust::types::Transparency,
transparency: codec::types::Transparency,
linetype_name: String,
linetype_handle: acadrust::types::Handle,
linetype_handle: codec::types::Handle,
line_weight: i16,
lt_scale: f64,
dimstyle_name: String,
dimstyle_handle: acadrust::types::Handle,
dimstyle_handle: codec::types::Handle,
tab_active_layer: String,
tab_layers_current: String,
ribbon_layer: String,
@@ -493,6 +495,21 @@ pub(super) struct OpenCADStudio {
drawing_units: Option<crate::ui::window::drawing_units::State>,
/// Working copy of the Block Definition dialog; `None` while it is closed.
block_definition: Option<crate::ui::window::block_definition::BlockDefinitionState>,
/// PDF dialogs' working copies; `None` while closed.
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
point_cloud_attach: Option<crate::ui::window::pdf_dialogs::PointCloudAttachState>,
/// ATTDEF dialog and Edit Attribute Definition dialog; `None` while closed.
attdef_dialog: Option<crate::ui::window::attdef_dialog::AttdefDialogState>,
attdef_edit: Option<crate::ui::window::attdef_dialog::AttdefEditState>,
/// Field dialog working copy; `None` while closed.
field_dialog: Option<crate::ui::window::field_dialog::FieldDialogState>,
point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>,
pc_section: Option<crate::ui::window::pdf_dialogs::PcSectionState>,
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
pdf_import_settings: Option<crate::modules::insert::pdf_import::PdfImportSettings>,
pdf_import_file: Option<crate::ui::window::pdf_dialogs::PdfImportFileState>,
/// The dialog Options was opened from; it comes back when Options closes.
options_parent: Option<ModalKind>,
/// Working copy of the Attach External Reference dialog; None while closed.
xref_attach: Option<crate::ui::window::xref_attach::XrefAttachState>,
/// Working copy of the Write Block (WBLOCK) dialog; `None` while it is closed.
@@ -507,7 +524,7 @@ pub(super) struct OpenCADStudio {
/// by the `PERF` command.
perf_hud: bool,
/// When set, the cycling list box is open: (canvas point, candidates).
cycle_candidates: Option<(iced::Point, Vec<acadrust::Handle>)>,
cycle_candidates: Option<(iced::Point, Vec<codec::Handle>)>,
/// Which status-bar pills the user has chosen to show (persisted).
statusbar_config: crate::ui::statusbar::statusbar_config::StatusBarConfig,
/// Add selected scales to existing annotative objects.
@@ -542,8 +559,15 @@ pub(super) struct OpenCADStudio {
polar_increment_deg: f32,
/// Reverse the mouse-wheel zoom direction when true (ZOOMWHEEL = 1).
zoom_wheel_reversed: bool,
/// Mouse-wheel zoom sensitivity, clamped to 3..=100 (ZOOMFACTOR).
/// Mouse-wheel zoom sensitivity (ZOOMFACTOR), held inside
/// `settings::ZOOM_FACTOR_MIN..=settings::ZOOM_FACTOR_MAX`. `SETVAR`
/// only sets the range the system variable itself has; the Options
/// window reaches the rest.
zoom_factor: i32,
/// The Options field behind `zoom_factor`. A text field is edited as
/// text — cleared, half-typed — so the buffer is what the user sees and
/// the value follows only once it parses.
zoom_factor_input: String,
/// Crosshair size setting (CURSORSIZE, 1..=100).
cursor_size: i32,
/// Selection-box size setting (PICKBOX, 0..=50).
@@ -566,6 +590,14 @@ pub(super) struct OpenCADStudio {
crosshair_color: Option<[u8; 3]>,
/// Editable Options buffer for the crosshair colour.
crosshair_color_input: String,
/// Explicit object-snap marker colour; `None` keeps the automatic one.
snap_marker_color: Option<[u8; 3]>,
/// Explicit command-line text colour; `None` keeps the theme's.
command_text_color: Option<[u8; 3]>,
/// Editable Options buffer for the command-line text colour.
command_text_color_input: String,
/// Editable Options buffer for the snap marker colour.
snap_marker_color_input: String,
/// Defer the ISOLINES mesh rebuild until the slider is released.
isolines_awaiting_regen: bool,
/// Edit buffer for the SNAPANG field on the Options Drafting page. Kept
@@ -591,8 +623,14 @@ pub(super) struct OpenCADStudio {
pub grid_beyond_limits: bool,
/// Dynamic input overlay (F12): show coordinate tooltip near cursor.
dyn_input: bool,
/// Dynamic input mode (DYNMODE): 0=off, 1=pointer, 2=dimensional, 3=both.
dyn_mode: i16,
/// Currently visible page in the application Options dialog.
options_tab: crate::ui::window::options::OptionsTab,
/// Saved graphics options (backend, compat renderer); read at launch, so
/// edits here only take effect after a restart.
#[cfg(not(target_arch = "wasm32"))]
graphics_prefs: crate::gpu_backend::GraphicsPrefs,
/// The Options window's commit point (see `options_session`).
options_saved: Option<options_session::OptionsSnapshot>,
/// Close was pressed with unapplied changes; the discard guard is up.
@@ -659,6 +697,14 @@ pub(super) struct OpenCADStudio {
block_mru: Vec<String>,
/// Insertion frequency per block name (uppercase key → count), capped.
block_freq: std::collections::HashMap<String, u32>,
/// BLOCKMRULIST: how many recent blocks the palette keeps (0–100).
pub(crate) block_mru_list: u8,
/// BLOCKREDEFINEMODE (0–2).
pub(crate) block_redefine_mode: u8,
/// BLOCKNAVIGATE: the Libraries tab's start folder ("." for none).
pub(crate) block_navigate: String,
/// INSNAME: the default block name for -INSERT (session only).
pub(crate) insname: String,
/// Last time block-usage was flushed to disk (debounce per 2.4).
#[cfg(not(target_arch = "wasm32"))]
block_usage_last_persist: Option<std::time::Instant>,
@@ -706,10 +752,10 @@ pub(super) struct OpenCADStudio {
/// A leader line just added via the "Add Leader" grip menu whose arrow is
/// being placed (follows the cursor). `(entity handle, new-arrow grip id)`.
/// Esc before the placement click removes it again.
grip_add_provisional: Option<(acadrust::Handle, usize)>,
grip_add_provisional: Option<(codec::Handle, usize)>,
/// Handles hidden from the base tessellation during an in-progress grip
/// drag. The edited entities are shown in the overlay until commit.
grip_preview_handles: Vec<acadrust::Handle>,
grip_preview_handles: Vec<codec::Handle>,
/// Pending rollover hit-test. Each idle cursor move stashes
/// `(last_move_at, point, tab)` here and clears the live highlight;
/// `HoverDwellTick` runs the pick once the cursor has been still for
@@ -721,11 +767,11 @@ pub(super) struct OpenCADStudio {
constraint_glyph_tooltip: Option<crate::scene::parametric_constraints::ConstraintKind>,
/// Snapshots of edited entities taken at the start of a grip drag. The drag
/// mutates the document live, so Escape restores this group atomically.
grip_originals: Vec<(acadrust::Handle, acadrust::EntityType)>,
grip_originals: Vec<(codec::Handle, codec::EntityType)>,
/// Solid-history objects paired with their owning entity before a grip drag.
grip_history_originals: Vec<(
acadrust::Handle,
Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
codec::Handle,
Vec<(codec::Handle, codec::objects::ObjectType)>,
)>,
/// Document dirty state before the live grip mutation began.
grip_dirty_before: Option<bool>,
@@ -765,7 +811,7 @@ pub(super) struct OpenCADStudio {
render_mode_menu_open: bool,
/// Mode whose sample is shown while the pointer moves through the flyout.
/// This does not alter the drawing until the corresponding row is clicked.
render_mode_preview: Option<acadrust::entities::ViewportRenderMode>,
render_mode_preview: Option<codec::entities::ViewportRenderMode>,
/// Whether the Properties panel is shown on the left (PROPERTIES).
show_properties: bool,
/// Whether the Graphic Attributes palette is shown (GRAPHICATTRIBUTES).
@@ -773,11 +819,28 @@ pub(super) struct OpenCADStudio {
graphic_attributes: crate::ui::window::graphic_attributes::GraphicAttributesState,
/// Docked Insert Block panel visibility.
pub(crate) show_block_palette: bool,
/// Node graph overlay over the viewport.
pub(crate) show_node_graph: bool,
/// Entity the Properties handlers act on instead of the selection, set
/// only for the duration of a node-graph property write.
pub(crate) property_target_override: Option<codec::Handle>,
/// A node-graph evaluation is recording; its writes join that one undo step.
pub(crate) graph_undo_open: bool,
/// Docked External References panel visibility (EXTERNALREFERENCES).
pub(crate) show_external_references: bool,
/// Whether the Browser panel is shown. Off until BROWSER opens it, so
/// the default layout is unchanged for existing users.
pub(crate) show_browser: bool,
/// Point Cloud Manager palette (POINTCLOUDMANAGER).
pub(crate) pc_manager: crate::ui::window::pc_manager::PcManager,
pub(crate) count_palette: crate::ui::window::count_palette::CountPalette,
/// Sheet Set Manager palette, the open sheet sets and their dialogs.
pub(crate) sheet_set: crate::ui::window::sheet_set::SheetSetManager,
/// A sheet opened from the manager: its drawing (path key) and layout,
/// switched to once the drawing has loaded.
pub(crate) sheet_set_pending_layout: Option<(String, String)>,
/// The subset / set Import Layout as Sheet adds to, while its file picker is open.
pub(crate) sheet_set_import_parent: Option<String>,
/// Which viewport background the colour wheel is editing, or `None` when
/// it is closed. One slot, because only one wheel can be open at a time.
pub(crate) bg_picker: Option<BgTarget>,
@@ -845,7 +908,7 @@ pub(super) struct OpenCADStudio {
/// of OS windows).
active_modal: Option<ModalKind>,
/// Selection and staged values owned by the Properties hyperlink dialog.
hyperlink_editor_handles: Vec<acadrust::Handle>,
hyperlink_editor_handles: Vec<codec::Handle>,
hyperlink_editor_url: String,
hyperlink_editor_description: String,
hyperlink_editor_mixed: bool,
@@ -898,7 +961,7 @@ pub(super) struct OpenCADStudio {
modal_resizing: bool,
// ── Attribute editor dialog (ATTEDIT / double-click a block) ───────────
/// INSERT whose attributes the editor modal is editing (`None` = closed).
attr_editor_handle: Option<acadrust::Handle>,
attr_editor_handle: Option<codec::Handle>,
/// Block name shown in the editor's title bar.
attr_editor_block: String,
/// Working copy of the block's attributes, in the same order as
@@ -972,6 +1035,8 @@ pub(super) struct OpenCADStudio {
check_missing_fonts: bool,
/// Custom font source base URL; empty selects the community repository.
font_source_url: String,
/// Template a new drawing starts from (QNEW); empty = blank.
qnew_template: String,
/// Editable copy of `font_source_url` shown in the missing-fonts prompt.
font_source_input: String,
donation_prompt_version: String,
@@ -986,9 +1051,9 @@ pub(super) struct OpenCADStudio {
/// shipped without notes.
update_notice_body: Option<String>,
/// In-memory clipboard: cloned entities waiting to be pasted.
clipboard: Vec<acadrust::EntityType>,
clipboard: Vec<codec::EntityType>,
/// Entities removed by the most recent ERASE, kept so OOPS can restore them.
oops_cache: Vec<Arc<acadrust::EntityType>>,
oops_cache: Vec<Arc<codec::EntityType>>,
/// Paste anchor: lower-left corner of the clipboard entities' bounding box
/// (or the point picked by COPYBASE). This point lands under the cursor at
/// paste time.
@@ -1044,7 +1109,7 @@ pub(super) struct OpenCADStudio {
/// `<previous>` list entry.
plot_prev: Option<crate::ui::window::plot::PlotDialogState>,
/// Full source settings behind the fields currently shown in Plot.
plot_setup_template: Option<acadrust::objects::PlotSettings>,
plot_setup_template: Option<codec::objects::PlotSettings>,
/// Paper layouts shown by Print All, in tab order with their selection.
print_all_layouts: Vec<(String, bool)>,
/// True while the Plot dialog is editing settings for Print All.
@@ -1057,7 +1122,7 @@ pub(super) struct OpenCADStudio {
print_all_plot_style_prev: Option<Option<crate::io::plot_style::PlotStyleTable>>,
/// Plot window restored together with cancelled Print All options.
print_all_plot_window_prev: Option<Option<(f64, f64, f64, f64)>>,
print_all_plot_setup_prev: Option<Option<acadrust::objects::PlotSettings>>,
print_all_plot_setup_prev: Option<Option<codec::objects::PlotSettings>>,
// ── Plot Style Table ──────────────────────────────────────────────────
/// Currently loaded CTB/STB table (None = no override).
@@ -1221,7 +1286,7 @@ pub(super) struct OpenCADStudio {
layer_state_name_buf: String,
layer_state_description_buf: String,
layer_state_filter: String,
layer_state_edit_draft: Option<acadrust::LayerState>,
layer_state_edit_draft: Option<codec::LayerState>,
layer_state_edit_filter: String,
layer_state_edit_color_open: Option<usize>,
@@ -1234,7 +1299,7 @@ pub(super) struct OpenCADStudio {
scale_rename_buf: String,
/// The entity the Annotation Object Scale dialog is editing (its per-object
/// annotation-scale membership).
anno_object_scale_target: Option<acadrust::types::Handle>,
anno_object_scale_target: Option<codec::types::Handle>,
/// Open transaction for the scale manager — restored if the window closes
/// without Apply, mirroring the style managers' staging.
scale_stage: Option<crate::app::style_ops::ScaleStage>,
@@ -1281,6 +1346,7 @@ pub(super) struct OpenCADStudio {
/// drawings in a file manager produces exactly that (one process per file,
/// all arriving at once), which makes this queue load-bearing, not polish.
pub(super) pending_opens: std::collections::VecDeque<PathBuf>,
pub(super) pending_startup_script_lines: Vec<String>,
/// One global interaction-index build at a time. Large drawings can each
/// hold millions of entries, so file-open bursts must not multiply peak
/// CPU and memory by the number of tabs.
@@ -1425,8 +1491,10 @@ pub(super) struct OpenCADStudio {
/// What triggered the "unsaved changes" dialog.
#[derive(Debug, Clone)]
pub(super) enum PendingClose {
/// User tried to close the tab at this index.
Tab(usize),
/// User tried to close the tab with this document id. Ids stay valid
/// while the dialog is open; a vector index would go stale the moment
/// another tab closes and Discard would remove the wrong tab.
Tab(u64),
/// User tried to quit the application.
Quit,
}
@@ -1452,7 +1520,7 @@ pub(super) enum SaveContinuation {
pub(super) struct PendingNativeThumbnailSave {
tab_id: u64,
path: PathBuf,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
purpose: SavePurpose,
continuation: SaveContinuation,
set_current_path: bool,
@@ -1465,7 +1533,7 @@ pub(super) struct PendingWebThumbnailSave {
tab_id: u64,
filename: String,
ext: String,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
bounds: iced::Rectangle,
}
@@ -1474,7 +1542,7 @@ pub(super) struct PendingWebThumbnailSave {
pub(super) struct PendingSaveFailure {
tab_id: u64,
path: PathBuf,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
purpose: SavePurpose,
continuation: SaveContinuation,
set_current_path: bool,
@@ -1486,7 +1554,7 @@ pub(super) struct PendingSaveFailure {
pub(super) struct PendingExternalChange {
tab_id: u64,
path: PathBuf,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
purpose: SavePurpose,
continuation: SaveContinuation,
set_current_path: bool,
@@ -1501,12 +1569,12 @@ pub struct SaveOutcome {
revision: u64,
camera_generation: u64,
path: PathBuf,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
previous_autosave: Option<PathBuf>,
set_current_path: bool,
purpose: SavePurpose,
continuation: SaveContinuation,
refreshed_preview: Option<Option<acadrust::Preview>>,
refreshed_preview: Option<Option<codec::Preview>>,
result: Result<(), crate::io::SaveFailure>,
}
/// Which viewport background a colour-wheel session is editing.
@@ -1543,8 +1611,8 @@ pub enum ColorPickTarget {
PropertiesField(String),
/// Current creation colour (ribbon).
Ribbon,
/// A layer's colour, by panel row index.
Layer(usize),
/// A layer's colour, by layer name.
Layer(String),
/// A saved layer-state layer's colour, by editor row index.
LayerState(usize),
/// The MText editor's selection (or global) colour.
@@ -1557,10 +1625,10 @@ pub enum ColorPickTarget {
/// their layer / linetype / style instead of dangling. See #129.
#[derive(Default, Clone)]
pub struct ClipboardDeps {
pub layers: Vec<acadrust::tables::Layer>,
pub linetypes: Vec<acadrust::tables::LineType>,
pub text_styles: Vec<acadrust::tables::TextStyle>,
pub dim_styles: Vec<acadrust::tables::DimStyle>,
pub layers: Vec<codec::tables::Layer>,
pub linetypes: Vec<codec::tables::LineType>,
pub text_styles: Vec<codec::tables::TextStyle>,
pub dim_styles: Vec<codec::tables::DimStyle>,
/// Block definitions the copied INSERTs reference (transitively),
/// snapshotted from the source drawing. Recreated on paste so a block
/// reference doesn't render empty in a drawing that lacks the
@@ -1582,7 +1650,7 @@ pub struct ClipboardDeps {
/// handles so a copied group stays grouped — cross-drawing too, the same way
/// the in-drawing COPY path preserves it. Partly-selected groups are not
/// captured (copying one member should not spawn a fragment). See #440.
pub groups: Vec<acadrust::objects::Group>,
pub groups: Vec<codec::objects::Group>,
}
/// A captured block definition: its base point and the entities it owns
@@ -1591,8 +1659,8 @@ pub struct ClipboardDeps {
#[derive(Clone)]
pub struct BlockDef {
pub name: String,
pub base_point: acadrust::types::Vector3,
pub entities: Vec<acadrust::EntityType>,
pub base_point: codec::types::Vector3,
pub entities: Vec<codec::EntityType>,
}
/// The extension-dictionary object graph captured for one copied entity.
@@ -1603,16 +1671,16 @@ pub struct BlockDef {
#[derive(Clone)]
pub struct ClipExtObjects {
pub entity_index: usize,
pub src_entity_handle: acadrust::Handle,
pub root: acadrust::Handle,
pub objects: Vec<(acadrust::Handle, acadrust::objects::ObjectType)>,
pub annotation_scales: Vec<(acadrust::Handle, acadrust::objects::Scale)>,
pub src_entity_handle: codec::Handle,
pub root: codec::Handle,
pub objects: Vec<(codec::Handle, codec::objects::ObjectType)>,
pub annotation_scales: Vec<(codec::Handle, codec::objects::Scale)>,
}
impl ClipboardDeps {
/// Snapshot the records `entities` reference that exist in `doc`.
pub fn capture(doc: &acadrust::CadDocument, entities: &[acadrust::EntityType]) -> Self {
use acadrust::EntityType;
pub fn capture(doc: &codec::CadDocument, entities: &[codec::EntityType]) -> Self {
use codec::EntityType;
use std::collections::BTreeSet;
let (mut layers, mut ltypes, mut tstyles, mut dstyles) = (
BTreeSet::new(),
@@ -1670,7 +1738,7 @@ impl ClipboardDeps {
if !objects.is_empty() {
let mut annotation_scales = Vec::new();
for (_, object) in &objects {
let acadrust::objects::ObjectType::ObjectContextData(context) = object
let codec::objects::ObjectType::ObjectContextData(context) = object
else {
continue;
};
@@ -1680,7 +1748,7 @@ impl ClipboardDeps {
{
continue;
}
if let Some(acadrust::objects::ObjectType::Scale(scale)) =
if let Some(codec::objects::ObjectType::Scale(scale)) =
doc.objects.get(&context.scale)
{
annotation_scales.push((context.scale, scale.clone()));
@@ -1721,13 +1789,13 @@ impl ClipboardDeps {
// Groups fully contained in the top-level selection. Groups reference
// top-level entities (not block internals), so match against `entities`
// only. Mirrors the in-drawing `copy_complete_groups` membership test.
let copied_handles: rustc_hash::FxHashSet<acadrust::Handle> =
let copied_handles: rustc_hash::FxHashSet<codec::Handle> =
entities.iter().map(|e| e.common().handle).collect();
let groups: Vec<acadrust::objects::Group> = doc
let groups: Vec<codec::objects::Group> = doc
.objects
.values()
.filter_map(|obj| match obj {
acadrust::objects::ObjectType::Group(g)
codec::objects::ObjectType::Group(g)
if !g.entities.is_empty()
&& g.entities.iter().all(|h| copied_handles.contains(h)) =>
{
@@ -1765,12 +1833,12 @@ impl ClipboardDeps {
/// `root` (dictionary entries, nested xdictionaries, dictionary defaults),
/// returned as `(source_handle, object)` pairs. Cycle-safe.
fn collect_ext_subtree(
doc: &acadrust::CadDocument,
root: acadrust::Handle,
) -> Vec<(acadrust::Handle, acadrust::objects::ObjectType)> {
use acadrust::objects::ObjectType;
doc: &codec::CadDocument,
root: codec::Handle,
) -> Vec<(codec::Handle, codec::objects::ObjectType)> {
use codec::objects::ObjectType;
use rustc_hash::FxHashSet;
let mut seen: FxHashSet<acadrust::Handle> = FxHashSet::default();
let mut seen: FxHashSet<codec::Handle> = FxHashSet::default();
let mut queue = vec![root];
let mut out = Vec::new();
while let Some(h) = queue.pop() {
@@ -1803,10 +1871,10 @@ impl ClipboardDeps {
/// INSERT, walking nested INSERTs transitively. Model/paper space and
/// xref blocks are skipped — those aren't portable definitions.
fn capture_blocks(
doc: &acadrust::CadDocument,
entities: &[acadrust::EntityType],
doc: &codec::CadDocument,
entities: &[codec::EntityType],
) -> Vec<BlockDef> {
use acadrust::EntityType;
use codec::EntityType;
use rustc_hash::FxHashSet;
let mut seen: FxHashSet<String> = FxHashSet::default();
let mut queue: Vec<String> = Vec::new();
@@ -1838,8 +1906,8 @@ impl ClipboardDeps {
/// Snapshot one block definition as a portable `BlockDef`: its base point
/// and owned entities, minus the structural Block/BlockEnd markers. Returns
/// None for model/paper space and xref blocks (not portable definitions).
fn snapshot_block(doc: &acadrust::CadDocument, name: &str) -> Option<BlockDef> {
use acadrust::EntityType;
fn snapshot_block(doc: &codec::CadDocument, name: &str) -> Option<BlockDef> {
use codec::EntityType;
let br = doc.block_records.get(name)?;
if name.starts_with("*Model_Space") || name.starts_with("*Paper_Space") || br.flags.is_xref
{
@@ -1847,7 +1915,7 @@ impl ClipboardDeps {
}
let base_point = match doc.get_entity(br.block_entity_handle) {
Some(EntityType::Block(b)) => b.base_point,
_ => acadrust::types::Vector3::ZERO,
_ => codec::types::Vector3::ZERO,
};
let mut owned = Vec::new();
for &eh in &br.entity_handles {
@@ -1890,9 +1958,20 @@ pub enum ModalKind {
LayerTranslator,
DrawingUnits,
BlockDefinition,
PdfAttach,
PointCloudAttach,
PointCloudColorMap,
PcSection,
UnderlayLayers,
PdfImportSettings,
PdfImportFile,
XrefAttach,
WriteBlock,
GeometricTolerance,
AttDef,
AttDefEdit,
CountInvalidArea,
Field,
DraftingSettings,
AutoConstrainSettings,
LayerStateEditor,
@@ -1940,6 +2019,8 @@ pub enum ModalKind {
GpuWarning,
/// Reference Manager help window (toolbar Help button).
XrefHelp,
/// Sheet set wizard, properties and sheet / subset forms.
SheetSet,
}
/// A property group controlled by a layer state's restore mask.
@@ -2050,6 +2131,15 @@ pub enum ArrowKey {
Right,
}
/// The text input holding focus when Ctrl+V arrives (native only).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PasteFocus {
None,
CommandLine,
/// Any other text field (dialogs, panels).
Field,
}
#[derive(Debug, Clone)]
pub enum Message {
GraphicAttributes(crate::ui::window::graphic_attributes::GraphicAttributesMsg),
@@ -2072,6 +2162,8 @@ pub enum Message {
ControlTaskDone(String),
ControlScreenshot(String, Option<iced::window::Screenshot>),
ControlToggle,
/// Node graph overlay interaction.
Graph(crate::ui::node_graph::GraphMsg),
Tick(Instant),
/// Periodic drain of plugin-to-host requests that arrived outside a host
/// call (e.g. mutations from the Python REPL).
@@ -2092,6 +2184,8 @@ pub enum Message {
/// Ctrl+V. Routed by `update` into an open text editor, the drawing-object
/// clipboard, or the system text clipboard.
PasteShortcut,
/// Which text input held focus when `PasteShortcut` arrived.
PasteShortcutResolved(PasteFocus),
/// Completion of a system text clipboard read requested by PASTECLIP.
SystemClipboardPaste(SystemClipboardText),
/// Ctrl/Cmd+A — select all layer rows when the Layer Manager is open, or all
@@ -2189,6 +2283,15 @@ pub enum Message {
OptionsOpen,
/// Switch the visible page in Options.
OptionsTabChanged(crate::ui::window::options::OptionsTab),
/// Options > Graphics: pin a backend (applies at next launch).
#[cfg(not(target_arch = "wasm32"))]
GraphicsBackendChanged(crate::gpu_backend::BackendChoice),
/// Options > Graphics: force the packed compatibility renderer.
#[cfg(not(target_arch = "wasm32"))]
GraphicsCompatToggled(bool),
/// Options > Graphics: prefer OpenGL on older GPUs.
#[cfg(not(target_arch = "wasm32"))]
GraphicsLegacyGlToggled(bool),
/// Set CURSORSIZE from the Display-page slider.
CursorSizeChanged(i32),
/// Set PICKBOX from the Selection-page slider.
@@ -2203,6 +2306,13 @@ pub enum Message {
LineweightDisplayScaleChanged(i32),
/// Edit the optional crosshair RGB value; blank restores automatic contrast.
CrosshairColorChanged(String),
SnapMarkerColorChanged(String),
CommandTextColorChanged(String),
/// Options → Files: the default template for new drawings.
QnewTemplateChanged(String),
QnewTemplateBrowse,
/// NEW: the picked template a new drawing starts from (`None` = cancelled).
NewFromTemplate(Option<std::path::PathBuf>),
/// Set the default type/version used when first saving a new drawing.
DefaultSaveFormatChanged(String),
/// Select one of Iced's built-in themes or the editable Custom theme.
@@ -2272,8 +2382,13 @@ pub enum Message {
CommandLineFadeChanged(i32),
/// Toggle reversing the mouse-wheel zoom direction (ZOOMWHEEL).
ZoomWheelReversedChanged(bool),
/// Change how far one wheel notch zooms (ZOOMFACTOR, 3..=100).
/// Change how far one wheel notch zooms, from the Options slider
/// (ZOOMFACTOR).
ZoomFactorChanged(i32),
/// Edit the Options zoom-factor field; committed when it holds a number,
/// which is how a value above the system variable's own range is set
/// (ZOOMFACTOR).
ZoomFactorInputChanged(String),
/// Options → User Preferences: right-click behaviour (SHORTCUTMENU).
RightClickModeChanged(settings::RightClickMode),
/// Options → User Preferences: time-sensitive hold threshold, ms.
@@ -2353,13 +2468,13 @@ pub enum Message {
LayerTranslatorTranslate,
/// Set the active tab's render mode — one of the seven visual styles, and
/// the only way a style is ever set.
SetRenderMode(acadrust::entities::ViewportRenderMode),
SetRenderMode(codec::entities::ViewportRenderMode),
/// Open or close the active viewport's visual-style flyout.
ToggleRenderModeMenu(acadrust::entities::ViewportRenderMode),
ToggleRenderModeMenu(codec::entities::ViewportRenderMode),
/// Close the visual-style flyout after Escape or an outside click.
DismissRenderModeMenu,
/// Change only the sample shown beside the visual-style list.
PreviewRenderMode(acadrust::entities::ViewportRenderMode),
PreviewRenderMode(codec::entities::ViewportRenderMode),
/// Switch camera projection: true = Orthographic, false = Perspective.
SetProjection(bool),
/// Select a ribbon module tab by index.
@@ -2391,8 +2506,12 @@ pub enum Message {
to: usize,
after: bool,
},
/// Close the given tab index.
TabClose(usize),
/// Close the tab with this document id. Carries the stable id, not a
/// vector index: iced dispatches every message built from one view
/// snapshot before rebuilding it, so an index captured alongside other
/// messages can already be stale (and point at a different tab) by the
/// time it is handled.
TabClose(u64),
/// Save every drawing that already has a file path.
DocTabSaveAll,
/// Close every non-Start drawing tab.
@@ -2432,7 +2551,7 @@ pub enum Message {
tab_id: u64,
filename: String,
ext: String,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
bounds: Option<iced::Rectangle>,
screenshot: Option<iced::window::Screenshot>,
},
@@ -2598,28 +2717,28 @@ pub enum Message {
XrefRowFindReplacePrompt(usize),
XrefRowChangePathEnter,
XrefRowChangePathLeave,
LayerToggleVisible(usize),
LayerToggleLock(usize),
LayerToggleFreeze(usize),
LayerTogglePlot(usize),
LayerToggleVisible(String),
LayerToggleLock(String),
LayerToggleFreeze(String),
LayerTogglePlot(String),
/// Sort the Layer Manager table by a clicked column header.
LayerSort(crate::ui::window::layers::LayerSortCol),
/// Toggle per-viewport freeze: (layer_index, vp_col_index)
LayerToggleVpFreeze(usize, usize),
/// Toggle per-viewport freeze: (layer_name, vp_col_index)
LayerToggleVpFreeze(String, usize),
LayerNew,
LayerDelete,
/// Confirm deleting a non-empty layer (erases its objects too).
LayerDeleteConfirm,
LayerSetCurrent,
LayerSelect(usize),
LayerRenameStart(usize),
LayerSelect(String),
LayerRenameStart(String),
LayerRenameEdit(String),
LayerColorPickerToggle(usize),
LayerColorPickerToggle(String),
LayerColorMorePalette,
LayerColorSet(acadrust::types::Color),
LayerColorSet(codec::types::Color),
LayerLinetypeSet(String),
LayerLineweightSet(LineWeight),
LayerTransparencyEdit(usize, String),
LayerTransparencyEdit(String, String),
LayerRenameCommit,
// ── Layer State Manager ─────────────────────────────────────────────
LayerStateManagerOpen,
@@ -2635,11 +2754,11 @@ pub enum Message {
LayerStateEditorMaskToggle(LayerStateProperty),
LayerStateEditorLayerFlagToggle(usize, LayerStateLayerFlag),
LayerStateEditorLayerColorToggle(usize),
LayerStateEditorLayerColor(usize, acadrust::types::Color),
LayerStateEditorLayerColor(usize, codec::types::Color),
LayerStateEditorLayerLinetype(usize, String),
LayerStateEditorLayerLineweight(usize, LineWeight),
LayerStateEditorLayerPlotStyle(usize, String),
LayerStateEditorLayerTransparency(usize, Option<acadrust::types::Transparency>),
LayerStateEditorLayerTransparency(usize, Option<codec::types::Transparency>),
LayerStateEditorName(String),
LayerStateEditorDescription(String),
LayerStateEditorCurrentLayer(String),
@@ -2648,10 +2767,10 @@ pub enum Message {
LayerStateEditorCancel,
/// ViewCube-local cursor movement, tagged with the floating viewport that
/// owned the overlay when the event was produced (`None` = Model layout).
CursorMoved(Point, Option<acadrust::Handle>),
CursorMoved(Point, Option<codec::Handle>),
/// ViewCube press with the same owner tag, so a stale overlay event can
/// never fall through and rotate a different camera.
ViewportClick(Option<acadrust::Handle>),
ViewportClick(Option<codec::Handle>),
ViewportMove(Point),
ViewportLeftPress,
ViewportLeftRelease,
@@ -2836,12 +2955,12 @@ pub enum Message {
/// Toggle selection cycling for overlapping objects.
ToggleSelectionCycling,
/// Add an object from the selection-cycling list box to the selection.
CycleSelect(acadrust::Handle),
CycleSelect(codec::Handle),
/// Preview (highlight) a cycling-list row's object, or clear with `None`.
CycleHover(Option<acadrust::Handle>),
CycleHover(Option<codec::Handle>),
/// Cursor left a cycling-list row; clear the preview only if it still
/// points at this row (guards against enter/exit event reordering).
CycleHoverExit(acadrust::Handle),
CycleHoverExit(codec::Handle),
/// Dismiss the selection-cycling list box without picking.
CycleCancel,
/// Toggle the selection-filter type picker open/closed.
@@ -3118,7 +3237,7 @@ pub enum Message {
/// dropdown is open at a time and they can't overlap. (#235)
PropColorPickerClose,
/// Enter the model-space editing mode inside the given viewport (MSPACE).
EnterViewport(acadrust::Handle),
EnterViewport(codec::Handle),
/// Exit MSPACE and return to paper-space editing (PSPACE).
ExitViewport,
/// MS command: enter MSPACE for the first available viewport.
@@ -3267,7 +3386,7 @@ pub enum Message {
PropParamAddNew,
/// A Constraints-section row was clicked: select every entity in the
/// list (replacing the current selection) in the viewport.
PropConstraintLinkClick(Vec<acadrust::Handle>),
PropConstraintLinkClick(Vec<codec::Handle>),
/// Remove one parametric constraint selected from Properties or the viewport.
PropConstraintDelete(crate::scene::parametric_constraints::ConstraintId),
// ── About window ────────────────────────────────────────────────────
@@ -3282,7 +3401,7 @@ pub enum Message {
CloseModal,
// ── Attribute editor dialog ───────────────────────────────────────────
/// Open the attribute editor for an INSERT (double-click / ATTEDIT).
AttrEditorOpen(acadrust::Handle),
AttrEditorOpen(codec::Handle),
/// Switch the editor's active tab.
AttrEditorTab(crate::ui::window::attribute_editor::AttrTab),
/// Select the row the Text Options / Properties tabs act on.
@@ -3305,7 +3424,7 @@ pub enum Message {
AttrEditorLayer(String),
AttrEditorLinetype(String),
AttrEditorColor(String),
AttrEditorLineweight(acadrust::types::LineWeight),
AttrEditorLineweight(codec::types::LineWeight),
/// Commit every attribute edit to the block, keeping the dialog open
/// (Apply); the frame ✕ closes and discards any un-applied edits.
AttrEditorApply,
@@ -3441,7 +3560,7 @@ pub enum Message {
/// Open / close the MText colour picker popup.
MTextColorPickerToggle,
/// Toolbar justification / attachment-point change.
MTextJustify(acadrust::entities::mtext::AttachmentPoint),
MTextJustify(codec::entities::mtext::AttachmentPoint),
/// Toolbar paragraph-alignment change.
MTextAlign(mtext_editor::ParaAlign),
/// Toolbar line-spacing change.
@@ -3543,6 +3662,15 @@ pub enum Message {
/// Completion of a PDF/preview/print job performed outside the UI thread.
/// The boolean restores the Plot dialog after a preview.
BackgroundIoFinished(Result<String, String>, bool),
/// A background worker thread panicked or stopped without reporting a
/// result. Surfaced on the command line instead of taking the whole
/// process down with it.
BackgroundTaskFailed {
/// Which operation the worker was running (e.g. "STL export").
context: String,
/// Panic message (or channel-failure description) from the worker.
detail: String,
},
/// Send current layout to the system printer (via lp / lpr).
PrintToPrinter,
/// Callback from the async printer job.
@@ -3576,6 +3704,8 @@ pub enum Message {
// ── Plot Style Table ─────────────────────────────────────────────────
/// Open file dialog to load a CTB/STB plot style table.
PlotStyleLoad,
/// LINETYPE Load: the picked `.lin` file's text, `None` when cancelled.
LinetypeLoaded(Option<String>),
/// Callback when the user picks (or cancels) a CTB/STB file.
/// The Load… picker finished: a table, nothing (cancelled), or why the
/// file could not be read.
@@ -3784,7 +3914,7 @@ pub enum Message {
/// Close the colour picker without choosing.
CloseColorPicker,
/// Commit the colour chosen in the shared picker.
ColorWindowPick(acadrust::types::Color),
ColorWindowPick(codec::types::Color),
/// Set a block/linetype Handle field on the selected dim style from a
/// dropdown of available block-records / linetypes (by name).
DsSetHandle {
@@ -3813,6 +3943,17 @@ pub enum Message {
// ── PDF Underlay ──────────────────────────────────────────────────────
/// Open file-picker dialog for PDFATTACH command (async).
PdfAttachPick,
/// PDFIMPORT File: pick the PDF to import.
PdfImportPick,
/// An edit in one of the PDF dialogs.
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
AttdefDialog(crate::ui::window::attdef_dialog::AttdefDialogMsg),
FieldDialog(crate::ui::window::field_dialog::FieldDialogMsg),
/// A click or search in the Point Cloud Manager.
PcManager(crate::ui::window::pc_manager::PcManagerMsg),
Count(crate::ui::window::count_palette::CountMsg),
SheetSet(crate::ui::window::sheet_set::SheetSetMsg),
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
/// Result of the PDFATTACH file picker.
PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
@@ -3823,6 +3964,16 @@ pub enum Message {
XAttachPickResult(Result<std::path::PathBuf, String>),
/// ATTACH: pick a drawing, image or PDF to reference.
AttachPick,
/// DWFATTACH / DGNATTACH: pick the file (the result goes the ATTACH way).
UnderlayAttachPick(codec::entities::UnderlayType),
/// POINTCLOUDATTACH: pick the scan or project (the result goes the
/// ATTACH way).
PointCloudAttachPick,
/// The Reference slide-out's xref fading: amount dragged, drag done,
/// switch.
XrefFadeSlide(u8),
XrefFadeCommit,
XrefFadeToggle,
/// Result of the ATTACH file picker.
AttachPickResult(Result<std::path::PathBuf, String>),
/// An edit in the Attach External Reference dialog.
@@ -3842,7 +3993,7 @@ pub enum Message {
TableInsertApply,
DataLinkManagerOpen,
DataLinkNew,
DataLinkSelect(acadrust::types::Handle),
DataLinkSelect(codec::types::Handle),
DataLinkEdit,
DataLinkEditCancel,
DataLinkField(crate::ui::window::annotation_data::DataLinkField),
@@ -3991,6 +4142,17 @@ impl OpenCADStudio {
layer_translator: None,
drawing_units: None,
block_definition: None,
pdf_attach: None,
point_cloud_attach: None,
attdef_dialog: None,
attdef_edit: None,
field_dialog: None,
point_cloud_color_map: None,
pc_section: None,
underlay_layers: None,
pdf_import_settings: None,
pdf_import_file: None,
options_parent: None,
xref_attach: None,
wblock: None,
geometric_tolerance: None,
@@ -4005,6 +4167,7 @@ impl OpenCADStudio {
polar_increment_deg: 45.0,
zoom_wheel_reversed: false,
zoom_factor: 60,
zoom_factor_input: 60.to_string(),
cursor_size: 5,
pick_box: 3,
double_click_block_refedit: false,
@@ -4016,6 +4179,10 @@ impl OpenCADStudio {
cursor_type: settings::CursorType::Crosshair,
crosshair_color: None,
crosshair_color_input: String::new(),
snap_marker_color: None,
snap_marker_color_input: String::new(),
command_text_color: None,
command_text_color_input: String::new(),
isolines_awaiting_regen: false,
snap_angle_input: "0".to_string(),
lineweight_display_scale: 100,
@@ -4029,7 +4196,10 @@ impl OpenCADStudio {
grid_adaptive: true,
grid_beyond_limits: true,
dyn_input: true,
dyn_mode: 3,
options_tab: crate::ui::window::options::OptionsTab::General,
#[cfg(not(target_arch = "wasm32"))]
graphics_prefs: crate::gpu_backend::load_prefs(),
options_saved: None,
options_close_confirm: false,
spacemouse: {
@@ -4070,6 +4240,10 @@ impl OpenCADStudio {
commandline_fade_ms: 3000,
block_mru: Vec::new(),
block_freq: std::collections::HashMap::new(),
block_mru_list: 50,
block_redefine_mode: 1,
block_navigate: ".".to_string(),
insname: String::new(),
#[cfg(not(target_arch = "wasm32"))]
block_usage_last_persist: None,
awaiting_vports: false,
@@ -4107,8 +4281,16 @@ impl OpenCADStudio {
show_graphic_attributes: false,
graphic_attributes: Default::default(),
show_block_palette: false,
show_node_graph: false,
property_target_override: None,
graph_undo_open: false,
show_external_references: false,
show_browser: false,
pc_manager: Default::default(),
count_palette: Default::default(),
sheet_set: Default::default(),
sheet_set_pending_layout: None,
sheet_set_import_parent: None,
bg_picker: None,
block_palette: Default::default(),
xref_manager: Default::default(),
@@ -4190,6 +4372,7 @@ impl OpenCADStudio {
default_assoc_prompted: false,
check_missing_fonts: true,
font_source_url: String::new(),
qnew_template: String::new(),
font_source_input: String::new(),
donation_prompt_version: String::new(),
read_only: false,
@@ -4236,6 +4419,7 @@ impl OpenCADStudio {
missing_fonts_downloading: false,
suppressed_missing_fonts: rustc_hash::FxHashSet::default(),
pending_opens: std::collections::VecDeque::new(),
pending_startup_script_lines: Vec::new(),
active_interaction_index: None,
queued_interaction_indices: std::collections::VecDeque::new(),
pending_close: None,
@@ -4582,21 +4766,26 @@ impl OpenCADStudio {
s.command_line.push_warning(&notice);
crate::scene::pipeline::report_gpu_line(&format!("[gpu] {notice}"));
}
let cli_open: Task<Message> = if !cfg.files.is_empty() {
let has_files = !cfg.files.is_empty();
let cli_open: Task<Message> = if has_files {
Task::batch(
cfg.files
.into_iter()
.map(|p| Task::done(Message::OpenExternal(p))),
)
} else if cfg.new {
} else if cfg.new || !cfg.script_lines.is_empty() {
Task::done(Message::TabNew)
} else {
Task::none()
};
// Startup command script: each line dispatched as if typed at the
// command line, in order, after any file open is requested.
// Startup command script: if files were passed to open, defer the
// script lines until the document finishes opening. Otherwise, dispatch
// them on the newly created drawing tab.
let script: Task<Message> = if cfg.script_lines.is_empty() {
Task::none()
} else if has_files {
s.pending_startup_script_lines = cfg.script_lines;
Task::none()
} else {
Task::batch(
cfg.script_lines