#249 — plugin XDATA written via write_record survived only in memory: the acadrust DWG writer dropped ExtendedData::records on save. Bump acadrust to e88a9a6 (records now encode to EED and decode back on read) and fix the host side that fed it: - ensure_app_id allocates a real APPID handle; a null handle serializes as 0 and the EED reference can't resolve, so the XDATA vanished on reopen. - write_record / remove_record drop stale raw_dwg_eed for the target app so an edit made after a save/reopen wins over the pre-edit bytes. #250 — out-of-process plugins got a throwaway document_mut() snapshot, so edits to existing entities were silently discarded and deletion wasn't expressible at all. Add the missing mutation surface: - UpdateEntity / RemoveEntity IPC requests + HostApi::update_entity / remove_entity (default in-process impls, RPC overrides on the client that invalidate the stale document cache). - Scene::update_entity replaces the entity in place, preserving its handle and owning block, and reseeds only its derived caches; remove reuses the cache-coherent erase_entities (which also honours layer locks). - document_mut() is documented as a local-only snapshot out-of-process. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a763144622
commit
f9f7138370
7 changed files with 295 additions and 4 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -71,7 +71,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "acadrust"
|
name = "acadrust"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#4ae2ceb72f00b69e983b20111c809aecf138814c"
|
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#e88a9a6afdcf20db4c1f3140088b2f4b8b851e2b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -120,10 +120,38 @@ pub trait HostApi {
|
||||||
|
|
||||||
// ── Document ────────────────────────────────────────────────────────────
|
// ── Document ────────────────────────────────────────────────────────────
|
||||||
fn document(&self) -> &CadDocument;
|
fn document(&self) -> &CadDocument;
|
||||||
|
/// Mutable access to the active document.
|
||||||
|
///
|
||||||
|
/// For an **out-of-process** plugin this borrows a *local snapshot*: edits
|
||||||
|
/// to existing entities made through it are NOT sent back to the host and
|
||||||
|
/// are silently discarded. To modify or delete entities from any plugin,
|
||||||
|
/// use [`add_entity`](Self::add_entity), [`update_entity`](Self::update_entity)
|
||||||
|
/// and [`remove_entity`](Self::remove_entity), which are committed to the
|
||||||
|
/// host document over IPC.
|
||||||
fn document_mut(&mut self) -> &mut CadDocument;
|
fn document_mut(&mut self) -> &mut CadDocument;
|
||||||
|
|
||||||
/// Add an entity to the active document, returning its handle.
|
/// Add an entity to the active document, returning its handle.
|
||||||
fn add_entity(&mut self, entity: EntityType) -> Handle;
|
fn add_entity(&mut self, entity: EntityType) -> Handle;
|
||||||
|
/// Replace the existing entity that carries `entity`'s handle, preserving
|
||||||
|
/// its identity (handle and owning block). Returns `false` when no entity
|
||||||
|
/// has that handle. This is the sanctioned way to commit in-place edits
|
||||||
|
/// from an out-of-process plugin — mutating `document_mut()` does not work
|
||||||
|
/// across the process boundary.
|
||||||
|
fn update_entity(&mut self, entity: EntityType) -> bool {
|
||||||
|
let handle = entity.common().handle;
|
||||||
|
match self.document_mut().get_entity_mut(handle) {
|
||||||
|
Some(slot) => {
|
||||||
|
*slot = entity;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Delete the entity with `handle` (and any derived render caches). Returns
|
||||||
|
/// `true` when an entity was removed.
|
||||||
|
fn remove_entity(&mut self, handle: Handle) -> bool {
|
||||||
|
self.document_mut().remove_entity(handle).is_some()
|
||||||
|
}
|
||||||
/// Mark the scene geometry dirty so it is re-tessellated next frame.
|
/// Mark the scene geometry dirty so it is re-tessellated next frame.
|
||||||
fn bump_geometry(&mut self);
|
fn bump_geometry(&mut self);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,46 @@ impl HostApi for PluginHostApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_entity(&mut self, entity: EntityType) -> bool {
|
||||||
|
match self.client.request(PluginRequest::UpdateEntity(entity)) {
|
||||||
|
Ok(PluginResponse::Bool(b)) => {
|
||||||
|
if b {
|
||||||
|
// The cached snapshot is now stale; drop it so a later
|
||||||
|
// document() re-fetches the host's post-edit truth.
|
||||||
|
self.document_cache = OnceCell::new();
|
||||||
|
}
|
||||||
|
b
|
||||||
|
}
|
||||||
|
Ok(other) => {
|
||||||
|
eprintln!("[plugin] unexpected UpdateEntity response: {other:?}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[plugin] UpdateEntity failed: {e}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_entity(&mut self, handle: Handle) -> bool {
|
||||||
|
match self.client.request(PluginRequest::RemoveEntity { handle }) {
|
||||||
|
Ok(PluginResponse::Bool(b)) => {
|
||||||
|
if b {
|
||||||
|
self.document_cache = OnceCell::new();
|
||||||
|
}
|
||||||
|
b
|
||||||
|
}
|
||||||
|
Ok(other) => {
|
||||||
|
eprintln!("[plugin] unexpected RemoveEntity response: {other:?}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[plugin] RemoveEntity failed: {e}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn bump_geometry(&mut self) {
|
fn bump_geometry(&mut self) {
|
||||||
let _ = self.client.request(PluginRequest::BumpGeometry);
|
let _ = self.client.request(PluginRequest::BumpGeometry);
|
||||||
}
|
}
|
||||||
|
|
@ -438,4 +478,36 @@ mod tests {
|
||||||
peer_handle.join().unwrap();
|
peer_handle.join().unwrap();
|
||||||
assert_eq!(handle, Handle::new(42));
|
assert_eq!(handle, Handle::new(42));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_entity_awaits_bool_response() {
|
||||||
|
let (mut api, mut peer) = make_client();
|
||||||
|
let peer_handle = thread::spawn(move || {
|
||||||
|
let msg = recv::<PluginToHost>(&mut peer).unwrap();
|
||||||
|
match msg {
|
||||||
|
PluginToHost::Request(PluginRequest::UpdateEntity(_)) => {}
|
||||||
|
other => panic!("unexpected: {other:?}"),
|
||||||
|
}
|
||||||
|
send(&mut peer, &HostToPlugin::Response(PluginResponse::Bool(true))).unwrap();
|
||||||
|
});
|
||||||
|
assert!(api.update_entity(EntityType::Point(Point::new())));
|
||||||
|
peer_handle.join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_entity_awaits_bool_response() {
|
||||||
|
let (mut api, mut peer) = make_client();
|
||||||
|
let peer_handle = thread::spawn(move || {
|
||||||
|
let msg = recv::<PluginToHost>(&mut peer).unwrap();
|
||||||
|
match msg {
|
||||||
|
PluginToHost::Request(PluginRequest::RemoveEntity { handle }) => {
|
||||||
|
assert_eq!(handle, Handle::new(7));
|
||||||
|
}
|
||||||
|
other => panic!("unexpected: {other:?}"),
|
||||||
|
}
|
||||||
|
send(&mut peer, &HostToPlugin::Response(PluginResponse::Bool(true))).unwrap();
|
||||||
|
});
|
||||||
|
assert!(api.remove_entity(Handle::new(7)));
|
||||||
|
peer_handle.join().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ pub enum PluginRequest {
|
||||||
PushOutput(String),
|
PushOutput(String),
|
||||||
PushError(String),
|
PushError(String),
|
||||||
AddEntity(EntityType),
|
AddEntity(EntityType),
|
||||||
|
/// Replace the existing entity carrying this entity's handle in place.
|
||||||
|
UpdateEntity(EntityType),
|
||||||
|
/// Delete the entity with `handle`.
|
||||||
|
RemoveEntity {
|
||||||
|
handle: Handle,
|
||||||
|
},
|
||||||
BumpGeometry,
|
BumpGeometry,
|
||||||
ReadRecord {
|
ReadRecord {
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ pub fn handle_plugin_request(
|
||||||
PluginResponse::Ok
|
PluginResponse::Ok
|
||||||
}
|
}
|
||||||
AddEntity(entity) => PluginResponse::Handle(host.add_entity(entity)),
|
AddEntity(entity) => PluginResponse::Handle(host.add_entity(entity)),
|
||||||
|
UpdateEntity(entity) => PluginResponse::Bool(host.update_entity(entity)),
|
||||||
|
RemoveEntity { handle } => PluginResponse::Bool(host.remove_entity(handle)),
|
||||||
BumpGeometry => {
|
BumpGeometry => {
|
||||||
host.bump_geometry();
|
host.bump_geometry();
|
||||||
PluginResponse::Ok
|
PluginResponse::Ok
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,31 @@ impl<'a> HostSession<'a> {
|
||||||
self.app.tabs[self.tab].scene.bump_geometry();
|
self.app.tabs[self.tab].scene.bump_geometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace the entity carrying `entity`'s handle in place, refreshing the
|
||||||
|
/// scene's derived caches. Returns `false` when no entity has that handle.
|
||||||
|
pub fn update_entity(&mut self, entity: EntityType) -> bool {
|
||||||
|
let ok = self.app.tabs[self.tab].scene.update_entity(entity);
|
||||||
|
if ok {
|
||||||
|
self.publish_document_view();
|
||||||
|
}
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete the entity with `handle`, keeping the scene's render caches in
|
||||||
|
/// sync. Returns `false` when the entity is absent or on a locked layer
|
||||||
|
/// (which `erase_entities` refuses to remove).
|
||||||
|
pub fn remove_entity(&mut self, handle: Handle) -> bool {
|
||||||
|
if self.document().get_entity(handle).is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.app.tabs[self.tab].scene.erase_entities(&[handle]);
|
||||||
|
let removed = self.document().get_entity(handle).is_none();
|
||||||
|
if removed {
|
||||||
|
self.publish_document_view();
|
||||||
|
}
|
||||||
|
removed
|
||||||
|
}
|
||||||
|
|
||||||
// ── XDATA convenience ──────────────────────────────────────────────────
|
// ── XDATA convenience ──────────────────────────────────────────────────
|
||||||
// Plugins persist domain data as XDATA on plain entities so it round-trips
|
// Plugins persist domain data as XDATA on plain entities so it round-trips
|
||||||
// through DWG/DXF. These wrap the `acadrust::xdata` API keyed by entity
|
// through DWG/DXF. These wrap the `acadrust::xdata` API keyed by entity
|
||||||
|
|
@ -94,6 +119,7 @@ impl<'a> HostSession<'a> {
|
||||||
pub fn write_record(&mut self, handle: Handle, record: ExtendedDataRecord) -> bool {
|
pub fn write_record(&mut self, handle: Handle, record: ExtendedDataRecord) -> bool {
|
||||||
let app = record.application_name.clone();
|
let app = record.application_name.clone();
|
||||||
self.ensure_app_id(&app);
|
self.ensure_app_id(&app);
|
||||||
|
let app_handle = self.document().app_ids.get(&app).map(|a| a.handle.value());
|
||||||
let Some(entity) = self.document_mut().get_entity_mut(handle) else {
|
let Some(entity) = self.document_mut().get_entity_mut(handle) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
@ -110,6 +136,13 @@ impl<'a> HostSession<'a> {
|
||||||
xd.add_record(r);
|
xd.add_record(r);
|
||||||
}
|
}
|
||||||
xd.add_record(record);
|
xd.add_record(record);
|
||||||
|
// Drop stale verbatim EED for this app so the fresh record — not the
|
||||||
|
// pre-edit bytes captured on a prior read — wins on the next save.
|
||||||
|
// Otherwise a plugin's edit made after a save/reopen (which registered
|
||||||
|
// the app in `raw_dwg_eed`) would not persist.
|
||||||
|
if let Some(ah) = app_handle {
|
||||||
|
xd.raw_dwg_eed.retain(|(a, _)| *a != ah);
|
||||||
|
}
|
||||||
self.publish_document_view();
|
self.publish_document_view();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +150,7 @@ impl<'a> HostSession<'a> {
|
||||||
/// Remove the XDATA record for `app_name` from entity `handle`. Returns
|
/// Remove the XDATA record for `app_name` from entity `handle`. Returns
|
||||||
/// `true` when a record was actually removed.
|
/// `true` when a record was actually removed.
|
||||||
pub fn remove_record(&mut self, handle: Handle, app_name: &str) -> bool {
|
pub fn remove_record(&mut self, handle: Handle, app_name: &str) -> bool {
|
||||||
|
let app_handle = self.document().app_ids.get(app_name).map(|a| a.handle.value());
|
||||||
let Some(entity) = self.document_mut().get_entity_mut(handle) else {
|
let Some(entity) = self.document_mut().get_entity_mut(handle) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
@ -127,23 +161,36 @@ impl<'a> HostSession<'a> {
|
||||||
.filter(|r| r.application_name != app_name)
|
.filter(|r| r.application_name != app_name)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
if kept.len() == xd.records().len() {
|
let removed_record = kept.len() != xd.records().len();
|
||||||
|
// Also drop verbatim EED for this app so the removal persists across a
|
||||||
|
// save (a record read back from DWG lives in `raw_dwg_eed`).
|
||||||
|
let removed_raw = app_handle
|
||||||
|
.map(|ah| xd.raw_dwg_eed.iter().any(|(a, _)| *a == ah))
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !removed_record && !removed_raw {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
xd.clear();
|
xd.clear();
|
||||||
for r in kept {
|
for r in kept {
|
||||||
xd.add_record(r);
|
xd.add_record(r);
|
||||||
}
|
}
|
||||||
|
if let Some(ah) = app_handle {
|
||||||
|
xd.raw_dwg_eed.retain(|(a, _)| *a != ah);
|
||||||
|
}
|
||||||
self.publish_document_view();
|
self.publish_document_view();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register `name` in the APPID table if it is not already present, so XDATA
|
/// Register `name` in the APPID table if it is not already present, so XDATA
|
||||||
/// written under it survives a DWG/DXF round-trip.
|
/// written under it survives a DWG/DXF round-trip. The entry is given a real
|
||||||
|
/// handle — a null-handle APPID is written as handle 0, which the DWG EED
|
||||||
|
/// reference then can't resolve, so the XDATA would be dropped on reopen.
|
||||||
fn ensure_app_id(&mut self, name: &str) {
|
fn ensure_app_id(&mut self, name: &str) {
|
||||||
let doc = self.document_mut();
|
let doc = self.document_mut();
|
||||||
if !doc.app_ids.contains(name) {
|
if !doc.app_ids.contains(name) {
|
||||||
let _ = doc.app_ids.add(AppId::new(name));
|
let mut app = AppId::new(name);
|
||||||
|
app.handle = doc.allocate_handle();
|
||||||
|
let _ = doc.app_ids.add(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,6 +233,12 @@ impl HostApi for HostSession<'_> {
|
||||||
fn add_entity(&mut self, entity: EntityType) -> Handle {
|
fn add_entity(&mut self, entity: EntityType) -> Handle {
|
||||||
self.add_entity(entity)
|
self.add_entity(entity)
|
||||||
}
|
}
|
||||||
|
fn update_entity(&mut self, entity: EntityType) -> bool {
|
||||||
|
self.update_entity(entity)
|
||||||
|
}
|
||||||
|
fn remove_entity(&mut self, handle: Handle) -> bool {
|
||||||
|
self.remove_entity(handle)
|
||||||
|
}
|
||||||
fn bump_geometry(&mut self) {
|
fn bump_geometry(&mut self) {
|
||||||
self.bump_geometry()
|
self.bump_geometry()
|
||||||
}
|
}
|
||||||
|
|
@ -447,6 +500,54 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_entity_replaces_in_place_preserving_handle() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.tabs[0].is_start = false;
|
||||||
|
let mut host = HostSession::new(&mut app, 0);
|
||||||
|
let h = host.add_entity(EntityType::Point(Point::at(acadrust::types::Vector3::new(
|
||||||
|
1.0, 1.0, 0.0,
|
||||||
|
))));
|
||||||
|
let epoch_before = host.app.tabs[0].scene.geometry_epoch;
|
||||||
|
|
||||||
|
// Edit a snapshot copy (as a plugin would) and commit it.
|
||||||
|
let mut edited = host.document().get_entity(h).unwrap().clone();
|
||||||
|
edited.common_mut().layer = "PLUGIN_EDIT".to_string();
|
||||||
|
assert!(host.update_entity(edited));
|
||||||
|
|
||||||
|
// Same handle, edit applied, geometry re-tessellated.
|
||||||
|
let got = host.document().get_entity(h).expect("entity kept its handle");
|
||||||
|
assert_eq!(got.common().layer, "PLUGIN_EDIT");
|
||||||
|
assert_ne!(
|
||||||
|
host.app.tabs[0].scene.geometry_epoch, epoch_before,
|
||||||
|
"update should bump geometry"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Updating an unknown handle fails and changes nothing.
|
||||||
|
let mut ghost = Point::new();
|
||||||
|
ghost.common.handle = Handle::new(999_999);
|
||||||
|
assert!(!host.update_entity(EntityType::Point(ghost)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_entity_deletes_and_clears_caches() {
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.tabs[0].is_start = false;
|
||||||
|
let mut host = HostSession::new(&mut app, 0);
|
||||||
|
let h = host.add_entity(EntityType::Point(Point::at(acadrust::types::Vector3::new(
|
||||||
|
2.0, 2.0, 0.0,
|
||||||
|
))));
|
||||||
|
assert!(host.document().get_entity(h).is_some());
|
||||||
|
|
||||||
|
assert!(host.remove_entity(h));
|
||||||
|
assert!(host.document().get_entity(h).is_none());
|
||||||
|
assert!(!host.app.tabs[0].scene.hatches.contains_key(&h));
|
||||||
|
assert!(!host.app.tabs[0].scene.meshes.contains_key(&h));
|
||||||
|
|
||||||
|
// Removing an already-gone handle reports false.
|
||||||
|
assert!(!host.remove_entity(h));
|
||||||
|
}
|
||||||
|
|
||||||
/// A plugin command: second point commits a Point and ends.
|
/// A plugin command: second point commits a Point and ends.
|
||||||
struct PlacePoint {
|
struct PlacePoint {
|
||||||
got_first: bool,
|
got_first: bool,
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,88 @@ impl Scene {
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace the entity stored under `entity`'s handle with `entity`, keeping
|
||||||
|
/// its identity (handle + owning block), and refresh the derived
|
||||||
|
/// hatch/image/mesh caches so the edit is visible. Returns `false` when no
|
||||||
|
/// entity has that handle. This is the in-place counterpart to
|
||||||
|
/// [`add_entity`](Self::add_entity) used to commit a plugin's edit of an
|
||||||
|
/// existing entity.
|
||||||
|
pub fn update_entity(&mut self, mut entity: EntityType) -> bool {
|
||||||
|
let handle = entity.common().handle;
|
||||||
|
let Some(existing) = self.document.get_entity(handle) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
// The caller edited a snapshot copy; keep the live entity in its block.
|
||||||
|
entity.common_mut().owner_handle = existing.common().owner_handle;
|
||||||
|
|
||||||
|
// Replacing (or becoming) a block entity forces a full block-cache
|
||||||
|
// rebuild; a plain entity only needs its own wires re-tessellated.
|
||||||
|
let affects_blocks = matches!(
|
||||||
|
existing,
|
||||||
|
EntityType::Insert(_) | EntityType::Block(_) | EntityType::BlockEnd(_)
|
||||||
|
) || matches!(
|
||||||
|
&entity,
|
||||||
|
EntityType::Insert(_) | EntityType::Block(_) | EntityType::BlockEnd(_)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rebuild the derived-model seeds from the new entity (as add_entity).
|
||||||
|
let hatch_seed = if let EntityType::Hatch(dxf) = &entity {
|
||||||
|
let color = self.render_style(&entity).0;
|
||||||
|
Self::hatch_model_from_dxf(dxf, color)
|
||||||
|
} else if let EntityType::Solid(solid) = &entity {
|
||||||
|
let color = self.render_style(&entity).0;
|
||||||
|
Some(Self::solid_hatch_model(solid, color))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let image_seed = if let EntityType::RasterImage(img) = &entity {
|
||||||
|
ImageModel::from_raster_image(img)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let facet_res = self.document.header.facet_resolution;
|
||||||
|
let mesh_seed = if matches!(
|
||||||
|
&entity,
|
||||||
|
EntityType::Solid3D(_) | EntityType::Region(_) | EntityType::Body(_) | EntityType::Surface(_)
|
||||||
|
) {
|
||||||
|
let color = self.render_style(&entity).0;
|
||||||
|
crate::entities::solid3d::tessellate_volume(&entity, color, facet_res)
|
||||||
|
.map(|m| offset_mesh_lod_set(m))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write the new entity into the live slot.
|
||||||
|
let Some(slot) = self.document.get_entity_mut(handle) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
*slot = entity;
|
||||||
|
|
||||||
|
// Drop stale derived caches for this handle, then reseed for the new
|
||||||
|
// entity's type (which may differ from the old one).
|
||||||
|
self.hatches.remove(&handle);
|
||||||
|
self.images.remove(&handle);
|
||||||
|
self.meshes.remove(&handle);
|
||||||
|
self.solid_models.remove(&handle);
|
||||||
|
if let Some(model) = hatch_seed {
|
||||||
|
self.hatches.insert(handle, model);
|
||||||
|
}
|
||||||
|
if let Some(model) = image_seed {
|
||||||
|
self.images.insert(handle, model);
|
||||||
|
}
|
||||||
|
if let Some(model) = mesh_seed {
|
||||||
|
self.meshes.insert(handle, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mark_entity_dirty(handle);
|
||||||
|
if affects_blocks {
|
||||||
|
self.bump_geometry();
|
||||||
|
} else {
|
||||||
|
self.bump_geometry_no_blocks();
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the RGBA color for the given layer name.
|
/// Returns the RGBA color for the given layer name.
|
||||||
pub fn layer_color(&self, layer: &str) -> [f32; 4] {
|
pub fn layer_color(&self, layer: &str) -> [f32; 4] {
|
||||||
let layer_entry = self.document.layers.get(layer);
|
let layer_entry = self.document.layers.get(layer);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue