refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
impl OpenCADStudio {
|
|
|
|
|
pub(super) fn dispatch_draw(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
|
|
|
|
match cmd {
|
|
|
|
|
// ── Draw commands ──────────────────────────────────────────────
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"LINE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::line::LineCommand;
|
|
|
|
|
let new_cmd = LineCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"MLINE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::mline::MlineCommand;
|
|
|
|
|
let style = self.tabs[i].scene.document.header.multiline_style.clone();
|
|
|
|
|
let cmd_obj = MlineCommand::with_style(style);
|
|
|
|
|
self.command_line.push_info(&cmd_obj.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd if cmd == "WIPEOUT" || cmd == "WO" || cmd.starts_with("WIPEOUT ") => {
|
|
|
|
|
use crate::modules::draw::draw::wipeout::WipeoutCommand;
|
|
|
|
|
let args = cmd
|
|
|
|
|
.split_once(' ')
|
|
|
|
|
.map(|(_, r)| r.trim().to_uppercase())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let wo_cmd = if args == "P" || args == "POLYGONAL" {
|
|
|
|
|
WipeoutCommand::new_polygonal()
|
|
|
|
|
} else {
|
|
|
|
|
WipeoutCommand::new_rectangular()
|
|
|
|
|
};
|
|
|
|
|
self.command_line.push_info(&wo_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(wo_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd if cmd == "IMAGE" || cmd == "IMAGEATTACH" || cmd == "IM" => {
|
|
|
|
|
return Some(Task::done(Message::ImagePick));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"REVCLOUD" => {
|
|
|
|
|
use crate::modules::draw::draw::revcloud::RevCloudCommand;
|
|
|
|
|
let cmd = RevCloudCommand::new();
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"ATTDEF" => {
|
|
|
|
|
use crate::modules::draw::draw::attdef::AttdefCommand;
|
|
|
|
|
let cmd = AttdefCommand::new();
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:27:35 +03:00
|
|
|
// Command-line attribute editing on selected Insert entities. Bare
|
|
|
|
|
// ATTEDIT and the ATE alias launch the interactive editor instead
|
|
|
|
|
// (see the ATTEDIT arm in the inquiry family); the dash form is the
|
|
|
|
|
// command-line entry point.
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
// Usage:
|
feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:27:35 +03:00
|
|
|
// -ATTEDIT — list all attributes on selected Insert(s)
|
|
|
|
|
// ATTEDIT <tag> <v> — quick-set attribute <tag> to <v>
|
|
|
|
|
cmd if cmd.starts_with("ATTEDIT ")
|
|
|
|
|
|| cmd == "-ATTEDIT"
|
|
|
|
|
|| cmd.starts_with("-ATTEDIT ") =>
|
|
|
|
|
{
|
|
|
|
|
let rest = cmd
|
|
|
|
|
.trim_start_matches("-ATTEDIT")
|
|
|
|
|
.trim_start_matches("ATTEDIT")
|
|
|
|
|
.trim();
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let parts: Vec<&str> = rest.splitn(2, char::is_whitespace).collect();
|
|
|
|
|
let selected_handles: Vec<acadrust::Handle> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(h, _)| *h)
|
|
|
|
|
.collect();
|
|
|
|
|
if selected_handles.is_empty() {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_error("ATTEDIT: select an Insert entity first.");
|
|
|
|
|
} else {
|
|
|
|
|
let mut found_any = false;
|
|
|
|
|
for sh in &selected_handles {
|
|
|
|
|
if let Some(acadrust::EntityType::Insert(ins)) = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.document
|
|
|
|
|
.entities()
|
|
|
|
|
.find(|e| e.common().handle == *sh)
|
|
|
|
|
{
|
|
|
|
|
found_any = true;
|
|
|
|
|
if rest.is_empty() {
|
|
|
|
|
// List attributes.
|
|
|
|
|
if ins.attributes.is_empty() {
|
|
|
|
|
self.command_line.push_output(&format!(
|
|
|
|
|
" Insert {:x}: no attributes.",
|
|
|
|
|
sh.value()
|
|
|
|
|
));
|
|
|
|
|
} else {
|
|
|
|
|
for attr in &ins.attributes {
|
|
|
|
|
self.command_line.push_output(&format!(
|
|
|
|
|
" [{tag}] = {val}",
|
|
|
|
|
tag = attr.tag,
|
|
|
|
|
val = attr.get_value()
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found_any {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_error("ATTEDIT: no Insert entities in selection.");
|
|
|
|
|
}
|
|
|
|
|
// If tag + value supplied, mutate attributes.
|
|
|
|
|
if parts.len() == 2 && !parts[0].is_empty() {
|
|
|
|
|
let tag_up = parts[0].to_uppercase();
|
|
|
|
|
let new_val = parts[1];
|
|
|
|
|
let mut changed = 0usize;
|
|
|
|
|
self.push_undo_snapshot(i, "ATTEDIT");
|
|
|
|
|
for sh in &selected_handles {
|
|
|
|
|
if let Some(acadrust::EntityType::Insert(ins)) = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.document
|
|
|
|
|
.entities_mut()
|
|
|
|
|
.find(|e| e.common().handle == *sh)
|
|
|
|
|
{
|
|
|
|
|
for attr in &mut ins.attributes {
|
|
|
|
|
if attr.tag.to_uppercase() == tag_up {
|
|
|
|
|
attr.set_value(new_val);
|
|
|
|
|
changed += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if changed > 0 {
|
|
|
|
|
self.tabs[i].dirty = true;
|
|
|
|
|
self.command_line.push_output(&format!(
|
|
|
|
|
"ATTEDIT: updated {changed} attribute(s) [{tag_up}] = {new_val}."
|
|
|
|
|
));
|
|
|
|
|
} else {
|
|
|
|
|
self.command_line.push_error(&format!(
|
|
|
|
|
"ATTEDIT: tag '{tag_up}' not found in selection."
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ATTDISP — control attribute display visibility.
|
|
|
|
|
// ATTDISP ON — make all AttributeDefinitions visible
|
|
|
|
|
// ATTDISP OFF — make all AttributeDefinitions invisible
|
|
|
|
|
// ATTDISP NORMAL — restore: show only those without the invisible flag
|
2026-07-16 13:19:04 +03:00
|
|
|
"ATTDISP" => {
|
|
|
|
|
use crate::command::KeywordCommand;
|
|
|
|
|
let c = KeywordCommand::new(
|
|
|
|
|
"ATTDISP",
|
|
|
|
|
"ATTDISP attribute display [On / Off / Normal]:",
|
|
|
|
|
vec![
|
|
|
|
|
("On", "ON", None),
|
|
|
|
|
("Off", "OFF", None),
|
|
|
|
|
("Normal", "NORMAL", None),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("ATTDISP ") => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let sub = cmd.split_whitespace().nth(1).unwrap_or("").to_uppercase();
|
|
|
|
|
match sub.as_str() {
|
|
|
|
|
"ON" | "OFF" | "NORMAL" => {
|
|
|
|
|
self.push_undo_snapshot(i, "ATTDISP");
|
|
|
|
|
let mut count = 0usize;
|
|
|
|
|
for entity in self.tabs[i].scene.document.entities_mut() {
|
|
|
|
|
if let acadrust::EntityType::AttributeDefinition(ad) = entity {
|
|
|
|
|
match sub.as_str() {
|
|
|
|
|
"ON" => {
|
|
|
|
|
ad.flags.invisible = false;
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
"OFF" => {
|
|
|
|
|
ad.flags.invisible = true;
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
"NORMAL" => { /* leave existing flags — they are already the "normal" state */
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
self.tabs[i].dirty = true;
|
|
|
|
|
self.command_line.push_output(&format!(
|
|
|
|
|
"ATTDISP {sub}: {count} attribute definition(s) updated."
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_info("Usage: ATTDISP ON | OFF | NORMAL");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"DONUT" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::donut::DonutCommand;
|
|
|
|
|
let cmd = DonutCommand::new();
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"CIRCLE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::circle::CircleCommand;
|
|
|
|
|
let new_cmd = CircleCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"CIRCLE_CD" => {
|
|
|
|
|
use crate::modules::draw::draw::circle::CircleCDCommand;
|
|
|
|
|
let new_cmd = CircleCDCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"CIRCLE_2P" => {
|
|
|
|
|
use crate::modules::draw::draw::circle::Circle2PCommand;
|
|
|
|
|
let new_cmd = Circle2PCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"CIRCLE_3P" => {
|
|
|
|
|
use crate::modules::draw::draw::circle::Circle3PCommand;
|
|
|
|
|
let new_cmd = Circle3PCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"CIRCLE_TTR" => {
|
|
|
|
|
use crate::modules::draw::draw::circle::CircleTTRCommand;
|
|
|
|
|
let new_cmd = CircleTTRCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.pre_cmd_tangent = Some(self.snapper.is_on(crate::snap::SnapType::Tangent));
|
|
|
|
|
self.snapper.enabled.insert(crate::snap::SnapType::Tangent);
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"CIRCLE_TTT" => {
|
|
|
|
|
use crate::modules::draw::draw::circle::CircleTTTCommand;
|
|
|
|
|
let new_cmd = CircleTTTCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.pre_cmd_tangent = Some(self.snapper.is_on(crate::snap::SnapType::Tangent));
|
|
|
|
|
self.snapper.enabled.insert(crate::snap::SnapType::Tangent);
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"ARC" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::arc::ArcCommand;
|
|
|
|
|
let new_cmd = ArcCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_3P" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::Arc3PCommand;
|
|
|
|
|
let new_cmd = Arc3PCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_SCE" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcSCECommand;
|
|
|
|
|
let new_cmd = ArcSCECommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_SCA" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcSCACommand;
|
|
|
|
|
let new_cmd = ArcSCACommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_SCL" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcSCLCommand;
|
|
|
|
|
let new_cmd = ArcSCLCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_SEA" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcSEACommand;
|
|
|
|
|
let new_cmd = ArcSEACommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_SER" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcSERCommand;
|
|
|
|
|
let new_cmd = ArcSERCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_SED" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcSEDCommand;
|
|
|
|
|
let new_cmd = ArcSEDCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_CSA" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcCSACommand;
|
|
|
|
|
let new_cmd = ArcCSACommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"ARC_CSL" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::ArcCSLCommand;
|
|
|
|
|
let new_cmd = ArcCSLCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
2026-07-08 20:39:40 +03:00
|
|
|
"ARC_CONT" => {
|
|
|
|
|
use crate::modules::draw::draw::arc::{continue_anchor, ArcContCommand};
|
|
|
|
|
// Prefer the anchor recorded when the last line/arc was drawn (it
|
|
|
|
|
// knows the true drawing-end tangent); otherwise fall back to the
|
|
|
|
|
// last line/arc found in the document (e.g. after a file load).
|
|
|
|
|
let seed = self.cont_anchor.or_else(|| {
|
|
|
|
|
self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.document
|
|
|
|
|
.entities()
|
|
|
|
|
.filter_map(|e| continue_anchor(e, None))
|
|
|
|
|
.last()
|
|
|
|
|
});
|
|
|
|
|
match seed {
|
|
|
|
|
Some((s, tangent)) => {
|
|
|
|
|
let new_cmd = ArcContCommand::new(s, tangent);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_info("ARC Continue No previous line or arc to continue.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"RECT" | "RECTANG" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::shapes::RectCommand;
|
|
|
|
|
let new_cmd = RectCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
if self.ortho_mode {
|
|
|
|
|
self.rect_suppressed_ortho = true;
|
|
|
|
|
self.ortho_mode = false;
|
|
|
|
|
}
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"RECT_ROT" => {
|
|
|
|
|
use crate::modules::draw::draw::shapes::RectRotCommand;
|
|
|
|
|
let new_cmd = RectRotCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
if self.ortho_mode {
|
|
|
|
|
self.rect_suppressed_ortho = true;
|
|
|
|
|
self.ortho_mode = false;
|
|
|
|
|
}
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"RECT_CEN" => {
|
|
|
|
|
use crate::modules::draw::draw::shapes::RectCenCommand;
|
|
|
|
|
let new_cmd = RectCenCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
if self.ortho_mode {
|
|
|
|
|
self.rect_suppressed_ortho = true;
|
|
|
|
|
self.ortho_mode = false;
|
|
|
|
|
}
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"POLY" | "POLYGON" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::shapes::PolyCommand;
|
|
|
|
|
let new_cmd = PolyCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"POLY_C" => {
|
|
|
|
|
use crate::modules::draw::draw::shapes::PolyCCommand;
|
|
|
|
|
let new_cmd = PolyCCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
"POLY_E" => {
|
|
|
|
|
use crate::modules::draw::draw::shapes::PolyECommand;
|
|
|
|
|
let new_cmd = PolyECommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"PLINE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::polyline::PlineCommand;
|
|
|
|
|
let new_cmd = PlineCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:27:35 +03:00
|
|
|
"3DPOLY" => {
|
|
|
|
|
use crate::modules::draw::draw::poly3d::Poly3dCommand;
|
|
|
|
|
let new_cmd = Poly3dCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2D filled solid. Reached via SO / SOLID2D — the bare SOLID verb is
|
|
|
|
|
// currently the shaded-display toggle (token collision tracked).
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"SOLID" | "SOLID2D" => {
|
feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:27:35 +03:00
|
|
|
use crate::modules::draw::draw::solid2d::Solid2dCommand;
|
|
|
|
|
let new_cmd = Solid2dCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"HELIX" => {
|
|
|
|
|
use crate::modules::draw::draw::helix::HelixCommand;
|
|
|
|
|
let new_cmd = HelixCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"TRACE" => {
|
|
|
|
|
use crate::modules::draw::draw::trace::TraceCommand;
|
|
|
|
|
let new_cmd = TraceCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
"CENTERLINE" => {
|
|
|
|
|
use crate::modules::draw::draw::centerline::CenterLineCommand;
|
|
|
|
|
let new_cmd = CenterLineCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"DIMCENTER" | "CENTERMARK" => {
|
feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:27:35 +03:00
|
|
|
use crate::modules::draw::draw::dimcenter::DimCenterCommand;
|
|
|
|
|
let new_cmd = DimCenterCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"SKETCH" => {
|
|
|
|
|
use crate::modules::draw::draw::sketch::SketchCommand;
|
|
|
|
|
let new_cmd = SketchCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"REVERSE" => {
|
|
|
|
|
use crate::modules::draw::modify::reverse::ReverseCommand;
|
|
|
|
|
let new_cmd = ReverseCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"MEASUREGEOM" => {
|
feat(commands): integrate standard CAD commands and fix behavior gaps
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:27:35 +03:00
|
|
|
use crate::modules::draw::inquiry::measuregeom::MeasureGeomCommand;
|
|
|
|
|
let new_cmd = MeasureGeomCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
// ── Modify commands ────────────────────────────────────────────
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
// MOVE works from picked points, so it already relocates entities
|
|
|
|
|
// in 3D; 3DMOVE is the same operation.
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"MOVE" | "3DMOVE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("MOVE");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
use crate::modules::draw::modify::translate::MoveCommand;
|
|
|
|
|
let wires = self.tabs[i].scene.wire_models_for(&handles);
|
|
|
|
|
let new_cmd = MoveCommand::new(handles, wires);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"COPY" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("COPY");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
use crate::modules::draw::modify::copy::CopyCommand;
|
|
|
|
|
let wires = self.tabs[i].scene.wire_models_for(&handles);
|
|
|
|
|
let new_cmd = CopyCommand::new(handles, wires);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"ROTATE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("ROTATE");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
use crate::modules::draw::modify::rotate::RotateCommand;
|
|
|
|
|
let wires = self.tabs[i].scene.wire_models_for(&handles);
|
|
|
|
|
let new_cmd = RotateCommand::new(handles, wires);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"TORIENT" => {
|
|
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("TORIENT");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
use crate::modules::draw::modify::torient::TorientCommand;
|
|
|
|
|
let entities: Vec<_> = handles
|
|
|
|
|
.iter()
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
.filter_map(|&h| {
|
|
|
|
|
self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.document
|
|
|
|
|
.get_entity(h)
|
|
|
|
|
.cloned()
|
|
|
|
|
.map(|e| (h, e))
|
|
|
|
|
})
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
.collect();
|
|
|
|
|
let cam_rot = self.tabs[i].scene.camera.borrow().rotation;
|
|
|
|
|
let right = cam_rot * glam::Vec3::X;
|
|
|
|
|
let view_twist = right.y.atan2(right.x) as f64;
|
|
|
|
|
let new_cmd = TorientCommand::new(entities, view_twist);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"POINT" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::point::PointCommand;
|
|
|
|
|
let new_cmd = PointCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"RAY" => {
|
|
|
|
|
use crate::modules::draw::draw::ray::RayCommand;
|
|
|
|
|
let new_cmd = RayCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"XLINE" | "CONSTRUCTIONLINE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::ray::XLineCommand;
|
|
|
|
|
let new_cmd = XLineCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"HATCH" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::hatch::HatchCommand;
|
|
|
|
|
let outlines = self.tabs[i].scene.closed_outlines();
|
|
|
|
|
let new_cmd = HatchCommand::new(outlines);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"HATCHEDIT" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::hatchedit::HatcheditCommand;
|
|
|
|
|
// If a single hatch is already selected, skip the pick step.
|
|
|
|
|
let sel = self.tabs[i].scene.selected_entities();
|
|
|
|
|
if sel.len() == 1 {
|
|
|
|
|
let (h, _) = sel[0];
|
|
|
|
|
if let Some(model) = self.tabs[i].scene.hatches.get(&h).cloned() {
|
|
|
|
|
let cmd = HatcheditCommand::with_handle(
|
|
|
|
|
h,
|
|
|
|
|
model.name.clone(),
|
|
|
|
|
model.scale,
|
|
|
|
|
model.angle_offset,
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_error("HATCHEDIT: selected entity is not a hatch.");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let cmd = HatcheditCommand::new();
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"GRADIENT" => {
|
|
|
|
|
use crate::modules::draw::draw::hatch::GradientCommand;
|
|
|
|
|
let outlines = self.tabs[i].scene.closed_outlines();
|
|
|
|
|
let new_cmd = GradientCommand::new(outlines);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"BOUNDARY" => {
|
|
|
|
|
use crate::modules::draw::draw::hatch::BoundaryCommand;
|
|
|
|
|
let outlines = self.tabs[i].scene.closed_outlines();
|
|
|
|
|
let new_cmd = BoundaryCommand::new(outlines);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"ELLIPSE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::ellipse::EllipseCommand;
|
|
|
|
|
let new_cmd = EllipseCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"ELLIPSE_AXIS" => {
|
|
|
|
|
use crate::modules::draw::draw::ellipse::EllipseAxisCommand;
|
|
|
|
|
let new_cmd = EllipseAxisCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"ELLIPSE_ARC" => {
|
|
|
|
|
use crate::modules::draw::draw::ellipse::EllipseArcCommand;
|
|
|
|
|
let new_cmd = EllipseArcCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"SPLINE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::draw::draw::spline::SplineCommand;
|
|
|
|
|
let new_cmd = SplineCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"SCALE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("SCALE");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
use crate::modules::draw::modify::scale::ScaleCommand;
|
|
|
|
|
let wires = self.tabs[i].scene.wire_models_for(&handles);
|
|
|
|
|
let new_cmd = ScaleCommand::new(handles, wires);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"MIRROR" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("MIRROR");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
use crate::modules::draw::modify::mirror::MirrorCommand;
|
2026-07-03 12:42:55 +03:00
|
|
|
let (wires, text_ghosts) =
|
|
|
|
|
self.tabs[i].scene.mirror_preview_parts(&handles);
|
|
|
|
|
let mirror_text = self.tabs[i].scene.document.header.mirror_text;
|
|
|
|
|
let new_cmd = MirrorCommand::new(handles, wires, text_ghosts, mirror_text);
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"ERASE" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let handles: Vec<_> = self.tabs[i]
|
|
|
|
|
.scene
|
|
|
|
|
.selected_entities()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(h, _)| h)
|
|
|
|
|
.collect();
|
|
|
|
|
if handles.is_empty() {
|
|
|
|
|
use crate::modules::draw::select::SelectObjectsCommand;
|
|
|
|
|
let cmd = SelectObjectsCommand::new("ERASE");
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
} else {
|
|
|
|
|
let n = handles.len();
|
perf(undo): record entity deltas instead of cloning the whole document
Frequent entity-only edits (MOVE/ROTATE/SCALE/MIRROR/ALIGN, COPY/ARRAY,
draw-add, ERASE) captured a full ~800k-entity CadDocument clone (~46ms) on
every edit — the last big per-edit cost after the incremental-render work.
They now record only the touched entities' before/after images:
- Scene self-records through the five mutation primitives when a recording is
open (UndoRecording + is_recording_undo guard, zero cost off-path).
- apply_entity_delta installs one side per handle (in-place / re-insert with
the original handle / remove) plus a per-handle cache reseed, then a single
bump_entities so the incremental caches patch instead of a full re-tess —
undo/redo of a small edit is fast too, not just the edit.
- HistorySnapshot is now Full | Delta; a Delta is self-inverting and moves
between the undo/redo stacks without re-capturing. Commands that touch
non-entity state (new layer, group, *D block, viewport) fall back to the
full snapshot via per-command predicates, and a poison flag debug-asserts
any mis-scope.
remove_entity leaves the handle dangling in its owner block record's
entity_handles (harmless on its own, a lookup miss is skipped), so a
re-insert strips stale entries first — otherwise add_entity would list the
handle twice and the DXF/DWG writer would emit the entity twice.
Round-trip tests cover transform/erase/add/copy (do->undo->redo equality,
handle preservation, no duplication).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:11:17 +03:00
|
|
|
let delta_safe = self.delta_erase_safe(i, &handles);
|
|
|
|
|
let pending = self.begin_undo(i, "ERASE", handles.len(), delta_safe);
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
// Stash the erased entities so OOPS can restore them.
|
|
|
|
|
self.oops_cache = handles
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|h| self.tabs[i].scene.document.get_entity(*h).cloned())
|
|
|
|
|
.collect();
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
self.tabs[i].scene.erase_entities(&handles);
|
|
|
|
|
self.tabs[i].dirty = true;
|
|
|
|
|
self.refresh_properties();
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_output(&format!("{n} object(s) erased."));
|
perf(undo): record entity deltas instead of cloning the whole document
Frequent entity-only edits (MOVE/ROTATE/SCALE/MIRROR/ALIGN, COPY/ARRAY,
draw-add, ERASE) captured a full ~800k-entity CadDocument clone (~46ms) on
every edit — the last big per-edit cost after the incremental-render work.
They now record only the touched entities' before/after images:
- Scene self-records through the five mutation primitives when a recording is
open (UndoRecording + is_recording_undo guard, zero cost off-path).
- apply_entity_delta installs one side per handle (in-place / re-insert with
the original handle / remove) plus a per-handle cache reseed, then a single
bump_entities so the incremental caches patch instead of a full re-tess —
undo/redo of a small edit is fast too, not just the edit.
- HistorySnapshot is now Full | Delta; a Delta is self-inverting and moves
between the undo/redo stacks without re-capturing. Commands that touch
non-entity state (new layer, group, *D block, viewport) fall back to the
full snapshot via per-command predicates, and a poison flag debug-asserts
any mis-scope.
remove_entity leaves the handle dangling in its owner block record's
entity_handles (harmless on its own, a lookup miss is skipped), so a
re-insert strips stale entries first — otherwise add_entity would list the
handle twice and the DXF/DWG writer would emit the entity twice.
Round-trip tests cover transform/erase/add/copy (do->undo->redo equality,
handle preservation, no duplication).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:11:17 +03:00
|
|
|
if let Some(pd) = pending {
|
|
|
|
|
self.commit_undo_delta(i, pd);
|
|
|
|
|
}
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Model commands (3D primitives) ─────────────────────────────
|
|
|
|
|
"BOX" | "WEDGE" | "CYLINDER" | "CONE" | "SPHERE" | "TORUS" => {
|
|
|
|
|
use crate::modules::model::primitive_cmd::PrimitiveCommand;
|
|
|
|
|
let new_cmd = PrimitiveCommand::new(cmd);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Design commands (solid booleans) ───────────────────────────
|
|
|
|
|
"UNION" | "SUBTRACT" | "INTERSECT" => {
|
|
|
|
|
use crate::modules::model::boolean_cmd::BoolOp;
|
|
|
|
|
if let Some(op) = BoolOp::from_id(cmd) {
|
|
|
|
|
return Some(self.solid_boolean(op));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
// INTERFERE — non-destructive intersect: solid from the overlap.
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"INTERFERE" => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
return Some(self.solid_interfere());
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-29 09:16:44 +03:00
|
|
|
// FLATSHOT — flatten the selected solid's edges to 2D lines at Z=0.
|
|
|
|
|
"FLATSHOT" => {
|
|
|
|
|
return Some(self.solid_flatshot());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CONVTOSURFACE — convert the selected solid(s) to surface entities.
|
|
|
|
|
"CONVTOSURFACE" => {
|
|
|
|
|
return Some(self.solid_convtosurface());
|
|
|
|
|
}
|
|
|
|
|
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
// POLYSOLID <width> <height> — extrude a selected polyline into a
|
|
|
|
|
// wall-like solid.
|
2026-07-16 14:47:45 +03:00
|
|
|
"POLYSOLID" => {
|
|
|
|
|
use crate::command::SelectThenTwoValueCommand;
|
|
|
|
|
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
|
|
|
|
let c = SelectThenTwoValueCommand::new(
|
|
|
|
|
"POLYSOLID",
|
|
|
|
|
"POLYSOLID wall width:",
|
|
|
|
|
"POLYSOLID wall height:",
|
|
|
|
|
has_sel,
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("POLYSOLID ") => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let nums: Vec<f64> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.filter_map(|s| s.parse::<f64>().ok())
|
|
|
|
|
.collect();
|
|
|
|
|
if nums.len() >= 2 && nums[0] > 0.0 && nums[1] > 0.0 {
|
|
|
|
|
return Some(self.solid_polysolid(nums[0], nums[1]));
|
|
|
|
|
}
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_info("Usage: POLYSOLID <width> <height> (select a polyline first)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SPLINEFIT — fit a smooth spline through the selected polyline's points.
|
|
|
|
|
"SPLINEFIT" | "FITSPLINE" => {
|
|
|
|
|
return Some(self.fit_spline());
|
|
|
|
|
}
|
2026-06-29 09:07:30 +03:00
|
|
|
|
|
|
|
|
// REGION — convert selected closed boundaries (closed polylines /
|
|
|
|
|
// circles) into Region entities (one wire loop each).
|
|
|
|
|
"REGION" | "REG" => {
|
|
|
|
|
use acadrust::entities::{Region, Wire};
|
|
|
|
|
use acadrust::types::Vector3;
|
|
|
|
|
let mut loops: Vec<Vec<Vector3>> = Vec::new();
|
|
|
|
|
for (_, e) in self.tabs[i].scene.selected_entities().iter() {
|
|
|
|
|
match e {
|
|
|
|
|
acadrust::EntityType::LwPolyline(pl)
|
|
|
|
|
if pl.is_closed && pl.vertices.len() >= 3 =>
|
|
|
|
|
{
|
|
|
|
|
loops.push(
|
|
|
|
|
pl.vertices
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|v| Vector3::new(v.location.x, v.location.y, 0.0))
|
|
|
|
|
.collect(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
acadrust::EntityType::Circle(c) => {
|
|
|
|
|
let n = 64;
|
|
|
|
|
loops.push(
|
|
|
|
|
(0..n)
|
|
|
|
|
.map(|k| {
|
|
|
|
|
let a = std::f64::consts::TAU * k as f64 / n as f64;
|
|
|
|
|
Vector3::new(
|
|
|
|
|
c.center.x + c.radius * a.cos(),
|
|
|
|
|
c.center.y + c.radius * a.sin(),
|
|
|
|
|
c.center.z,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if loops.is_empty() {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_error("REGION: select closed polylines or circles.");
|
|
|
|
|
} else {
|
|
|
|
|
self.push_undo_snapshot(i, "REGION");
|
|
|
|
|
let count = loops.len();
|
|
|
|
|
for pts in loops {
|
|
|
|
|
let mut w = Wire::new();
|
|
|
|
|
let first = pts.first().copied().unwrap_or(Vector3::new(0.0, 0.0, 0.0));
|
|
|
|
|
w.points = pts;
|
|
|
|
|
let mut r = Region::new();
|
|
|
|
|
r.point_of_reference = first;
|
|
|
|
|
r.wires = vec![w];
|
|
|
|
|
r.common.layer = self.tabs[i].active_layer.clone();
|
|
|
|
|
self.tabs[i].scene.add_entity(acadrust::EntityType::Region(r));
|
|
|
|
|
}
|
|
|
|
|
self.tabs[i].dirty = true;
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_output(&format!("REGION: created {count} region(s)."));
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
|
|
|
|
|
// PYRAMID <radius> <height> [sides] — create an n-sided pyramid mesh.
|
2026-07-16 14:43:34 +03:00
|
|
|
"PYRAMID" | "PYR" => {
|
|
|
|
|
use crate::command::TwoValuePromptCommand;
|
|
|
|
|
let c = TwoValuePromptCommand::new(
|
|
|
|
|
"PYRAMID",
|
|
|
|
|
"PYRAMID base radius:",
|
|
|
|
|
"PYRAMID height (add sides by typing a 3rd number):",
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("PYRAMID ") || cmd.starts_with("PYR ") => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let nums: Vec<f64> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.filter_map(|s| s.parse::<f64>().ok())
|
|
|
|
|
.collect();
|
|
|
|
|
if nums.len() >= 2 && nums[0] > 0.0 && nums[1] > 0.0 {
|
|
|
|
|
let sides = nums.get(2).map(|s| *s as usize).unwrap_or(4);
|
|
|
|
|
return Some(self.solid_pyramid(nums[0], nums[1], sides));
|
|
|
|
|
}
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_info("Usage: PYRAMID <radius> <height> [sides] (default 4 sides)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SECTION [X|Y|Z] <value> — draw the cross-section outline of the solid.
|
2026-07-16 14:43:34 +03:00
|
|
|
"SECTION" => {
|
|
|
|
|
use crate::command::SelectThenKeywordCommand;
|
|
|
|
|
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
|
|
|
|
let c = SelectThenKeywordCommand::new(
|
|
|
|
|
"SECTION",
|
|
|
|
|
"SECTION cutting-plane axis [X / Y / Z]:",
|
|
|
|
|
vec![
|
|
|
|
|
("X", "X", Some("SECTION offset along X:")),
|
|
|
|
|
("Y", "Y", Some("SECTION offset along Y:")),
|
|
|
|
|
("Z", "Z", Some("SECTION offset along Z:")),
|
|
|
|
|
],
|
|
|
|
|
has_sel,
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("SECTION ") => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let parts: Vec<String> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.map(|s| s.to_uppercase())
|
|
|
|
|
.collect();
|
|
|
|
|
let (axis, val_idx) = match parts.first().map(String::as_str) {
|
|
|
|
|
Some("X") => (0, 1),
|
|
|
|
|
Some("Y") => (1, 1),
|
|
|
|
|
Some("Z") => (2, 1),
|
|
|
|
|
_ => (2, 0),
|
|
|
|
|
};
|
|
|
|
|
match parts.get(val_idx).and_then(|s| s.parse::<f64>().ok()) {
|
|
|
|
|
Some(v) => return Some(self.solid_section(axis, v)),
|
|
|
|
|
None => self.command_line.push_info(
|
|
|
|
|
"Usage: SECTION [X|Y|Z] <value> (cross-sections the selected solid)",
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3DALIGN <18 numbers> — align the selected solid by 3 source→3 dest points.
|
|
|
|
|
cmd if cmd == "3DALIGN"
|
|
|
|
|
|| cmd == "ALIGN3D"
|
|
|
|
|
|| cmd.starts_with("3DALIGN ")
|
|
|
|
|
|| cmd.starts_with("ALIGN3D ") =>
|
|
|
|
|
{
|
|
|
|
|
let n: Vec<f64> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.filter_map(|s| s.parse::<f64>().ok())
|
|
|
|
|
.collect();
|
|
|
|
|
if n.len() >= 18 {
|
|
|
|
|
let src = [[n[0], n[1], n[2]], [n[3], n[4], n[5]], [n[6], n[7], n[8]]];
|
|
|
|
|
let dst = [
|
|
|
|
|
[n[9], n[10], n[11]],
|
|
|
|
|
[n[12], n[13], n[14]],
|
|
|
|
|
[n[15], n[16], n[17]],
|
|
|
|
|
];
|
|
|
|
|
return Some(self.solid_align3d(src, dst));
|
|
|
|
|
}
|
|
|
|
|
self.command_line.push_info(
|
|
|
|
|
"Usage: 3DALIGN <sx1 sy1 sz1 … sx3 sy3 sz3 dx1 dy1 dz1 … dx3 dy3 dz3> (18 numbers: 3 source then 3 destination points)",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3DMIRROR [X|Y|Z] — add a mirror of the selected solid across a plane.
|
2026-07-16 14:43:34 +03:00
|
|
|
"3DMIRROR" | "MIRROR3D" => {
|
|
|
|
|
use crate::command::SelectThenKeywordCommand;
|
|
|
|
|
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
|
|
|
|
let c = SelectThenKeywordCommand::new(
|
|
|
|
|
"3DMIRROR",
|
|
|
|
|
"3DMIRROR mirror plane [X / Y / Z]:",
|
|
|
|
|
vec![("X", "X", None), ("Y", "Y", None), ("Z", "Z", None)],
|
|
|
|
|
has_sel,
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("3DMIRROR ") || cmd.starts_with("MIRROR3D ") => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let parts: Vec<String> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.map(|s| s.to_uppercase())
|
|
|
|
|
.collect();
|
|
|
|
|
let axis = match parts.first().map(String::as_str) {
|
|
|
|
|
Some("X") => 0,
|
|
|
|
|
Some("Y") => 1,
|
|
|
|
|
Some("Z") => 2,
|
|
|
|
|
_ => {
|
|
|
|
|
self.command_line.push_info(
|
|
|
|
|
"Usage: 3DMIRROR [X|Y|Z] (mirrors the selected solid across that plane)",
|
|
|
|
|
);
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return Some(self.solid_mirror3d(axis));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3DROTATE [X|Y|Z] <angle> — rotate the selected solid about an axis.
|
2026-07-16 14:43:34 +03:00
|
|
|
"3DROTATE" | "ROTATE3D" => {
|
|
|
|
|
use crate::command::SelectThenKeywordCommand;
|
|
|
|
|
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
|
|
|
|
let c = SelectThenKeywordCommand::new(
|
|
|
|
|
"3DROTATE",
|
|
|
|
|
"3DROTATE rotation axis [X / Y / Z]:",
|
|
|
|
|
vec![
|
|
|
|
|
("X", "X", Some("3DROTATE angle in degrees:")),
|
|
|
|
|
("Y", "Y", Some("3DROTATE angle in degrees:")),
|
|
|
|
|
("Z", "Z", Some("3DROTATE angle in degrees:")),
|
|
|
|
|
],
|
|
|
|
|
has_sel,
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("3DROTATE ") || cmd.starts_with("ROTATE3D ") => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let parts: Vec<String> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.map(|s| s.to_uppercase())
|
|
|
|
|
.collect();
|
|
|
|
|
let axis = match parts.first().map(String::as_str) {
|
|
|
|
|
Some("X") => 0,
|
|
|
|
|
Some("Y") => 1,
|
|
|
|
|
_ => 2,
|
|
|
|
|
};
|
|
|
|
|
let angle: Option<f64> = parts.iter().find_map(|s| s.parse::<f64>().ok());
|
|
|
|
|
match angle {
|
|
|
|
|
Some(a) => return Some(self.solid_rotate3d(axis, a)),
|
|
|
|
|
None => self.command_line.push_info(
|
|
|
|
|
"Usage: 3DROTATE [X|Y|Z] <angle> (rotates the selected solid)",
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SLICE [X|Y|Z] <value> [TOP|BOTTOM] — cut the selected solid with an
|
|
|
|
|
// axis-aligned plane, keeping the lower half by default.
|
2026-07-16 14:43:34 +03:00
|
|
|
"SLICE" | "SL" => {
|
|
|
|
|
use crate::command::SelectThenKeywordCommand;
|
|
|
|
|
let has_sel = !self.tabs[i].scene.selected_entities().is_empty();
|
|
|
|
|
let c = SelectThenKeywordCommand::new(
|
|
|
|
|
"SLICE",
|
|
|
|
|
"SLICE cutting-plane axis [X / Y / Z] (add TOP/BOTTOM by typing):",
|
|
|
|
|
vec![
|
|
|
|
|
("X", "X", Some("SLICE offset along X:")),
|
|
|
|
|
("Y", "Y", Some("SLICE offset along Y:")),
|
|
|
|
|
("Z", "Z", Some("SLICE offset along Z:")),
|
|
|
|
|
],
|
|
|
|
|
has_sel,
|
|
|
|
|
);
|
|
|
|
|
self.command_line.push_info(&c.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
|
|
|
}
|
|
|
|
|
cmd if cmd.starts_with("SLICE ") || cmd.starts_with("SL ") => {
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let parts: Vec<String> = cmd
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.map(|s| s.to_uppercase())
|
|
|
|
|
.collect();
|
|
|
|
|
let (axis, val_idx) = match parts.first().map(String::as_str) {
|
|
|
|
|
Some("X") => (0, 1),
|
|
|
|
|
Some("Y") => (1, 1),
|
|
|
|
|
Some("Z") => (2, 1),
|
|
|
|
|
_ => (2, 0), // default Z plane
|
|
|
|
|
};
|
|
|
|
|
let value: Option<f64> = parts.get(val_idx).and_then(|s| s.parse().ok());
|
|
|
|
|
let keep_low = !parts.iter().any(|s| s == "TOP");
|
|
|
|
|
match value {
|
|
|
|
|
Some(v) => return Some(self.solid_slice(axis, v, keep_low)),
|
|
|
|
|
None => self.command_line.push_info(
|
|
|
|
|
"Usage: SLICE [X|Y|Z] <value> [TOP|BOTTOM] (cuts the selected solid)",
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
// ── Annotate commands ──────────────────────────────────────────
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"TEXT" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::annotate::text::TextCommand;
|
|
|
|
|
let new_cmd = TextCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"DDEDIT" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::annotate::ddedit::DdeditCommand;
|
|
|
|
|
// A single text entity already selected opens its in-place
|
|
|
|
|
// editor directly; otherwise prompt for a pick.
|
|
|
|
|
let sel = self.tabs[i].scene.selected_entities();
|
|
|
|
|
let editable = (sel.len() == 1).then(|| sel[0].0).filter(|h| {
|
|
|
|
|
self.tabs[i].scene.document.get_entity(*h).is_some_and(|e| {
|
|
|
|
|
super::super::text_inline::read_text_field(e).is_some()
|
|
|
|
|
|| matches!(e, acadrust::EntityType::Leader(_))
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
if let Some(h) = editable {
|
|
|
|
|
return Some(self.begin_text_edit(h));
|
|
|
|
|
}
|
|
|
|
|
if sel.len() == 1 {
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_error("DDEDIT: selected entity is not text.");
|
|
|
|
|
} else {
|
|
|
|
|
let cmd = DdeditCommand::new();
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(alias): centralize command aliases in user-editable ocad.pgp (#286/#288)
Command aliases (L→LINE, CC→COPYCLIP) were scattered across dispatch match
arms and CommandRegistration name arrays. Move them all into a single
user-editable file:
- assets/ocad.pgp: shipped default alias table (ALIAS,*COMMAND format),
embedded and copied to config/ocad.pgp on first launch; only that user
copy is read after. AA→AREA default closes #286.
- resolve_alias() rewrites the leading command token at the top of
dispatch_command_inner, so aliases resolve for every path (families,
plugins, Repeat menu). Args after the first space are untouched.
- Autocomplete hides alias keys and, when the whole input is an alias,
surfaces its target command as the top (highlighted) suggestion so the
highlight matches what Enter runs (AA→AREA, L→LINE).
- ALIASEDIT opens a new editor modal (add/edit/delete rows) with an Apply
button; Apply persists to ocad.pgp, closing discards unapplied edits.
- Strip alias tokens from all 8 dispatch match-arm files and 71 module
CommandRegistration arrays, keeping canonical/synonym/distinct-command
names. ocad.pgp is now the sole source: deleting or remapping an alias
there takes effect. CUI stays on the keyboard-shortcut panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:39:22 +03:00
|
|
|
"MTEXT" => {
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
use crate::modules::annotate::mtext::MTextCommand;
|
|
|
|
|
let new_cmd = MTextCommand::new();
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"TEXTEDIT" | "TEDIT" => {
|
|
|
|
|
use crate::modules::annotate::textedit::TexteditCommand;
|
feat(commands): expand standard CAD command coverage
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 08:23:58 +03:00
|
|
|
let mode_str = if self.texteditmode {
|
|
|
|
|
"Single"
|
|
|
|
|
} else {
|
|
|
|
|
"Multiple"
|
|
|
|
|
};
|
|
|
|
|
self.command_line
|
|
|
|
|
.push_output(&format!("Current settings: Edit mode = {}", mode_str));
|
refactor: split four oversized files into submodule trees
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:51:29 +03:00
|
|
|
let new_cmd = TexteditCommand::new(self.texteditmode);
|
|
|
|
|
self.command_line.push_info(&new_cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"TEXTEDITMODE" => {
|
|
|
|
|
use crate::modules::annotate::textedit::TexteditmodeCommand;
|
|
|
|
|
let cmd = TexteditmodeCommand::new(self.texteditmode);
|
|
|
|
|
self.command_line.push_info(&cmd.prompt());
|
|
|
|
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ => return None,
|
|
|
|
|
}
|
|
|
|
|
Some(self.finish_dispatch(cmd))
|
|
|
|
|
}
|
|
|
|
|
}
|