forked from 1b-agent/opencadstudio-upstream
Add nested attribute entity host support
This commit is contained in:
1 parent
5ef7000b67
commit
cf2a923568
6 files changed
+2509
-541
No files matched your search
+219
-53
@@ -17,7 +17,9 @@ include!("src/type_registry_types.rs");
|
|||||||
mod entity_coverage_schema {
|
mod entity_coverage_schema {
|
||||||
include!("src/entity_coverage_types.rs");
|
include!("src/entity_coverage_types.rs");
|
||||||
}
|
}
|
||||||
use entity_coverage_schema::{EntityCoverageCatalog, EntityKindCoverage, EntityScope, ModelAccess, PropertyCoverage};
|
use entity_coverage_schema::{
|
||||||
|
EntityCoverageCatalog, EntityKindCoverage, EntityScope, ModelAccess, PropertyCoverage,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct EntityCoveragePolicy {
|
struct EntityCoveragePolicy {
|
||||||
@@ -506,41 +508,71 @@ fn generate_type_registry(out_dir: &Path) {
|
|||||||
|
|
||||||
fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
let policy: EntityCoveragePolicy = serde_json::from_str(include_str!("entity_coverage_policy.json"))
|
let policy: EntityCoveragePolicy =
|
||||||
|
serde_json::from_str(include_str!("entity_coverage_policy.json"))
|
||||||
.expect("valid entity coverage policy");
|
.expect("valid entity coverage policy");
|
||||||
let variants = ®istry.types[&TypeId::new("EntityType")].variants;
|
let variants = ®istry.types[&TypeId::new("EntityType")].variants;
|
||||||
let variant_names: HashSet<&str> = variants.iter().map(|v| v.name.as_str()).collect();
|
let variant_names: HashSet<&str> = variants.iter().map(|v| v.name.as_str()).collect();
|
||||||
for name in policy.internal_kinds.iter().chain(&policy.opaque_kinds)
|
for name in policy
|
||||||
.chain(policy.editable.keys()).chain(policy.readable.keys()) {
|
.internal_kinds
|
||||||
assert!(variant_names.contains(name.as_str()), "coverage policy names unknown kind: {name}");
|
.iter()
|
||||||
|
.chain(&policy.opaque_kinds)
|
||||||
|
.chain(policy.editable.keys())
|
||||||
|
.chain(policy.readable.keys())
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
variant_names.contains(name.as_str()),
|
||||||
|
"coverage policy names unknown kind: {name}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for name in &policy.internal_kinds {
|
for name in &policy.internal_kinds {
|
||||||
assert!(!policy.opaque_kinds.contains(name) && !policy.editable.contains_key(name),
|
assert!(
|
||||||
"internal kind has conflicting coverage: {name}");
|
!policy.opaque_kinds.contains(name) && !policy.editable.contains_key(name),
|
||||||
|
"internal kind has conflicting coverage: {name}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for name in &policy.opaque_kinds {
|
for name in &policy.opaque_kinds {
|
||||||
assert!(!policy.editable.contains_key(name), "opaque kind is editable: {name}");
|
assert!(
|
||||||
|
!policy.editable.contains_key(name),
|
||||||
|
"opaque kind is editable: {name}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
for name in policy.aliases.keys() {
|
for name in policy.aliases.keys() {
|
||||||
assert!(policy.editable.contains_key(name), "aliases for non-editable kind: {name}");
|
assert!(
|
||||||
|
policy.editable.contains_key(name),
|
||||||
|
"aliases for non-editable kind: {name}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let common = ®istry.types[&TypeId::new("EntityCommon")];
|
let common = ®istry.types[&TypeId::new("EntityCommon")];
|
||||||
let mut entity_kinds = Vec::with_capacity(variants.len());
|
let mut entity_kinds = Vec::with_capacity(variants.len());
|
||||||
for variant in variants {
|
for variant in variants {
|
||||||
let kind = &variant.name;
|
let kind = &variant.name;
|
||||||
let scope = if policy.internal_kinds.contains(kind) { EntityScope::Internal }
|
let scope = if policy.internal_kinds.contains(kind) {
|
||||||
else if policy.opaque_kinds.contains(kind) { EntityScope::Opaque }
|
EntityScope::Internal
|
||||||
else { EntityScope::Canvas };
|
} else if policy.opaque_kinds.contains(kind) {
|
||||||
|
EntityScope::Opaque
|
||||||
|
} else {
|
||||||
|
EntityScope::Canvas
|
||||||
|
};
|
||||||
let editable = policy.editable.get(kind).cloned().unwrap_or_default();
|
let editable = policy.editable.get(kind).cloned().unwrap_or_default();
|
||||||
let readable = policy.readable.get(kind).cloned().unwrap_or_default();
|
let readable = policy.readable.get(kind).cloned().unwrap_or_default();
|
||||||
let mut unresolved: HashSet<String> = editable.iter().chain(&readable).cloned().collect();
|
let mut unresolved: HashSet<String> = editable.iter().chain(&readable).cloned().collect();
|
||||||
assert_eq!(unresolved.len(), editable.len() + readable.len(), "duplicate mapped property in {kind}");
|
assert_eq!(
|
||||||
|
unresolved.len(),
|
||||||
|
editable.len() + readable.len(),
|
||||||
|
"duplicate mapped property in {kind}"
|
||||||
|
);
|
||||||
let aliases = policy.aliases.get(kind);
|
let aliases = policy.aliases.get(kind);
|
||||||
let mut properties = vec![PropertyCoverage {
|
let mut properties = vec![PropertyCoverage {
|
||||||
name: "kind".into(), source_path: "<variant>".into(), type_id: "String".into(),
|
name: "kind".into(),
|
||||||
optional: false, is_sequence: false, snapshot_readable: true,
|
source_path: "<variant>".into(),
|
||||||
model_access: ModelAccess::ReadOnly, validation: "none".into(),
|
type_id: "String".into(),
|
||||||
|
optional: false,
|
||||||
|
is_sequence: false,
|
||||||
|
snapshot_readable: true,
|
||||||
|
model_access: ModelAccess::ReadOnly,
|
||||||
|
validation: "none".into(),
|
||||||
}];
|
}];
|
||||||
for field in &common.fields {
|
for field in &common.fields {
|
||||||
let (name, access) = match field.name.as_str() {
|
let (name, access) = match field.name.as_str() {
|
||||||
@@ -549,69 +581,203 @@ fn generate_entity_coverage(out_dir: &Path, registry: &TypeRegistry) {
|
|||||||
"layer" if scope == EntityScope::Canvas => {
|
"layer" if scope == EntityScope::Canvas => {
|
||||||
unresolved.remove("layer");
|
unresolved.remove("layer");
|
||||||
("layer".to_owned(), ModelAccess::ReadWrite)
|
("layer".to_owned(), ModelAccess::ReadWrite)
|
||||||
},
|
}
|
||||||
"layer" => ("layer".to_owned(), ModelAccess::ReadOnly),
|
"layer" => ("layer".to_owned(), ModelAccess::ReadOnly),
|
||||||
other => (format!("common.{other}"), ModelAccess::Unmapped),
|
other => (format!("common.{other}"), ModelAccess::Unmapped),
|
||||||
};
|
};
|
||||||
properties.push(PropertyCoverage {
|
properties.push(PropertyCoverage {
|
||||||
name, source_path: format!("common.{}", field.name), type_id: field.type_id.as_str().to_owned(),
|
name,
|
||||||
optional: field.optional, is_sequence: field.is_sequence, snapshot_readable: true,
|
source_path: format!("common.{}", field.name),
|
||||||
|
type_id: field.type_id.as_str().to_owned(),
|
||||||
|
optional: field.optional,
|
||||||
|
is_sequence: field.is_sequence,
|
||||||
|
snapshot_readable: true,
|
||||||
validation: if access == ModelAccess::ReadWrite && field.name == "layer" {
|
validation: if access == ModelAccess::ReadWrite && field.name == "layer" {
|
||||||
"transaction_nonempty"
|
"transaction_nonempty"
|
||||||
} else if access == ModelAccess::ReadWrite { "type_conversion_only" } else { "none" }.into(),
|
} else if access == ModelAccess::ReadWrite {
|
||||||
|
"type_conversion_only"
|
||||||
|
} else {
|
||||||
|
"none"
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
model_access: access,
|
model_access: access,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let shape_type = variant.fields.first().expect("entity variant payload").type_id.clone();
|
let shape_type = variant
|
||||||
|
.fields
|
||||||
|
.first()
|
||||||
|
.expect("entity variant payload")
|
||||||
|
.type_id
|
||||||
|
.clone();
|
||||||
let shape = ®istry.types[&shape_type];
|
let shape = ®istry.types[&shape_type];
|
||||||
for field in &shape.fields {
|
for field in &shape.fields {
|
||||||
if field.name == "common" { continue; }
|
if field.name == "common" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let exposed = editable.iter().chain(&readable).find(|name| {
|
let exposed = editable.iter().chain(&readable).find(|name| {
|
||||||
aliases.and_then(|map| map.get(*name)).map_or(name.as_str(), String::as_str) == field.name
|
aliases
|
||||||
|
.and_then(|map| map.get(*name))
|
||||||
|
.map_or(name.as_str(), String::as_str)
|
||||||
|
== field.name
|
||||||
});
|
});
|
||||||
let (name, access) = match exposed {
|
let (name, access) = match exposed {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
unresolved.remove(name);
|
unresolved.remove(name);
|
||||||
((*name).clone(), if editable.contains(name) { ModelAccess::ReadWrite } else { ModelAccess::ReadOnly })
|
(
|
||||||
|
(*name).clone(),
|
||||||
|
if editable.contains(name) {
|
||||||
|
ModelAccess::ReadWrite
|
||||||
|
} else {
|
||||||
|
ModelAccess::ReadOnly
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => (field.name.clone(), ModelAccess::Unmapped),
|
None => (field.name.clone(), ModelAccess::Unmapped),
|
||||||
};
|
};
|
||||||
let validation = if access != ModelAccess::ReadWrite { "none" }
|
let validation = if access != ModelAccess::ReadWrite {
|
||||||
else if matches!((kind.as_str(), name.as_str()),
|
"none"
|
||||||
("Point", "location") | ("Line", "start" | "end") |
|
} else if matches!(
|
||||||
("Circle" | "Arc", "center" | "radius") |
|
(kind.as_str(), name.as_str()),
|
||||||
("Ray" | "XLine", "base_point" | "direction") |
|
("Point", "location")
|
||||||
("Solid", "first_corner" | "second_corner" | "third_corner" | "fourth_corner" | "normal" | "thickness") |
|
| ("Line", "start" | "end")
|
||||||
("Face3D", "first_corner" | "second_corner" | "third_corner" | "fourth_corner" | "invisible_edges") |
|
| ("Circle" | "Arc", "center" | "radius")
|
||||||
("Insert", "insert_point" | "x_scale" | "y_scale" | "z_scale" | "rotation" | "normal" |
|
| ("Ray" | "XLine", "base_point" | "direction")
|
||||||
"column_count" | "row_count" | "column_spacing" | "row_spacing") |
|
| (
|
||||||
("Tolerance", "insertion_point" | "direction" | "normal" | "text" |
|
"Solid",
|
||||||
"dimension_style_name" | "text_height" | "dimension_gap") |
|
"first_corner"
|
||||||
("Shape", "insertion_point" | "size" | "shape_name" | "shape_number" |
|
| "second_corner"
|
||||||
"rotation" | "relative_x_scale" | "oblique_angle" | "normal" |
|
| "third_corner"
|
||||||
"thickness" | "style_name") |
|
| "fourth_corner"
|
||||||
("AttributeDefinition", "tag" | "prompt" | "default_value" |
|
| "normal"
|
||||||
"insertion_point" | "alignment_point" | "height" | "rotation" |
|
| "thickness"
|
||||||
"width_factor" | "oblique_angle" | "text_style" |
|
)
|
||||||
"text_generation_flags" | "horizontal_alignment" |
|
| (
|
||||||
"vertical_alignment" | "flags" | "field_length" | "normal" |
|
"Face3D",
|
||||||
"mtext_flag" | "is_multiline" | "line_count" | "lock_position")) {
|
"first_corner"
|
||||||
|
| "second_corner"
|
||||||
|
| "third_corner"
|
||||||
|
| "fourth_corner"
|
||||||
|
| "invisible_edges"
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
"Insert",
|
||||||
|
"insert_point"
|
||||||
|
| "x_scale"
|
||||||
|
| "y_scale"
|
||||||
|
| "z_scale"
|
||||||
|
| "rotation"
|
||||||
|
| "normal"
|
||||||
|
| "column_count"
|
||||||
|
| "row_count"
|
||||||
|
| "column_spacing"
|
||||||
|
| "row_spacing"
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
"Tolerance",
|
||||||
|
"insertion_point"
|
||||||
|
| "direction"
|
||||||
|
| "normal"
|
||||||
|
| "text"
|
||||||
|
| "dimension_style_name"
|
||||||
|
| "text_height"
|
||||||
|
| "dimension_gap"
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
"Shape",
|
||||||
|
"insertion_point"
|
||||||
|
| "size"
|
||||||
|
| "shape_name"
|
||||||
|
| "shape_number"
|
||||||
|
| "rotation"
|
||||||
|
| "relative_x_scale"
|
||||||
|
| "oblique_angle"
|
||||||
|
| "normal"
|
||||||
|
| "thickness"
|
||||||
|
| "style_name"
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
"AttributeDefinition",
|
||||||
|
"tag"
|
||||||
|
| "prompt"
|
||||||
|
| "default_value"
|
||||||
|
| "insertion_point"
|
||||||
|
| "alignment_point"
|
||||||
|
| "height"
|
||||||
|
| "rotation"
|
||||||
|
| "width_factor"
|
||||||
|
| "oblique_angle"
|
||||||
|
| "text_style"
|
||||||
|
| "text_generation_flags"
|
||||||
|
| "horizontal_alignment"
|
||||||
|
| "vertical_alignment"
|
||||||
|
| "flags"
|
||||||
|
| "field_length"
|
||||||
|
| "normal"
|
||||||
|
| "mtext_flag"
|
||||||
|
| "is_multiline"
|
||||||
|
| "line_count"
|
||||||
|
| "lock_position"
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
"AttributeEntity",
|
||||||
|
"tag"
|
||||||
|
| "value"
|
||||||
|
| "insertion_point"
|
||||||
|
| "alignment_point"
|
||||||
|
| "height"
|
||||||
|
| "rotation"
|
||||||
|
| "width_factor"
|
||||||
|
| "oblique_angle"
|
||||||
|
| "text_style"
|
||||||
|
| "text_generation_flags"
|
||||||
|
| "horizontal_alignment"
|
||||||
|
| "vertical_alignment"
|
||||||
|
| "flags"
|
||||||
|
| "field_length"
|
||||||
|
| "normal"
|
||||||
|
| "mtext_flag"
|
||||||
|
| "is_multiline"
|
||||||
|
| "line_count"
|
||||||
|
| "lock_position"
|
||||||
|
)
|
||||||
|
) {
|
||||||
"transaction_geometry"
|
"transaction_geometry"
|
||||||
} else { "type_conversion_only" };
|
} else {
|
||||||
|
"type_conversion_only"
|
||||||
|
};
|
||||||
properties.push(PropertyCoverage {
|
properties.push(PropertyCoverage {
|
||||||
name, source_path: field.name.clone(),
|
name,
|
||||||
type_id: if exposed.is_some_and(|name| name == "closed") { "bool".into() }
|
source_path: field.name.clone(),
|
||||||
else { field.type_id.as_str().to_owned() },
|
type_id: if exposed.is_some_and(|name| name == "closed") {
|
||||||
optional: field.optional, is_sequence: field.is_sequence, snapshot_readable: true,
|
"bool".into()
|
||||||
|
} else {
|
||||||
|
field.type_id.as_str().to_owned()
|
||||||
|
},
|
||||||
|
optional: field.optional,
|
||||||
|
is_sequence: field.is_sequence,
|
||||||
|
snapshot_readable: true,
|
||||||
validation: validation.into(),
|
validation: validation.into(),
|
||||||
model_access: access,
|
model_access: access,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
assert!(unresolved.is_empty(), "{kind} has coverage keys absent from registry: {unresolved:?}");
|
assert!(
|
||||||
entity_kinds.push(EntityKindCoverage { kind: kind.clone(), scope, properties });
|
unresolved.is_empty(),
|
||||||
|
"{kind} has coverage keys absent from registry: {unresolved:?}"
|
||||||
|
);
|
||||||
|
entity_kinds.push(EntityKindCoverage {
|
||||||
|
kind: kind.clone(),
|
||||||
|
scope,
|
||||||
|
properties,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let catalog = EntityCoverageCatalog { schema_version: 1, entity_kinds };
|
let catalog = EntityCoverageCatalog {
|
||||||
fs::write(out_dir.join("entity_coverage.json"), serde_json::to_string_pretty(&catalog).unwrap())
|
schema_version: 1,
|
||||||
|
entity_kinds,
|
||||||
|
};
|
||||||
|
fs::write(
|
||||||
|
out_dir.join("entity_coverage.json"),
|
||||||
|
serde_json::to_string_pretty(&catalog).unwrap(),
|
||||||
|
)
|
||||||
.expect("write entity coverage");
|
.expect("write entity coverage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,14 @@
|
|||||||
"Insert": ["layer", "insert_point", "x_scale", "y_scale", "z_scale", "rotation", "normal", "column_count", "row_count", "column_spacing", "row_spacing"],
|
"Insert": ["layer", "insert_point", "x_scale", "y_scale", "z_scale", "rotation", "normal", "column_count", "row_count", "column_spacing", "row_spacing"],
|
||||||
"Tolerance": ["layer", "insertion_point", "direction", "normal", "text", "dimension_style_name", "text_height", "dimension_gap"],
|
"Tolerance": ["layer", "insertion_point", "direction", "normal", "text", "dimension_style_name", "text_height", "dimension_gap"],
|
||||||
"Shape": ["layer", "insertion_point", "size", "shape_name", "shape_number", "rotation", "relative_x_scale", "oblique_angle", "normal", "thickness", "style_name"],
|
"Shape": ["layer", "insertion_point", "size", "shape_name", "shape_number", "rotation", "relative_x_scale", "oblique_angle", "normal", "thickness", "style_name"],
|
||||||
"AttributeDefinition": ["layer", "tag", "prompt", "default_value", "insertion_point", "alignment_point", "height", "rotation", "width_factor", "oblique_angle", "text_style", "text_generation_flags", "horizontal_alignment", "vertical_alignment", "flags", "field_length", "normal", "mtext_flag", "is_multiline", "line_count", "lock_position"]
|
"AttributeDefinition": ["layer", "tag", "prompt", "default_value", "insertion_point", "alignment_point", "height", "rotation", "width_factor", "oblique_angle", "text_style", "text_generation_flags", "horizontal_alignment", "vertical_alignment", "flags", "field_length", "normal", "mtext_flag", "is_multiline", "line_count", "lock_position"],
|
||||||
|
"AttributeEntity": ["layer", "tag", "value", "insertion_point", "alignment_point", "height", "rotation", "width_factor", "oblique_angle", "text_style", "text_generation_flags", "horizontal_alignment", "vertical_alignment", "flags", "field_length", "normal", "mtext_flag", "is_multiline", "line_count", "lock_position"]
|
||||||
},
|
},
|
||||||
"readable": {
|
"readable": {
|
||||||
"Insert": ["block_name"],
|
"Insert": ["block_name"],
|
||||||
"Tolerance": ["dimension_style_handle"],
|
"Tolerance": ["dimension_style_handle"],
|
||||||
"Shape": ["style_handle"]
|
"Shape": ["style_handle"],
|
||||||
|
"AttributeEntity": ["attdef_handle"]
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"Polyline": {"closed": "flags"},
|
"Polyline": {"closed": "flags"},
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -3,6 +3,27 @@
|
|||||||
use crate::host::HostApi;
|
use crate::host::HostApi;
|
||||||
use crate::ipc::protocol::{PluginRequest, PluginResponse};
|
use crate::ipc::protocol::{PluginRequest, PluginResponse};
|
||||||
|
|
||||||
|
fn addressable_document_snapshot(host: &dyn HostApi) -> acadrust::CadDocument {
|
||||||
|
let mut snapshot = host.document().clone();
|
||||||
|
let attributes = host
|
||||||
|
.document()
|
||||||
|
.entities()
|
||||||
|
.filter_map(|entity| match entity {
|
||||||
|
acadrust::EntityType::Insert(insert) => Some(insert.attributes.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.map(acadrust::EntityType::AttributeEntity)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for attribute in attributes {
|
||||||
|
// Attribute entities remain canonical children of INSERT in the host
|
||||||
|
// document. The IPC snapshot also indexes them by handle so plugin
|
||||||
|
// document models can address a child exactly like any other entity.
|
||||||
|
let _ = snapshot.add_entity(attribute);
|
||||||
|
}
|
||||||
|
snapshot
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply one plugin request to the host's `HostApi` implementation.
|
/// Apply one plugin request to the host's `HostApi` implementation.
|
||||||
///
|
///
|
||||||
/// `on_start_interactive` is called when the plugin starts an interactive
|
/// `on_start_interactive` is called when the plugin starts an interactive
|
||||||
@@ -54,7 +75,7 @@ pub fn handle_plugin_request(
|
|||||||
on_start_interactive(command_id);
|
on_start_interactive(command_id);
|
||||||
PluginResponse::Ok
|
PluginResponse::Ok
|
||||||
}
|
}
|
||||||
DocumentSnapshot => PluginResponse::Document(Box::new(host.document().clone())),
|
DocumentSnapshot => PluginResponse::Document(Box::new(addressable_document_snapshot(host))),
|
||||||
OpenDocumentView => match host.document_view() {
|
OpenDocumentView => match host.document_view() {
|
||||||
Some(info) => PluginResponse::DocumentView {
|
Some(info) => PluginResponse::DocumentView {
|
||||||
path: info.path,
|
path: info.path,
|
||||||
@@ -77,9 +98,7 @@ pub fn handle_plugin_request(
|
|||||||
DocumentPath { tab_id } => PluginResponse::DocumentPath(
|
DocumentPath { tab_id } => PluginResponse::DocumentPath(
|
||||||
host.document_path(tab_id).map(|path| path.into_os_string()),
|
host.document_path(tab_id).map(|path| path.into_os_string()),
|
||||||
),
|
),
|
||||||
GetSystemVariable { name } => {
|
GetSystemVariable { name } => PluginResponse::SystemVariable(host.system_variable(&name)),
|
||||||
PluginResponse::SystemVariable(host.system_variable(&name))
|
|
||||||
}
|
|
||||||
SetSystemVariable { name, value } => {
|
SetSystemVariable { name, value } => {
|
||||||
PluginResponse::SystemVariableResult(host.set_system_variable(&name, value))
|
PluginResponse::SystemVariableResult(host.set_system_variable(&name, value))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,13 +83,19 @@ impl<T: SnapshotData> DocumentSnapshotStore<T> {
|
|||||||
/// page.
|
/// page.
|
||||||
pub fn new(tab_id: u64, segment_size: usize) -> io::Result<Self> {
|
pub fn new(tab_id: u64, segment_size: usize) -> io::Result<Self> {
|
||||||
let segment_size = segment_size.checked_next_multiple_of(4096).ok_or_else(|| {
|
let segment_size = segment_size.checked_next_multiple_of(4096).ok_or_else(|| {
|
||||||
io::Error::new(io::ErrorKind::InvalidInput, "snapshot segment size overflow")
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"snapshot segment size overflow",
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
let total = segment_size
|
let total = segment_size
|
||||||
.checked_mul(2)
|
.checked_mul(2)
|
||||||
.and_then(|segments| CONTROL_SIZE.checked_add(segments))
|
.and_then(|segments| CONTROL_SIZE.checked_add(segments))
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
io::Error::new(io::ErrorKind::InvalidInput, "snapshot mapping size overflow")
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"snapshot mapping size overflow",
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
if total > MAX_SNAPSHOT_SIZE {
|
if total > MAX_SNAPSHOT_SIZE {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
@@ -245,7 +251,10 @@ impl<T: SnapshotData> SharedDocumentReader<T> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
let file_len = usize::try_from(file_len).map_err(|_| {
|
let file_len = usize::try_from(file_len).map_err(|_| {
|
||||||
io::Error::new(io::ErrorKind::InvalidData, "snapshot file size is unsupported")
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"snapshot file size is unsupported",
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
let mmap = unsafe { MmapOptions::new().len(file_len).map(&file)? };
|
let mmap = unsafe { MmapOptions::new().len(file_len).map(&file)? };
|
||||||
let segment_size = (file_len - CONTROL_SIZE) / 2;
|
let segment_size = (file_len - CONTROL_SIZE) / 2;
|
||||||
@@ -543,10 +552,22 @@ pub struct DocumentViewDataV4 {
|
|||||||
|
|
||||||
impl From<&CadDocument> for DocumentViewDataV4 {
|
impl From<&CadDocument> for DocumentViewDataV4 {
|
||||||
fn from(doc: &CadDocument) -> Self {
|
fn from(doc: &CadDocument) -> Self {
|
||||||
|
let mut entities: Vec<_> = doc.entities().map(EntityViewV4::from).collect();
|
||||||
|
// ATTRIB records live inside their owning INSERT in CadDocument rather
|
||||||
|
// than in the flat entity index. Flatten them into the plugin snapshot
|
||||||
|
// as addressable entities while keeping the INSERT's canonical nested
|
||||||
|
// copy intact for rendering and file serialization.
|
||||||
|
for entity in doc.entities() {
|
||||||
|
if let EntityType::Insert(insert) = entity {
|
||||||
|
entities.extend(insert.attributes.iter().map(|attribute| {
|
||||||
|
EntityViewV4::from(&EntityType::AttributeEntity(attribute.clone()))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
layers: doc.layers.iter().map(LayerView::from).collect(),
|
layers: doc.layers.iter().map(LayerView::from).collect(),
|
||||||
app_ids: doc.app_ids.iter().map(AppIdView::from).collect(),
|
app_ids: doc.app_ids.iter().map(AppIdView::from).collect(),
|
||||||
entities: doc.entities().map(EntityViewV4::from).collect(),
|
entities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -721,7 +742,10 @@ mod tests {
|
|||||||
|
|
||||||
let archived = reader.archived().unwrap();
|
let archived = reader.archived().unwrap();
|
||||||
let entity = &archived.entities[0];
|
let entity = &archived.entities[0];
|
||||||
assert_eq!(entity.handle, doc.entities().next().unwrap().common().handle.value());
|
assert_eq!(
|
||||||
|
entity.handle,
|
||||||
|
doc.entities().next().unwrap().common().handle.value()
|
||||||
|
);
|
||||||
let decoded: EntityType = bincode::deserialize(&entity.data).expect("bincode decode");
|
let decoded: EntityType = bincode::deserialize(&entity.data).expect("bincode decode");
|
||||||
assert!(matches!(decoded, EntityType::Point(_)));
|
assert!(matches!(decoded, EntityType::Point(_)));
|
||||||
}
|
}
|
||||||
|
|||||||
+1345
-267
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user