forked from 1b-agent/opencadstudio-upstream
fix(scene): use layout-specific viewport classification and preserve duplicate AutoCAD *Active viewports
This commit is contained in:
1 parent
47a4bf19b3
commit
4a55cb1b0f
1 file changed
+90
-74
+90
-74
@@ -922,6 +922,35 @@ impl Scene {
|
|||||||
vp.center.x.abs() >= 0.5 || vp.center.y.abs() >= 0.5
|
vp.center.x.abs() >= 0.5 || vp.center.y.abs() >= 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_layout_sheet_viewport_handle(&self) -> Handle {
|
||||||
|
self.document.objects.values().find_map(|obj| {
|
||||||
|
let ObjectType::Layout(layout) = obj else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if layout.name == self.current_layout {
|
||||||
|
Some(layout.viewport)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}).unwrap_or(Handle::NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_content_viewport_in_layout(
|
||||||
|
&self,
|
||||||
|
vp: &acadrust::entities::Viewport,
|
||||||
|
layout_block: Handle,
|
||||||
|
) -> bool {
|
||||||
|
if vp.common.owner_handle != layout_block {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let sheet_handle = self.current_layout_sheet_viewport_handle();
|
||||||
|
if sheet_handle.is_valid() {
|
||||||
|
vp.common.handle != sheet_handle
|
||||||
|
} else {
|
||||||
|
Self::is_content_viewport(vp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Public accessor for the block-record handle of the current layout.
|
/// Public accessor for the block-record handle of the current layout.
|
||||||
/// Used by external callers (e.g. `commit_entity`) that need the handle
|
/// Used by external callers (e.g. `commit_entity`) that need the handle
|
||||||
/// without going through private API.
|
/// without going through private API.
|
||||||
@@ -1081,7 +1110,7 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
self.document.entities().find_map(|e| {
|
self.document.entities().find_map(|e| {
|
||||||
if let EntityType::Viewport(vp) = e {
|
if let EntityType::Viewport(vp) = e {
|
||||||
if Self::is_content_viewport(vp) && vp.common.owner_handle == layout_block {
|
if self.is_content_viewport_in_layout(vp, layout_block) {
|
||||||
return Some(vp_effective_scale(
|
return Some(vp_effective_scale(
|
||||||
vp.custom_scale,
|
vp.custom_scale,
|
||||||
vp.view_height,
|
vp.view_height,
|
||||||
@@ -1107,7 +1136,7 @@ impl Scene {
|
|||||||
.entities()
|
.entities()
|
||||||
.filter_map(|e| {
|
.filter_map(|e| {
|
||||||
if let EntityType::Viewport(vp) = e {
|
if let EntityType::Viewport(vp) = e {
|
||||||
if Self::is_content_viewport(vp) && vp.common.owner_handle == layout_block {
|
if self.is_content_viewport_in_layout(vp, layout_block) {
|
||||||
Some((vp.common.handle, vp.id, vp.frozen_layers.clone()))
|
Some((vp.common.handle, vp.id, vp.frozen_layers.clone()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -1145,7 +1174,7 @@ impl Scene {
|
|||||||
.entities()
|
.entities()
|
||||||
.filter(|e| {
|
.filter(|e| {
|
||||||
if let EntityType::Viewport(vp) = e {
|
if let EntityType::Viewport(vp) = e {
|
||||||
Self::is_content_viewport(vp) && vp.common.owner_handle == layout_block
|
self.is_content_viewport_in_layout(vp, layout_block)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -1173,7 +1202,7 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
self.document.entities().find_map(|e| {
|
self.document.entities().find_map(|e| {
|
||||||
if let EntityType::Viewport(vp) = e {
|
if let EntityType::Viewport(vp) = e {
|
||||||
if Self::is_content_viewport(vp) && vp.common.owner_handle == layout_block {
|
if self.is_content_viewport_in_layout(vp, layout_block) {
|
||||||
return Some(vp.common.handle);
|
return Some(vp.common.handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2187,8 +2216,7 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(|vp| {
|
.filter(|vp| {
|
||||||
Self::is_content_viewport(vp)
|
self.is_content_viewport_in_layout(vp, paper_block)
|
||||||
&& vp.common.owner_handle == paper_block
|
|
||||||
&& vp.status.is_on
|
&& vp.status.is_on
|
||||||
&& only_vp.map_or(true, |h| vp.common.handle == h)
|
&& only_vp.map_or(true, |h| vp.common.handle == h)
|
||||||
&& exclude_vp.map_or(true, |h| vp.common.handle != h)
|
&& exclude_vp.map_or(true, |h| vp.common.handle != h)
|
||||||
@@ -2668,8 +2696,7 @@ impl Scene {
|
|||||||
let EntityType::Viewport(vp) = e else {
|
let EntityType::Viewport(vp) = e else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if !Self::is_content_viewport(vp)
|
if !self.is_content_viewport_in_layout(vp, layout_block)
|
||||||
|| vp.common.owner_handle != layout_block
|
|
||||||
|| !vp.status.is_on
|
|| !vp.status.is_on
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
@@ -2694,8 +2721,7 @@ impl Scene {
|
|||||||
let EntityType::Viewport(vp) = e else {
|
let EntityType::Viewport(vp) = e else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if Self::is_content_viewport(vp)
|
if self.is_content_viewport_in_layout(vp, layout_block)
|
||||||
&& vp.common.owner_handle == layout_block
|
|
||||||
&& vp.status.is_on
|
&& vp.status.is_on
|
||||||
{
|
{
|
||||||
Some(vp.common.handle)
|
Some(vp.common.handle)
|
||||||
@@ -4690,66 +4716,49 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restore `model_tiles` from `*OCS_Tile_*` VPort entries that a previous
|
/// Restore `model_tiles` from VPort entries that a previous save left in
|
||||||
/// save left in the document. Returns true on success — the caller skips
|
/// the document. Native AutoCAD tiled model-space layouts are represented
|
||||||
/// `apply_active_vport_camera` in that case because the active tile's
|
/// by duplicate `*Active` VPort entries.
|
||||||
/// camera has already been loaded into `self.camera`.
|
/// Returns true on success — the caller skips `apply_active_vport_camera`
|
||||||
|
/// in that case because the active tile's camera has already been loaded
|
||||||
|
/// into `self.camera`.
|
||||||
fn restore_model_tiles_from_vports(&mut self) -> bool {
|
fn restore_model_tiles_from_vports(&mut self) -> bool {
|
||||||
let mut indexed: Vec<(usize, acadrust::tables::VPort)> = self
|
let active_vports: Vec<acadrust::tables::VPort> = self
|
||||||
.document
|
.document
|
||||||
.vports
|
.vports
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|v| {
|
.filter(|v| v.name == "*Active")
|
||||||
let idx: usize = v
|
.cloned()
|
||||||
.name
|
|
||||||
.strip_prefix("*OCS_Tile_")
|
|
||||||
.and_then(|s| s.parse().ok())?;
|
|
||||||
Some((idx, v.clone()))
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
if indexed.len() < 2 {
|
|
||||||
|
if active_vports.len() <= 1 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
indexed.sort_by_key(|(i, _)| *i);
|
|
||||||
let mut tiles = Vec::with_capacity(indexed.len());
|
let tiles: Vec<ModelTile> = active_vports
|
||||||
for (_, vp) in &indexed {
|
|
||||||
let Some(cam) = self.camera_from_vport(vp) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
tiles.push(ModelTile {
|
|
||||||
rect: Self::vport_to_tile_rect(vp.lower_left, vp.upper_right),
|
|
||||||
camera: cam,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Active tile = the one whose stored camera fields match `*Active`.
|
|
||||||
// The save path writes `*Active` to mirror the active tile's view, so
|
|
||||||
// this matches by view_target (the most discriminating field).
|
|
||||||
let active_idx = self
|
|
||||||
.document
|
|
||||||
.vports
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|v| v.name == "*Active")
|
.filter_map(|vp| {
|
||||||
.and_then(|act| {
|
self.camera_from_vport(vp).map(|cam| ModelTile {
|
||||||
indexed.iter().position(|(_, vp)| {
|
rect: Self::vport_to_tile_rect(vp.lower_left, vp.upper_right),
|
||||||
let dx = vp.view_target.x - act.view_target.x;
|
camera: cam,
|
||||||
let dy = vp.view_target.y - act.view_target.y;
|
|
||||||
let dz = vp.view_target.z - act.view_target.z;
|
|
||||||
dx * dx + dy * dy + dz * dz < 1e-12
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or(0);
|
.collect();
|
||||||
let active_cam = tiles[active_idx].camera.clone();
|
|
||||||
|
if tiles.len() <= 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let active_cam = tiles[0].camera.clone();
|
||||||
*self.model_tiles.borrow_mut() = tiles;
|
*self.model_tiles.borrow_mut() = tiles;
|
||||||
self.active_model_tile.set(active_idx);
|
self.active_model_tile.set(0);
|
||||||
*self.camera.borrow_mut() = active_cam;
|
*self.camera.borrow_mut() = active_cam;
|
||||||
self.camera_generation += 1;
|
self.camera_generation += 1;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persist `model_tiles` to the VPort table. The active tile is also
|
/// Persist `model_tiles` to the VPort table. Native AutoCAD tiled model
|
||||||
/// mirrored to `*Active` (already written by `sync_camera_to_document`);
|
/// viewports are written as duplicate `*Active` entries.
|
||||||
/// every tile (including a single full-screen one) is written as a
|
|
||||||
/// `*OCS_Tile_<i>` entry so the layout survives roundtrip.
|
|
||||||
fn save_model_tiles_to_vports(&mut self) {
|
fn save_model_tiles_to_vports(&mut self) {
|
||||||
// Stash the live camera into the active tile so the about-to-write
|
// Stash the live camera into the active tile so the about-to-write
|
||||||
// snapshot reflects the user's most recent orbit / pan / zoom.
|
// snapshot reflects the user's most recent orbit / pan / zoom.
|
||||||
@@ -4761,29 +4770,40 @@ impl Scene {
|
|||||||
t.camera = live_cam;
|
t.camera = live_cam;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove every previous tile entry — names are reassigned by index.
|
|
||||||
let to_remove: Vec<String> = self
|
let table_handle = self.document.vports.handle();
|
||||||
|
let preserved_vps: Vec<acadrust::tables::VPort> = self
|
||||||
.document
|
.document
|
||||||
.vports
|
.vports
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|v| v.name.starts_with("*OCS_Tile_"))
|
.filter(|v| v.name != "*Active")
|
||||||
.map(|v| v.name.clone())
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
for name in to_remove {
|
let mut new_vports = acadrust::tables::Table::with_handle(table_handle);
|
||||||
self.document.vports.remove(&name);
|
for vp in preserved_vps {
|
||||||
|
new_vports.add_or_replace(vp);
|
||||||
}
|
}
|
||||||
// Skip writing tile entries for the trivial single-tile config —
|
self.document.vports = new_vports;
|
||||||
// `*Active` alone is enough and keeps the file noise-free.
|
|
||||||
let tiles = self.model_tiles.borrow().clone();
|
let tiles = self.model_tiles.borrow().clone();
|
||||||
if tiles.len() <= 1 {
|
if tiles.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let active = self.active_model_tile.get().min(tiles.len().saturating_sub(1));
|
||||||
|
let mut ordered_tiles = Vec::with_capacity(tiles.len());
|
||||||
|
ordered_tiles.push(tiles[active].clone());
|
||||||
for (i, tile) in tiles.iter().enumerate() {
|
for (i, tile) in tiles.iter().enumerate() {
|
||||||
|
if i != active {
|
||||||
|
ordered_tiles.push(tile.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for tile in ordered_tiles {
|
||||||
let (ll, ur) = Self::tile_rect_to_vport(tile.rect);
|
let (ll, ur) = Self::tile_rect_to_vport(tile.rect);
|
||||||
let name = format!("*OCS_Tile_{i}");
|
let mut entry = self.vport_from_camera("*Active", &tile.camera, ll, ur);
|
||||||
let mut entry = self.vport_from_camera(&name, &tile.camera, ll, ur);
|
|
||||||
entry.handle = self.document.allocate_handle();
|
entry.handle = self.document.allocate_handle();
|
||||||
self.document.vports.add_or_replace(entry);
|
self.document.vports.add_allow_duplicate(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4817,7 +4837,7 @@ impl Scene {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.find(|vp| vp.common.owner_handle == layout_block && !Self::is_content_viewport(vp));
|
.find(|vp| !self.is_content_viewport_in_layout(vp, layout_block));
|
||||||
|
|
||||||
let vp = match sheet_vp {
|
let vp = match sheet_vp {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
@@ -4906,9 +4926,7 @@ impl Scene {
|
|||||||
vp.view_height = view_height as f64;
|
vp.view_height = view_height as f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist the tiled layout (multiple `*OCS_Tile_<i>` entries) so
|
// Persist the tiled layout as duplicate `*Active` VPort entries.
|
||||||
// the panel split survives save→reopen. Single-tile configs skip
|
|
||||||
// this — `*Active` alone is enough.
|
|
||||||
self.save_model_tiles_to_vports();
|
self.save_model_tiles_to_vports();
|
||||||
|
|
||||||
// Also write to View table — survives DWG save without override.
|
// Also write to View table — survives DWG save without override.
|
||||||
@@ -4936,7 +4954,7 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.find(|vp| {
|
.find(|vp| {
|
||||||
vp.common.owner_handle == layout_block && !Self::is_content_viewport(vp)
|
vp.common.owner_handle == layout_block && !self.is_content_viewport_in_layout(vp, layout_block)
|
||||||
})
|
})
|
||||||
.map(|vp| vp.common.handle);
|
.map(|vp| vp.common.handle);
|
||||||
|
|
||||||
@@ -5610,8 +5628,7 @@ impl Scene {
|
|||||||
let EntityType::Viewport(vp) = e else {
|
let EntityType::Viewport(vp) = e else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if !Self::is_content_viewport(vp)
|
if !self.is_content_viewport_in_layout(vp, layout_block)
|
||||||
|| vp.common.owner_handle != layout_block
|
|
||||||
|| !vp.status.is_on
|
|| !vp.status.is_on
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -6995,4 +7012,3 @@ fn is_unindexable_entity(e: &acadrust::EntityType) -> bool {
|
|||||||
E::Insert(_) | E::Viewport(_) | E::Block(_) | E::BlockEnd(_)
|
E::Insert(_) | E::Viewport(_) | E::Block(_) | E::BlockEnd(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user