feat: VPORTS preset viewport configurations (2H / 2V / 4 / SINGLE)
VPORTS now accepts a layout subcommand in paper-space: VPORTS 2H — two viewports side by side (horizontal split) VPORTS 2V — two viewports stacked (vertical split) VPORTS 4 — 2×2 grid of four equal viewports VPORTS SINGLE — one full-page viewport Paper dimensions are read from PlotSettings (fallback A4 landscape). Existing user viewports are replaced and each new viewport is auto-fitted to show model-space content. Bare VPORTS still lists existing viewports. Closes ROADMAP 8.5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
28f5c84b09
commit
ff39759d02
1 changed files with 126 additions and 4 deletions
|
|
@ -2079,12 +2079,14 @@ impl H7CAD {
|
||||||
return Task::done(Message::PspaceCommand);
|
return Task::done(Message::PspaceCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── VPORTS — list viewports in current layout ─────────────────
|
// ── VPORTS — list or create preset viewport configurations ────
|
||||||
"VPORTS" => {
|
cmd if cmd == "VPORTS" || cmd.starts_with("VPORTS ") => {
|
||||||
|
let sub = cmd.split_whitespace().nth(1).unwrap_or("").to_uppercase();
|
||||||
let scene = &self.tabs[i].scene;
|
let scene = &self.tabs[i].scene;
|
||||||
if scene.current_layout == "Model" {
|
if scene.current_layout == "Model" {
|
||||||
self.command_line.push_error("VPORTS: switch to a paper space layout first.");
|
self.command_line.push_error("VPORTS: switch to a paper space layout first.");
|
||||||
} else {
|
} else if sub.is_empty() {
|
||||||
|
// ── List existing viewports ──────────────────────────
|
||||||
let layout_block = scene.current_layout_block_handle_pub();
|
let layout_block = scene.current_layout_block_handle_pub();
|
||||||
let viewports: Vec<_> = scene.document.entities()
|
let viewports: Vec<_> = scene.document.entities()
|
||||||
.filter_map(|e| {
|
.filter_map(|e| {
|
||||||
|
|
@ -2096,7 +2098,7 @@ impl H7CAD {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if viewports.is_empty() {
|
if viewports.is_empty() {
|
||||||
self.command_line.push_info("No viewports in current layout. Use MVIEW to create one.");
|
self.command_line.push_info("No viewports. Use MVIEW to create one, or VPORTS 2H / 2V / 4 / SINGLE.");
|
||||||
} else {
|
} else {
|
||||||
self.command_line.push_output(&format!("{} viewport(s) in layout \"{}\":", viewports.len(), scene.current_layout));
|
self.command_line.push_output(&format!("{} viewport(s) in layout \"{}\":", viewports.len(), scene.current_layout));
|
||||||
for (id, center, w, h, scale, is_on, locked) in &viewports {
|
for (id, center, w, h, scale, is_on, locked) in &viewports {
|
||||||
|
|
@ -2111,6 +2113,126 @@ impl H7CAD {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// ── Preset viewport layout ───────────────────────────
|
||||||
|
// Determine paper dimensions from PlotSettings (fallback A4 landscape).
|
||||||
|
let layout_name = scene.current_layout.clone();
|
||||||
|
let (paper_w, paper_h) = {
|
||||||
|
use acadrust::objects::ObjectType;
|
||||||
|
let mut pw = 297.0_f64;
|
||||||
|
let mut ph = 210.0_f64;
|
||||||
|
for (_, obj) in &scene.document.objects {
|
||||||
|
if let ObjectType::PlotSettings(ps) = obj {
|
||||||
|
if ps.page_name == layout_name && ps.paper_width > 0.0 {
|
||||||
|
pw = ps.paper_width;
|
||||||
|
ph = ps.paper_height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(pw, ph)
|
||||||
|
};
|
||||||
|
let margin = 5.0_f64; // mm margin around the usable area
|
||||||
|
let uw = paper_w - 2.0 * margin; // usable width
|
||||||
|
let uh = paper_h - 2.0 * margin; // usable height
|
||||||
|
// Collect rectangle specs: (cx, cz, w, h) in mm
|
||||||
|
let rects: Vec<(f64, f64, f64, f64)> = match sub.as_str() {
|
||||||
|
"2H" => {
|
||||||
|
// Two viewports side by side (horizontal split)
|
||||||
|
let vw = (uw - 2.0) / 2.0;
|
||||||
|
vec![
|
||||||
|
(margin + vw / 2.0, margin + uh / 2.0, vw, uh),
|
||||||
|
(margin + vw + 2.0 + vw / 2.0, margin + uh / 2.0, vw, uh),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"2V" => {
|
||||||
|
// Two viewports stacked (vertical split)
|
||||||
|
let vh = (uh - 2.0) / 2.0;
|
||||||
|
vec![
|
||||||
|
(margin + uw / 2.0, margin + vh + 2.0 + vh / 2.0, uw, vh),
|
||||||
|
(margin + uw / 2.0, margin + vh / 2.0, uw, vh),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"4" => {
|
||||||
|
// Four equal viewports (2×2 grid)
|
||||||
|
let vw = (uw - 2.0) / 2.0;
|
||||||
|
let vh = (uh - 2.0) / 2.0;
|
||||||
|
vec![
|
||||||
|
(margin + vw / 2.0, margin + vh + 2.0 + vh / 2.0, vw, vh),
|
||||||
|
(margin + vw + 2.0 + vw / 2.0, margin + vh + 2.0 + vh / 2.0, vw, vh),
|
||||||
|
(margin + vw / 2.0, margin + vh / 2.0, vw, vh),
|
||||||
|
(margin + vw + 2.0 + vw / 2.0, margin + vh / 2.0, vw, vh),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"SINGLE" | "1" => {
|
||||||
|
// Single full-page viewport
|
||||||
|
vec![(margin + uw / 2.0, margin + uh / 2.0, uw, uh)]
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.command_line.push_error(
|
||||||
|
"VPORTS: unknown option. Use VPORTS 2H | 2V | 4 | SINGLE"
|
||||||
|
);
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !rects.is_empty() {
|
||||||
|
// Remove existing user viewports in this layout first.
|
||||||
|
let layout_block = self.tabs[i].scene.current_layout_block_handle_pub();
|
||||||
|
let to_erase: Vec<acadrust::Handle> = self.tabs[i].scene.document.entities()
|
||||||
|
.filter_map(|e| {
|
||||||
|
if let acadrust::EntityType::Viewport(vp) = e {
|
||||||
|
if vp.id > 1 && vp.common.owner_handle == layout_block {
|
||||||
|
Some(vp.common.handle)
|
||||||
|
} else { None }
|
||||||
|
} else { None }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.push_undo_snapshot(i, "VPORTS");
|
||||||
|
self.tabs[i].scene.erase_entities(&to_erase);
|
||||||
|
// Create new viewports.
|
||||||
|
for (cx, cz, w, h) in &rects {
|
||||||
|
let mut vp = acadrust::entities::Viewport::new();
|
||||||
|
vp.center = acadrust::types::Vector3::new(*cx, 0.0, *cz);
|
||||||
|
vp.width = *w;
|
||||||
|
vp.height = *h;
|
||||||
|
vp.id = 2; // commit_entity will assign unique IDs
|
||||||
|
match self.tabs[i].scene.document.add_entity_to_layout(
|
||||||
|
acadrust::EntityType::Viewport(vp),
|
||||||
|
&layout_name,
|
||||||
|
) {
|
||||||
|
Ok(handle) => {
|
||||||
|
self.tabs[i].scene.auto_fit_viewport(handle);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.command_line.push_error(&format!("VPORTS: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Re-assign unique IDs (1 + existing max per viewport).
|
||||||
|
let layout_block2 = self.tabs[i].scene.current_layout_block_handle_pub();
|
||||||
|
let mut id_counter = 2_i16;
|
||||||
|
let handles: Vec<acadrust::Handle> = self.tabs[i].scene.document.entities()
|
||||||
|
.filter_map(|e| {
|
||||||
|
if let acadrust::EntityType::Viewport(vp) = e {
|
||||||
|
if vp.id >= 2 && vp.common.owner_handle == layout_block2 {
|
||||||
|
Some(vp.common.handle)
|
||||||
|
} else { None }
|
||||||
|
} else { None }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for h in handles {
|
||||||
|
if let Some(acadrust::EntityType::Viewport(vp)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(h)
|
||||||
|
{
|
||||||
|
vp.id = id_counter;
|
||||||
|
id_counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.command_line.push_output(&format!(
|
||||||
|
"VPORTS: created {} viewport(s) [{}].", rects.len(), sub
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue