feat: node graph overlay; move to opencadcodec/opencadkernel/opencadgraph

The codec and kernel repositories and crates are renamed opencadcodec and
opencadkernel. Inside OCS they are depended on under short keys, so code
reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node
graph engine). File-format and protocol spellings stay as they were: the
ACIS product id `acadrust`, the ACADRUST_* switches and the plugin
version keys `acadrust_version` / `acadrust_source`.

A button left of MCP on the command line opens a translucent node graph
over the viewport, with a palette of object, input, math, logic, list,
text and geometry nodes added by click or drag. An object node owns
drawing entities and its rows are the Properties panel's own rows for
them: the single-object page is extracted from refresh_properties as
entity_property_sections, values use the control protocol's
property_json, and writes go through set_property_value with a property
target override, so the panel, MCP and the graph share one read/write
path. A list reaching an object row makes one entity per element. Each
graph action is one undo step; the canvas pans and zooms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-09-25 07:34:23 +03:00
1 parent 56c04408d6
commit f537aff75c
430 files changed
+9925 -8540

No files matched your search

+2 -2
View File
@@ -10,7 +10,7 @@
use std::path::{Path, PathBuf};
use acadrust::CadDocument;
use codec::CadDocument;
/// The community folder's GitHub contents API (lists name + download URL).
#[cfg(not(target_arch = "wasm32"))]
@@ -294,7 +294,7 @@ mod tests {
std::fs::write(dir.join("exists.shx"), b"stub").unwrap();
let mut doc = CadDocument::new();
let mk = |name: &str, file: &str| {
let mut style = acadrust::TextStyle::new(name);
let mut style = codec::TextStyle::new(name);
style.font_file = file.into();
style
};
+3 -3
View File
@@ -8,8 +8,8 @@
use rustc_hash::FxHashMap as HashMap;
use std::sync::OnceLock;
use acadrust::tables::linetype::{LineType, LineTypeComplexContent, LineTypeElement};
use acadrust::{CadDocument, TableEntry};
use codec::tables::linetype::{LineType, LineTypeComplexContent, LineTypeElement};
use codec::{CadDocument, TableEntry};
// ── Complex linetype types ────────────────────────────────────────────────
@@ -612,7 +612,7 @@ fn push_lt_segment(token: &str, out: &mut Vec<LtSegment>) {
#[cfg(test)]
mod header_tests {
use super::{parse, populate_document_from_source};
use acadrust::CadDocument;
use codec::CadDocument;
/// A `.lin` header's description is optional, and without one the comma
/// goes too: `*PLAIN` followed by its `A,` line is a whole definition.
+98 -98
View File
@@ -1,6 +1,6 @@
// I/O module — open, save, and export CAD documents.
//
// All file reading/writing goes through acadrust.
// All file reading/writing goes through opencadcodec.
// Default save format: DWG (AC1032 / R2018+).
pub mod file_association;
@@ -32,9 +32,9 @@ mod web_worker;
pub(crate) mod web_recent;
use crate::scene::DerivedCaches;
use acadrust::entities::EntityType;
use acadrust::io::dwg::DwgReader;
use acadrust::{
use codec::entities::EntityType;
use codec::io::dwg::DwgReader;
use codec::{
CadDocument, DwgReadOptions, DwgWriter, DxfReader, DxfReaderConfiguration, DxfWriter,
};
use std::path::{Path, PathBuf};
@@ -114,7 +114,7 @@ fn recovery_fingerprint_needed(caches: &DerivedCaches) -> bool {
pub struct OpenLoadError {
pub message: String,
pub source_sha256: Option<String>,
pub read_stats: Option<acadrust::ReadStats>,
pub read_stats: Option<codec::ReadStats>,
pub recovery_available: bool,
}
@@ -131,7 +131,7 @@ impl OpenLoadError {
#[cfg(not(target_arch = "wasm32"))]
fn recovery_prompt(
message: impl Into<String>,
read_stats: Option<acadrust::ReadStats>,
read_stats: Option<codec::ReadStats>,
) -> Self {
Self {
message: message.into(),
@@ -246,7 +246,7 @@ pub async fn recover_path_with_phase(
progress: Arc<OpenProgressState>,
model_bg: [f32; 4],
initial_error: String,
initial_stats: Option<acadrust::ReadStats>,
initial_stats: Option<codec::ReadStats>,
) -> Result<(String, PathBuf, CadDocument, DerivedCaches), OpenLoadError> {
open_path_with_phase_attempt(
path,
@@ -261,13 +261,13 @@ pub async fn recover_path_with_phase(
#[derive(Debug, Clone)]
enum OpenAttempt {
Strict,
Recovery(String, Option<acadrust::ReadStats>),
Recovery(String, Option<codec::ReadStats>),
}
#[cfg(not(target_arch = "wasm32"))]
struct OpenAttemptFailure {
message: String,
read_stats: Option<acadrust::ReadStats>,
read_stats: Option<codec::ReadStats>,
recoverable: bool,
}
@@ -586,7 +586,7 @@ pub async fn recover_web_bytes(
bytes: Arc<[u8]>,
progress: Arc<OpenProgressState>,
initial_error: String,
initial_stats: Option<acadrust::ReadStats>,
initial_stats: Option<codec::ReadStats>,
) -> WebOpenOutcome {
let size_bytes = bytes.len() as u64;
let result = load_web_bytes(
@@ -671,7 +671,7 @@ async fn load_web_bytes(
progress: Arc<OpenProgressState>,
recovery_mode: bool,
initial_error: &str,
mut initial_stats: Option<acadrust::ReadStats>,
mut initial_stats: Option<codec::ReadStats>,
) -> Result<(String, PathBuf, CadDocument, DerivedCaches), OpenLoadError> {
progress.set(crate::app::OPEN_PHASE_PARSING, 1000, 0, 1);
let (outcome, mut source_sha256) = match web_worker::parse_document(
@@ -831,7 +831,7 @@ pub fn load_file(_path: &Path) -> Result<CadDocument, String> {
pub(crate) fn load_file_with_progress(
path: &Path,
_progress: Option<Arc<dyn Fn(u16) + Send + Sync>>,
) -> Result<acadrust::ReadOutcome, String> {
) -> Result<codec::ReadOutcome, String> {
let outcome = read_file_attempt(path, _progress, false).map_err(|failure| failure.message)?;
if !outcome.stats.has_usable_drawing_data() {
return Err("initial read returned no source drawing records".to_string());
@@ -844,7 +844,7 @@ fn load_file_for_open(
path: &Path,
progress: Option<Arc<dyn Fn(u16) + Send + Sync>>,
attempt: &OpenAttempt,
) -> Result<acadrust::ReadOutcome, OpenAttemptFailure> {
) -> Result<codec::ReadOutcome, OpenAttemptFailure> {
let outcome = match attempt {
OpenAttempt::Strict => {
let outcome = read_file_attempt(path, progress, false).map_err(|failure| {
@@ -905,14 +905,14 @@ fn load_file_for_open(
});
}
outcome.document.notifications.notify(
acadrust::notification::NotificationType::Error,
codec::notification::NotificationType::Error,
format!("Initial read failed; recovery mode continued: {initial_error}"),
);
acadrust::push_read_diagnostic(
codec::push_read_diagnostic(
&mut outcome.stats.diagnostics,
acadrust::ReadDiagnostic::new(
codec::ReadDiagnostic::new(
"strict-read-failed",
acadrust::ReadStage::RecordStream,
codec::ReadStage::RecordStream,
initial_error.clone(),
),
);
@@ -933,7 +933,7 @@ struct ReaderFailure {
}
impl ReaderFailure {
fn from_reader(error: acadrust::DxfError) -> Self {
fn from_reader(error: codec::DxfError) -> Self {
Self {
#[cfg(not(target_arch = "wasm32"))]
recoverable: recoverable_reader_error(&error),
@@ -951,40 +951,40 @@ impl ReaderFailure {
}
#[cfg(not(target_arch = "wasm32"))]
fn recoverable_reader_error(error: &acadrust::DxfError) -> bool {
fn recoverable_reader_error(error: &codec::DxfError) -> bool {
matches!(
error,
acadrust::DxfError::Compression(_)
| acadrust::DxfError::Parse(_)
| acadrust::DxfError::InvalidDxfCode(_)
| acadrust::DxfError::InvalidHandle(_)
| acadrust::DxfError::ObjectNotFound(_)
| acadrust::DxfError::InvalidEntityType(_)
| acadrust::DxfError::ChecksumMismatch { .. }
| acadrust::DxfError::InvalidHeader(_)
| acadrust::DxfError::InvalidFormat(_)
| acadrust::DxfError::InvalidSentinel(_)
| acadrust::DxfError::Decompression(_)
| acadrust::DxfError::Encoding(_)
codec::DxfError::Compression(_)
| codec::DxfError::Parse(_)
| codec::DxfError::InvalidDxfCode(_)
| codec::DxfError::InvalidHandle(_)
| codec::DxfError::ObjectNotFound(_)
| codec::DxfError::InvalidEntityType(_)
| codec::DxfError::ChecksumMismatch { .. }
| codec::DxfError::InvalidHeader(_)
| codec::DxfError::InvalidFormat(_)
| codec::DxfError::InvalidSentinel(_)
| codec::DxfError::Decompression(_)
| codec::DxfError::Encoding(_)
)
}
fn merge_read_diagnostics(
target: &mut acadrust::ReadStats,
source: acadrust::ReadStats,
target: &mut codec::ReadStats,
source: codec::ReadStats,
) {
for diagnostic in source.diagnostics {
if !target.diagnostics.contains(&diagnostic) {
acadrust::push_read_diagnostic(&mut target.diagnostics, diagnostic);
codec::push_read_diagnostic(&mut target.diagnostics, diagnostic);
}
}
}
#[cfg(target_arch = "wasm32")]
fn merge_read_stats(
primary: Option<acadrust::ReadStats>,
fallback: Option<acadrust::ReadStats>,
) -> Option<acadrust::ReadStats> {
primary: Option<codec::ReadStats>,
fallback: Option<codec::ReadStats>,
) -> Option<codec::ReadStats> {
match (primary, fallback) {
(Some(mut primary), Some(fallback)) => {
merge_read_diagnostics(&mut primary, fallback);
@@ -999,7 +999,7 @@ fn read_file_attempt(
path: &Path,
progress: Option<Arc<dyn Fn(u16) + Send + Sync>>,
failsafe: bool,
) -> Result<acadrust::ReadOutcome, ReaderFailure> {
) -> Result<codec::ReadOutcome, ReaderFailure> {
let ext = path
.extension()
.map(|e| e.to_string_lossy().to_lowercase())
@@ -1024,12 +1024,12 @@ fn read_file_attempt(
fn finalize_loaded_outcome(
path: &Path,
mut outcome: acadrust::ReadOutcome,
) -> Result<acadrust::ReadOutcome, String> {
mut outcome: codec::ReadOutcome,
) -> Result<codec::ReadOutcome, String> {
let doc = &mut outcome.document;
normalize_block_origins(doc);
normalize_knotless_splines(doc);
if outcome.stats.source_format == Some(acadrust::SourceFormat::Dxf) {
if outcome.stats.source_format == Some(codec::SourceFormat::Dxf) {
fix_dxf_dimension_rotations(doc);
fix_dxf_layout_plot_settings(doc);
}
@@ -1044,7 +1044,7 @@ fn read_dwg_path(
path: &Path,
progress: Option<Arc<dyn Fn(u16) + Send + Sync>>,
failsafe: bool,
) -> Result<acadrust::ReadOutcome, ReaderFailure> {
) -> Result<codec::ReadOutcome, ReaderFailure> {
let options = if failsafe {
DwgReadOptions::failsafe()
} else {
@@ -1071,7 +1071,7 @@ fn read_dwg_path(
// The file shrank or became unreadable mid-parse;
// retry from a snapshot instead of reporting a bare
// I/O error.
Err(acadrust::DxfError::Io(_)) => {}
Err(codec::DxfError::Io(_)) => {}
Err(error) => return Err(ReaderFailure::from_reader(error)),
}
}
@@ -1164,7 +1164,7 @@ fn read_drawing_snapshot(path: &Path) -> std::io::Result<Vec<u8>> {
}
}
fn read_dxf_path(path: &Path, failsafe: bool) -> Result<acadrust::ReadOutcome, ReaderFailure> {
fn read_dxf_path(path: &Path, failsafe: bool) -> Result<codec::ReadOutcome, ReaderFailure> {
DxfReader::from_file(path)
.map_err(ReaderFailure::from_reader)?
.with_configuration(DxfReaderConfiguration {
@@ -1183,8 +1183,8 @@ fn read_dxf_path(path: &Path, failsafe: bool) -> Result<acadrust::ReadOutcome, R
/// insertion can then share the ordinary zero-origin transform without each
/// path having to reinterpret this compatibility field.
fn normalize_block_origins(doc: &mut CadDocument) {
use acadrust::types::Vector3;
use acadrust::EntityType;
use codec::types::Vector3;
use codec::EntityType;
let blocks: Vec<_> = doc
.block_records
@@ -1237,11 +1237,11 @@ fn normalize_block_origins(doc: &mut CadDocument) {
/// the stored path if it exists, otherwise the same file name next to the
/// drawing — and write the result onto the entity so the renderer finds it.
fn resolve_raster_image_paths(doc: &mut CadDocument, base_dir: Option<&Path>) {
use acadrust::objects::ObjectType;
use acadrust::EntityType;
use codec::objects::ObjectType;
use codec::EntityType;
use std::collections::HashMap;
let defs: HashMap<acadrust::Handle, String> = doc
let defs: HashMap<codec::Handle, String> = doc
.objects
.iter()
.filter_map(|(h, o)| match o {
@@ -1338,8 +1338,8 @@ pub fn source_is_dxf(path: Option<&Path>, document: &CadDocument) -> bool {
/// Parse a format string like "DWG 2013" or "DXF 2007" into
/// `(extension, DxfVersion)`. Falls back to ("dwg", AC1032) for unknown strings.
pub fn parse_save_format(format: &str) -> (&'static str, acadrust::DxfVersion) {
use acadrust::DxfVersion;
pub fn parse_save_format(format: &str) -> (&'static str, codec::DxfVersion) {
use codec::DxfVersion;
let f = format.to_ascii_uppercase();
let is_dxf = f.starts_with("DXF");
let ext = if is_dxf { "dxf" } else { "dwg" };
@@ -1364,8 +1364,8 @@ pub fn parse_save_format(format: &str) -> (&'static str, acadrust::DxfVersion) {
/// Parse an explicit automation/CLI target version without silently falling
/// back to a newer format. Accepts either release names (`R14`, `2000`, …)
/// or their DXF codes (`AC1014`, `AC1015`, …).
pub fn parse_target_version(value: &str) -> Result<acadrust::DxfVersion, String> {
use acadrust::DxfVersion;
pub fn parse_target_version(value: &str) -> Result<codec::DxfVersion, String> {
use codec::DxfVersion;
match value.trim().to_ascii_uppercase().as_str() {
"R14" | "14" | "AC1014" => Ok(DxfVersion::AC1014),
"2000" | "AC1015" => Ok(DxfVersion::AC1015),
@@ -1384,8 +1384,8 @@ pub fn parse_target_version(value: &str) -> Result<acadrust::DxfVersion, String>
/// version + DXF/DWG choice (e.g. `AC1018, is_dxf=false` -> `"DWG 2004"`).
/// Used to default the Save-As dropdown to the loaded file's version so a
/// round-trip preserves it instead of silently offering "DWG 2018".
pub fn format_for_version(version: acadrust::DxfVersion, is_dxf: bool) -> String {
use acadrust::DxfVersion::*;
pub fn format_for_version(version: codec::DxfVersion, is_dxf: bool) -> String {
use codec::DxfVersion::*;
let year = match version {
AC1032 => "2018",
AC1027 => "2013",
@@ -1405,8 +1405,8 @@ pub fn format_for_version(version: acadrust::DxfVersion, is_dxf: bool) -> String
/// DXF). Returns 0 for a same-version DWG save, where they round-trip verbatim.
/// Used to warn the user before a lossy Save-As.
pub fn dropped_on_save_count(
doc: &acadrust::CadDocument,
target_version: acadrust::DxfVersion,
doc: &codec::CadDocument,
target_version: codec::DxfVersion,
is_dxf: bool,
) -> usize {
if !is_dxf && doc.dwg_source_version == Some(target_version) {
@@ -1417,7 +1417,7 @@ pub fn dropped_on_save_count(
.objects
.values()
.filter(|object| match object {
acadrust::objects::ObjectType::Unknown {
codec::objects::ObjectType::Unknown {
raw_dxf_codes,
raw_dwg_data,
raw_dwg_version,
@@ -1435,7 +1435,7 @@ pub fn dropped_on_save_count(
.count();
for e in doc.entities() {
let dropped = match e {
acadrust::EntityType::Unknown(entity) => {
codec::EntityType::Unknown(entity) => {
if is_dxf {
entity.raw_dxf_codes.is_none()
} else {
@@ -1586,9 +1586,9 @@ mod save_failure_tests {
));
let error = save_as_version(
&acadrust::CadDocument::new(),
&codec::CadDocument::new(),
&path,
acadrust::DxfVersion::AC1032,
codec::DxfVersion::AC1032,
)
.unwrap_err();
@@ -1615,9 +1615,9 @@ mod save_failure_tests {
std::fs::write(&path, b"new").unwrap();
let error = super::save_owned_as_version_atomic(
acadrust::CadDocument::new(),
codec::CadDocument::new(),
&path,
acadrust::DxfVersion::AC1032,
codec::DxfVersion::AC1032,
false,
Some(expected),
None,
@@ -1649,9 +1649,9 @@ mod save_failure_tests {
std::fs::rename(&replacement, &path).unwrap();
let error = super::save_owned_as_version_atomic(
acadrust::CadDocument::new(),
codec::CadDocument::new(),
&path,
acadrust::DxfVersion::AC1032,
codec::DxfVersion::AC1032,
false,
Some(expected),
Some(reader),
@@ -1747,7 +1747,7 @@ pub async fn pick_embedded_image_file() -> Result<ole_embed::EmbeddedImage, Stri
pub fn save_as_version(
doc: &CadDocument,
path: &Path,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
) -> Result<(), String> {
let clone_started = iced::time::Instant::now();
let snapshot = doc.clone();
@@ -1764,7 +1764,7 @@ pub fn save_as_version(
pub fn save_owned_as_version_atomic(
doc: CadDocument,
path: &Path,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
backup: bool,
expected_fingerprint: Option<edit_lock::FileFingerprint>,
verify_reader: Option<std::fs::File>,
@@ -1790,7 +1790,7 @@ pub fn save_owned_as_version_atomic(
fn save_owned_as_version_inner<F>(
mut doc: CadDocument,
path: &Path,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
backup: bool,
clone_ms: f64,
before_replace: F,
@@ -1918,7 +1918,7 @@ pub fn save(doc: &CadDocument, path: &Path) -> Result<(), String> {
pub fn save_to_bytes(
doc: &CadDocument,
ext: &str,
version: acadrust::DxfVersion,
version: codec::DxfVersion,
) -> Result<Vec<u8>, String> {
let perf = crate::perf::enabled();
let total_started = iced::time::Instant::now();
@@ -1968,7 +1968,7 @@ pub fn save_to_bytes(
// fall back to "Standard". Only overrides when the handle resolves, leaving the
// DXF-provided names intact.
fn fix_current_style_names(doc: &mut CadDocument) {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let h = doc.header.current_text_style_handle;
if h.is_valid() {
@@ -2012,8 +2012,8 @@ fn fix_current_style_names(doc: &mut CadDocument) {
/// Find the handle of the `DictionaryVariable` registered under `name` in any
/// of the document's dictionaries (the variable dictionary).
fn vardict_handle(doc: &CadDocument, name: &str) -> Option<acadrust::Handle> {
use acadrust::objects::ObjectType;
fn vardict_handle(doc: &CadDocument, name: &str) -> Option<codec::Handle> {
use codec::objects::ObjectType;
doc.objects.values().find_map(|o| {
let entries = match o {
ObjectType::Dictionary(d) => &d.entries,
@@ -2030,7 +2030,7 @@ fn vardict_handle(doc: &CadDocument, name: &str) -> Option<acadrust::Handle> {
/// Look up a system-variable value stored in the document's variable
/// dictionary.
fn vardict_value(doc: &CadDocument, name: &str) -> Option<String> {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let handle = vardict_handle(doc, name)?;
match doc.objects.get(&handle) {
Some(ObjectType::DictionaryVariable(v)) => Some(v.value.clone()),
@@ -2071,7 +2071,7 @@ fn reflect_sketch_settings(doc: &mut CadDocument) {
/// Write a drawing variable, creating the variable dictionary and record when
/// needed so new drawings preserve the value too.
pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &str) {
use acadrust::objects::{Dictionary, DictionaryVariable, ObjectType};
use codec::objects::{Dictionary, DictionaryVariable, ObjectType};
if let Some(h) = vardict_handle(doc, name) {
if let Some(ObjectType::DictionaryVariable(v)) = doc.objects.get_mut(&h) {
v.value = value.to_string();
@@ -2163,7 +2163,7 @@ pub fn set_saved_active_layout(doc: &mut CadDocument, name: &str) {
/// DXF additionally writes its own header vars from the names, so this keeps
/// every representation consistent.
fn sync_current_styles_on_save(doc: &mut CadDocument) {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let th = doc
.text_styles
@@ -2202,7 +2202,7 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) {
// ── Corrupt-entity guard ──────────────────────────────────────────────────
//
// acadrust's DWG parser occasionally desynchronises on certain files and
// opencadcodec's DWG parser occasionally desynchronises on certain files and
// produces entities with garbage fields: non-unit normals (components in
// 1e200+), nonsensical vertex counts (e.g. 100000), or infinite/NaN
// coordinates. Tessellating such entities triggers huge allocations and
@@ -2214,7 +2214,7 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) {
//
// Keep valid degenerate geometry: dropping it would trigger strict-open recovery.
fn finite_unit_normal(n: &acadrust::types::Vector3) -> bool {
fn finite_unit_normal(n: &codec::types::Vector3) -> bool {
let (x, y, z) = (n.x, n.y, n.z);
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
return false;
@@ -2229,13 +2229,13 @@ fn finite_coord(v: f64) -> bool {
v.is_finite() && v.abs() < 1.0e12
}
fn finite_vec3(v: &acadrust::types::Vector3) -> bool {
fn finite_vec3(v: &codec::types::Vector3) -> bool {
finite_coord(v.x) && finite_coord(v.y) && finite_coord(v.z)
}
/// Returns true if the entity looks like parser garbage and should be dropped.
pub(crate) fn is_entity_corrupt(e: &EntityType) -> bool {
use acadrust::entities::EntityType as E;
use codec::entities::EntityType as E;
// Reject polylines at or above this vertex count. Even valid drawings
// rarely use this many — and parser desync produces exactly-100_000-vertex
// junk records.
@@ -2346,7 +2346,7 @@ fn normalize_knotless_splines(doc: &mut CadDocument) {
}
let degree = (spline.degree as usize).min(n - 1);
spline.degree = degree as i32;
spline.knots = acadrust::entities::Spline::generate_clamped_knots(degree, n);
spline.knots = codec::entities::Spline::generate_clamped_knots(degree, n);
}
}
@@ -2358,7 +2358,7 @@ pub fn purge_corrupt_entities(doc: &mut CadDocument) -> usize {
// entity references in one pass, test in parallel, then remove serially
// (`remove_entity` needs `&mut doc`).
let entities: Vec<&EntityType> = doc.entities().collect();
let bad: Vec<acadrust::Handle> = entities
let bad: Vec<codec::Handle> = entities
.par_iter()
.filter(|e| is_entity_corrupt(e))
.map(|e| e.common().handle)
@@ -2370,10 +2370,10 @@ pub fn purge_corrupt_entities(doc: &mut CadDocument) -> usize {
n
}
/// acadrust's ViewportStatusFlags::from_bits() maps bit 0 → is_on and bit 15 → locked,
/// opencadcodec's ViewportStatusFlags::from_bits() maps bit 0 → is_on and bit 15 → locked,
/// but the real DXF/DWG spec uses bit 15 (0x8000) → viewport on and bit 14 (0x4000) → locked.
/// Files from AutoCAD and other tools always set bit 15 for active viewports, leaving bit 0
/// clear, so acadrust reads every such viewport as off. Correct that here after loading.
/// clear, so opencadcodec reads every such viewport as off. Correct that here after loading.
fn fix_viewport_status_flags(doc: &mut CadDocument) {
for entity in doc.entities_mut() {
if let EntityType::Viewport(vp) = entity {
@@ -2388,7 +2388,7 @@ fn fix_viewport_status_flags(doc: &mut CadDocument) {
}
}
/// The acadrust DXF reader still stores Shape rotation directly from group
/// The opencadcodec DXF reader still stores Shape rotation directly from group
/// code 50 in degrees, while DWG and our own creation code store radians.
/// Dimension angles and ATTRIB/ATTDEF rotation are converted inside the
/// reader, so arms for them here would convert twice.
@@ -2403,13 +2403,13 @@ fn fix_dxf_dimension_rotations(doc: &mut CadDocument) {
}
}
/// Recover integer-valued AcDbPlotSettings fields that acadrust can leave at
/// Recover integer-valued AcDbPlotSettings fields that opencadcodec can leave at
/// their defaults when a DXF writer right-aligns the value with leading spaces.
/// The raw pairs are preserved on Layout, so trim and parse those authoritative
/// values after loading. In particular, losing code 73 turns a 90°/270° sheet
/// back to 0° and makes a landscape layout render as portrait (#505).
fn fix_dxf_layout_plot_settings(doc: &mut CadDocument) {
use acadrust::objects::{ObjectType, PlotFlags};
use codec::objects::{ObjectType, PlotFlags};
for object in doc.objects.values_mut() {
let ObjectType::Layout(layout) = object else {
@@ -2470,7 +2470,7 @@ fn fix_dxf_layout_plot_settings(doc: &mut CadDocument) {
#[cfg(test)]
mod layer_roundtrip_tests {
use super::*;
use acadrust::tables::layer::Layer as DocLayer;
use codec::tables::layer::Layer as DocLayer;
// Add `count` new layers the way the UI does (allocate_handle, then add),
// round-trip through `ext`, and return whether every one survived.
@@ -2484,7 +2484,7 @@ mod layer_roundtrip_tests {
doc.layers.add(dl).unwrap();
}
let path = std::env::temp_dir().join(format!("ocs_layer_rt_{count}.{ext}"));
save_as_version(&doc, &path, acadrust::DxfVersion::AC1032).expect("save");
save_as_version(&doc, &path, codec::DxfVersion::AC1032).expect("save");
let loaded = load_file(&path).expect("load");
let _ = std::fs::remove_file(&path);
names.iter().all(|n| loaded.layers.contains(n))
@@ -2513,8 +2513,8 @@ mod layer_roundtrip_tests {
// (which reopens as layer "0").
#[test]
fn dwg_preserves_entity_layer_auto_registered_on_add() {
use acadrust::entities::Point;
use acadrust::EntityType;
use codec::entities::Point;
use codec::EntityType;
let mut scene = crate::scene::Scene::new();
crate::io::linetypes::populate_document(&mut scene.document);
@@ -2525,7 +2525,7 @@ mod layer_roundtrip_tests {
assert!(!h.is_null(), "entity was not added");
let path = std::env::temp_dir().join("ocs_entity_layer_rt.dwg");
save_as_version(&scene.document, &path, acadrust::DxfVersion::AC1032).expect("save");
save_as_version(&scene.document, &path, codec::DxfVersion::AC1032).expect("save");
let loaded = load_file(&path).expect("load");
let _ = std::fs::remove_file(&path);
@@ -2548,8 +2548,8 @@ mod layer_roundtrip_tests {
#[cfg(test)]
mod corrupt_guard_tests {
use super::*;
use acadrust::entities::{Arc, Circle, EntityType, Spline};
use acadrust::types::Vector3;
use codec::entities::{Arc, Circle, EntityType, Spline};
use codec::types::Vector3;
fn knotless_spline(degree: i32, points: usize) -> Spline {
let mut spline = Spline::new();
@@ -2583,7 +2583,7 @@ mod corrupt_guard_tests {
"ocs_knotless_spline_{}.dwg",
std::process::id()
));
save_as_version(&doc, &path, acadrust::DxfVersion::AC1032).expect("save");
save_as_version(&doc, &path, codec::DxfVersion::AC1032).expect("save");
let _ = std::fs::remove_file(&path);
}
@@ -2608,7 +2608,7 @@ mod corrupt_guard_tests {
.expect("spline");
assert_eq!(spline.degree, 1);
assert_eq!(spline.knots.len(), 4);
save_as_version(&doc, &target, acadrust::DxfVersion::AC1032).expect("save");
save_as_version(&doc, &target, codec::DxfVersion::AC1032).expect("save");
let _ = std::fs::remove_file(&target);
}
@@ -2723,7 +2723,7 @@ mod corrupt_guard_tests {
// drive the render graph's per-instance allocation and expansion.
#[test]
fn preserves_large_minsert_data_while_rendering_is_bounded() {
let mut i = acadrust::entities::Insert::new("BLOCK", Vector3::ZERO);
let mut i = codec::entities::Insert::new("BLOCK", Vector3::ZERO);
i.row_count = u16::MAX;
i.column_count = u16::MAX;
assert!(!is_entity_corrupt(&EntityType::Insert(i)));
@@ -2732,7 +2732,7 @@ mod corrupt_guard_tests {
// An ordinary array insert, well under the budget, is valid source data.
#[test]
fn keeps_a_reasonable_minsert() {
let mut i = acadrust::entities::Insert::new("BLOCK", Vector3::ZERO);
let mut i = codec::entities::Insert::new("BLOCK", Vector3::ZERO);
i.row_count = 10;
i.column_count = 10;
i.row_spacing = 5.0;
@@ -2743,7 +2743,7 @@ mod corrupt_guard_tests {
// A plain (non-array) INSERT is never treated as a MINSERT-count problem.
#[test]
fn keeps_a_plain_insert() {
let i = acadrust::entities::Insert::new("BLOCK", Vector3::ZERO);
let i = codec::entities::Insert::new("BLOCK", Vector3::ZERO);
assert!(!is_entity_corrupt(&EntityType::Insert(i)));
}
}
+17 -17
View File
@@ -4,21 +4,21 @@
// so a drawing copied to another machine loses its pictures. An OLE2FRAME
// instead carries the encoded raster inside an OLE compound file embedded in
// the entity data, so
// the .dwg is self-contained. cadcodec both builds the compound storage and
// the .dwg is self-contained. opencadcodec both builds the compound storage and
// reads it back, and the renderer already paints OLE2FRAMEs via
// ImageModel::from_ole2frame, so this module only packs and unpacks.
use std::io::Write as _;
use std::path::Path;
use acadrust::compound_file::{
use codec::compound_file::{
BinaryRecord, CompoundEntry, CompoundFile, CompoundStorage, CompoundStream,
CompoundStreamContent, StructuredStoragePayload,
};
use acadrust::entities::{Ole2Frame, OleFrameEnvelope, OleObjectType};
use acadrust::types::Vector3;
use codec::entities::{Ole2Frame, OleFrameEnvelope, OleObjectType};
use codec::types::Vector3;
/// OLE compound-file stream holding OLE 1.0 native data. cadcodec's
/// OLE compound-file stream holding OLE 1.0 native data. opencadcodec's
/// presentation extractor looks here first; the body is the encoded raster
/// behind a 4-byte little-endian length.
const OLE10NATIVE: &str = "\u{1}Ole10Native";
@@ -109,7 +109,7 @@ pub fn corners_from_placement(origin: Vector3, width: f64, aspect: f64) -> (Vect
/// Pack `image` into an OLE2FRAME spanning `upper_left` → `lower_right` in
/// world space. The picture rides in an `\x01Ole10Native` stream as OLE 1.0
/// native data behind the geometry envelope cadcodec writes around it.
/// native data behind the geometry envelope opencadcodec writes around it.
pub fn build_embedded_ole(
image: &EmbeddedImage,
upper_left: Vector3,
@@ -161,14 +161,14 @@ pub fn build_embedded_ole(
/// Non-interactive embed used by the control API: add the entity to `doc` and
/// return its handle. The undo snapshot is the caller's responsibility.
pub fn add_embedded_image(
doc: &mut acadrust::CadDocument,
doc: &mut codec::CadDocument,
image: &EmbeddedImage,
origin: Vector3,
width: f64,
) -> Result<acadrust::Handle, String> {
) -> Result<codec::Handle, String> {
let (upper_left, lower_right) = corners_from_placement(origin, width, image.aspect());
let ole = build_embedded_ole(image, upper_left, lower_right);
doc.add_entity(acadrust::EntityType::Ole2Frame(ole))
doc.add_entity(codec::EntityType::Ole2Frame(ole))
.map_err(|e| e.to_string())
}
@@ -218,8 +218,8 @@ mod tests {
Vector3::new(4.0, 0.0, 0.0),
);
let payload = ole.encoded_payload();
match acadrust::entities::extract_presentation(&payload) {
Some(acadrust::entities::OlePresentation::Raster(bytes)) => assert_eq!(bytes, png),
match codec::entities::extract_presentation(&payload) {
Some(codec::entities::OlePresentation::Raster(bytes)) => assert_eq!(bytes, png),
other => panic!("expected the embedded raster back, got {other:?}"),
}
}
@@ -233,28 +233,28 @@ mod tests {
pixel_height: 3,
name: "t.png".into(),
};
let mut doc = acadrust::CadDocument::new();
let mut doc = codec::CadDocument::new();
let handle =
add_embedded_image(&mut doc, &image, Vector3::new(10.0, 10.0, 0.0), 20.0).unwrap();
let dir = std::env::temp_dir().join("ocs_ole_embed_tests");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("embedded_roundtrip.dwg");
acadrust::DwgWriter::write_to_file(&path, &doc).unwrap();
codec::DwgWriter::write_to_file(&path, &doc).unwrap();
let mut reader = acadrust::io::dwg::DwgReader::from_file(&path).unwrap();
let mut reader = codec::io::dwg::DwgReader::from_file(&path).unwrap();
let reopened = reader.read().unwrap();
std::fs::remove_file(&path).ok();
let ole = match reopened.get_entity(handle) {
Some(acadrust::EntityType::Ole2Frame(ole)) => ole,
Some(codec::EntityType::Ole2Frame(ole)) => ole,
other => panic!("expected the Ole2Frame back, got {other:?}"),
};
// Corners survive: 20 wide at aspect 4:3 → 15 tall, base at (10,10).
assert_eq!(ole.lower_right_corner, Vector3::new(30.0, 10.0, 0.0));
assert_eq!(ole.upper_left_corner, Vector3::new(10.0, 25.0, 0.0));
match acadrust::entities::extract_presentation(&ole.encoded_payload()) {
Some(acadrust::entities::OlePresentation::Raster(bytes)) => assert_eq!(bytes, png),
match codec::entities::extract_presentation(&ole.encoded_payload()) {
Some(codec::entities::OlePresentation::Raster(bytes)) => assert_eq!(bytes, png),
other => panic!("expected the embedded raster back, got {other:?}"),
}
}
+1 -1
View File
@@ -1093,7 +1093,7 @@ fn emit_wire_fills(
boundary_exterior: None,
boundary_sources: None,
boundary_paths: None,
style: acadrust::entities::HatchStyleType::Normal,
style: codec::entities::HatchStyleType::Normal,
pattern: pattern.clone(),
name: "PLOTSTYLE".to_string(),
color: wire.color,
+2 -2
View File
@@ -231,7 +231,7 @@ impl PlotStyleTable {
"grayscale.ctb" => {
let mut table = Self::identity(GRAYSCALE_PLOT_STYLE);
for aci in 1..=255u8 {
if let Some((r, g, b)) = acadrust::types::aci_to_rgb(aci) {
if let Some((r, g, b)) = codec::types::aci_to_rgb(aci) {
let gray = if aci == 7 {
0
} else {
@@ -332,7 +332,7 @@ impl PlotStyleTable {
if aci == 7 {
Some([0.0; 3])
} else {
acadrust::types::aci_to_rgb(aci).map(|(r, g, b)| {
codec::types::aci_to_rgb(aci).map(|(r, g, b)| {
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
})
}
+9 -9
View File
@@ -22,7 +22,7 @@ pub struct RecoveryReport {
pub source_path: Option<PathBuf>,
pub size_bytes: u64,
pub source_sha256: Option<String>,
pub read_stats: Option<acadrust::ReadStats>,
pub read_stats: Option<codec::ReadStats>,
pub entities_scanned: usize,
pub entities_removed: usize,
pub referenced_entities_removed: usize,
@@ -35,7 +35,7 @@ pub struct RecoveryReport {
pub references_failed: usize,
pub references_skipped: usize,
pub reference_details: Vec<(String, String, String, Option<String>)>,
pub reference_stats: Vec<(String, acadrust::ReadStats)>,
pub reference_stats: Vec<(String, codec::ReadStats)>,
pub timings: OpenTimings,
pub total_ms: u32,
pub failure_phase: Option<String>,
@@ -52,12 +52,12 @@ impl RecoveryReport {
path: &Path,
size_bytes: u64,
source_sha256: Option<String>,
read_stats: Option<acadrust::ReadStats>,
read_stats: Option<codec::ReadStats>,
entities_scanned: usize,
entities_removed: usize,
referenced_entities_removed: usize,
references: &[XrefInfo],
notifications: &acadrust::notification::NotificationCollection,
notifications: &codec::notification::NotificationCollection,
save_as_required: bool,
timings: OpenTimings,
total_ms: u32,
@@ -124,7 +124,7 @@ impl RecoveryReport {
.iter()
.filter(|item| {
item.notification_type
== acadrust::notification::NotificationType::Error
== codec::notification::NotificationType::Error
})
.count()
});
@@ -193,7 +193,7 @@ impl RecoveryReport {
file_name: String,
size_bytes: u64,
source_sha256: Option<String>,
read_stats: Option<acadrust::ReadStats>,
read_stats: Option<codec::ReadStats>,
failure_phase: String,
error: String,
total_ms: u32,
@@ -259,7 +259,7 @@ impl RecoveryReport {
lines.push(format!("Application revision: {}", env!("OCS_GIT_REV")));
lines.push(format!("Build profile: {}", env!("OCS_BUILD_PROFILE")));
lines.push(format!("Build features: {}", env!("OCS_BUILD_FEATURES")));
lines.push(format!("Reader version: {}", acadrust::VERSION));
lines.push(format!("Reader version: {}", codec::VERSION));
lines.push(format!("Reader revision: {}", reader_revision()));
lines.push(format!("Platform: {}", diagnostic_platform()));
lines.push(format!("Created (Unix seconds): {}", self.created_unix_seconds));
@@ -543,7 +543,7 @@ fn redact_source_path(message: &str, source: Option<&Path>) -> String {
redacted
}
fn structured_diagnostic_line(diagnostic: &acadrust::ReadDiagnostic) -> String {
fn structured_diagnostic_line(diagnostic: &codec::ReadDiagnostic) -> String {
let section = diagnostic.section.as_deref().unwrap_or("-");
let offset = diagnostic
.source_offset
@@ -579,7 +579,7 @@ fn structured_diagnostic_line(diagnostic: &acadrust::ReadDiagnostic) -> String {
fn reader_revision() -> String {
include_str!("../../Cargo.toml")
.lines()
.find(|line| line.trim_start().starts_with("acadrust = { git ="))
.find(|line| line.trim_start().starts_with("opencadcodec = { git ="))
.and_then(|line| line.split("rev = \"").nth(1))
.and_then(|value| value.split('"').next())
.filter(|value| !value.is_empty())
+2 -2
View File
@@ -1,13 +1,13 @@
//! DWG preview thumbnails.
//!
//! - [`from_screenshot`] crops the visible drawing area into a small
//! [`acadrust::Preview`] embedded on save.
//! [`codec::Preview`] embedded on save.
//! - [`read_handle`] / [`extract_to_png`] read a DWG's *embedded* preview back
//! for the Start page and the OS file-manager thumbnailer. Extraction lives in
//! the shared [`dwg_thumbnailer`] core crate (also used by the Windows/macOS
//! thumbnail handlers).
use acadrust::{Preview, PreviewFormat};
use codec::{Preview, PreviewFormat};
use iced::Rectangle;
use image::{ImageFormat, RgbImage};
use std::io::Cursor;
+7 -7
View File
@@ -31,14 +31,14 @@ pub(super) async fn parse_document(
bytes: &[u8],
recovery_mode: bool,
initial_error: &str,
) -> Result<(acadrust::ReadOutcome, Option<String>), super::OpenLoadError> {
) -> Result<(codec::ReadOutcome, Option<String>), super::OpenLoadError> {
let options = WorkerOptions::new();
options.set_type(WorkerType::Module);
let worker = Worker::new_with_options(WORKER_URL, &options)
.map_err(|error| super::OpenLoadError::from(js_error(error)))?;
let (sender, receiver) = iced::futures::channel::oneshot::channel::<
Result<(acadrust::ReadOutcome, Option<String>), super::OpenLoadError>,
Result<(codec::ReadOutcome, Option<String>), super::OpenLoadError>,
>();
let sender = Rc::new(RefCell::new(Some(sender)));
let message_sender = sender.clone();
@@ -56,8 +56,8 @@ pub(super) async fn parse_document(
let payload: (
u16,
Result<
acadrust::ReadOutcome,
(String, Option<acadrust::ReadStats>),
codec::ReadOutcome,
(String, Option<codec::ReadStats>),
>,
Option<String>,
bool,
@@ -143,12 +143,12 @@ pub(super) async fn parse_document(
}
fn restore_entity_runtime_fields(
document: &mut acadrust::CadDocument,
document: &mut codec::CadDocument,
fields: Vec<EntityRuntimeFields>,
) {
let handle = |value: Option<u64>| value.map(acadrust::Handle::new);
let handle = |value: Option<u64>| value.map(codec::Handle::new);
for fields in fields {
let Some(entity) = document.get_entity_mut(acadrust::Handle::new(fields.handle)) else {
let Some(entity) = document.get_entity_mut(codec::Handle::new(fields.handle)) else {
continue;
};
let common = entity.common_mut();
+102 -102
View File
@@ -1,10 +1,10 @@
// XREF resolution — scan a loaded document for external-reference blocks and
// populate them with geometry from the referenced DWG/DXF files.
use acadrust::entities::{Block, BlockEnd};
use acadrust::tables::TableEntry;
use acadrust::types::{Handle, Vector3};
use acadrust::{CadDocument, EntityType};
use codec::entities::{Block, BlockEnd};
use codec::tables::TableEntry;
use codec::types::{Handle, Vector3};
use codec::{CadDocument, EntityType};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::path::{Path, PathBuf};
@@ -40,7 +40,7 @@ pub struct XrefInfo {
pub status: XrefStatus,
/// Reader diagnostics retained for the recovery report.
pub diagnostics: Vec<String>,
pub read_stats: Option<acadrust::ReadStats>,
pub read_stats: Option<codec::ReadStats>,
pub source_sha256: Option<String>,
}
@@ -119,7 +119,7 @@ fn resolve_xrefs_with_filter(
String,
Handle,
Option<PathBuf>,
Option<Result<acadrust::ReadOutcome, String>>,
Option<Result<codec::ReadOutcome, String>>,
Option<String>,
Option<SourceFingerprint>,
)> = xref_entries
@@ -217,7 +217,7 @@ fn resolve_xrefs_with_filter(
|| outcome.stats.skipped_source_records > 0
|| !outcome.stats.stream_completed
|| outcome.document.notifications.iter().any(|item| {
item.notification_type == acadrust::notification::NotificationType::Error
item.notification_type == codec::notification::NotificationType::Error
});
let mut diagnostics: Vec<String> = outcome
.document
@@ -311,7 +311,7 @@ fn resolve_xrefs_with_filter(
#[cfg(not(target_arch = "wasm32"))]
pub fn register_underlay_sources(doc: &CadDocument, base_dir: &Path) {
for object in doc.objects.values() {
let acadrust::objects::ObjectType::UnderlayDefinition(def) = object else {
let codec::objects::ObjectType::UnderlayDefinition(def) = object else {
continue;
};
let stored = def.file_path.as_str();
@@ -328,9 +328,9 @@ pub fn register_underlay_sources(doc: &CadDocument, base_dir: &Path) {
/// "Layer property overrides" row.
#[derive(Clone, PartialEq)]
struct LayerLook {
color: acadrust::types::Color,
color: codec::types::Color,
line_type: String,
line_weight: acadrust::types::LineWeight,
line_weight: codec::types::LineWeight,
off: bool,
frozen: bool,
locked: bool,
@@ -338,7 +338,7 @@ struct LayerLook {
}
impl LayerLook {
fn of(layer: &acadrust::tables::Layer) -> Self {
fn of(layer: &codec::tables::Layer) -> Self {
Self {
color: layer.color.clone(),
line_type: layer.line_type.to_uppercase(),
@@ -726,7 +726,7 @@ fn import_xref_symbols(
// so without an import every bound image loses its file link. Fresh
// handle, detached owner; the path stays verbatim here.
{
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
for (handle, obj) in xref_doc.objects.iter() {
match obj {
ObjectType::ImageDefinition(def) => {
@@ -798,7 +798,7 @@ fn remap_xref_entity(
}
// ── Text-style names (incl. embedded MText in attributes) ──
let remap_mtext_style = |mtext: &mut acadrust::entities::MText, maps: &XrefSymbolMaps| {
let remap_mtext_style = |mtext: &mut codec::entities::MText, maps: &XrefSymbolMaps| {
rename(&maps.text_styles, &mut mtext.style);
};
match entity {
@@ -862,7 +862,7 @@ fn remap_xref_entity(
img.definition_handle = Some(new);
// Keep the entity path in sync with its definition (bind
// COLLECT retargets the definition before merging).
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
if let Some(ObjectType::ImageDefinition(def)) = host.objects.get(&new) {
img.file_path = def.file_name.clone();
}
@@ -889,7 +889,7 @@ fn copy_xref_sortents(
br_handle_map: &HashMap<Handle, Handle>,
entity_handle_map: &HashMap<Handle, Handle>,
) {
use acadrust::objects::{ObjectType, SortEntitiesTable};
use codec::objects::{ObjectType, SortEntitiesTable};
let xref_ms_handle = xref_doc.header.model_space_block_handle;
for obj in xref_doc.objects.values() {
let ObjectType::SortEntitiesTable(t) = obj else {
@@ -1052,7 +1052,7 @@ fn merge_source_entities(
continue;
};
entity.common_mut().owner_handle = new_owner;
// Clear the foreign handle so acadrust assigns a new one.
// Clear the foreign handle so opencadcodec assigns a new one.
set_handle(&mut entity, Handle::NULL);
if let Ok(new_h) = target.add_entity(entity) {
entity_handle_map.insert(old_h, new_h);
@@ -1106,7 +1106,7 @@ pub use crate::io::xref_model::child_key;
/// nested file that fails to parse is skipped silently. Synchronous by
/// design; async stat is Task 8.
pub fn collect_entries(
doc: &acadrust::CadDocument,
doc: &codec::CadDocument,
base_dir: &Path,
unloaded: &std::collections::HashSet<crate::io::xref_model::UnloadKey>,
) -> Vec<crate::io::xref_model::ReferenceEntry> {
@@ -1116,13 +1116,13 @@ pub fn collect_entries(
/// [`collect_entries`] with a load-time mtime baseline for `Stale` detection.
/// See [`collect_entries`] for the full contract.
pub fn collect_entries_with_prev(
doc: &acadrust::CadDocument,
doc: &codec::CadDocument,
base_dir: &Path,
unloaded: &std::collections::HashSet<crate::io::xref_model::UnloadKey>,
prev: &std::collections::HashMap<u64, std::time::SystemTime>,
) -> Vec<crate::io::xref_model::ReferenceEntry> {
use acadrust::entities::UnderlayType;
use acadrust::objects::ObjectType;
use codec::entities::UnderlayType;
use codec::objects::ObjectType;
use crate::io::xref_model::{
child_key, decide_status, normalize_lexical, RefKind, RefStatus, RefType,
ReferenceEntry, UnloadKey,
@@ -1385,7 +1385,7 @@ fn find_target(doc: &CadDocument, key: u64) -> Option<RefTarget> {
}
}
for (handle, obj) in doc.objects.iter() {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
match obj {
ObjectType::ImageDefinition(def) if handle.value() == key => {
return Some(RefTarget::Image {
@@ -1437,7 +1437,7 @@ pub fn unload_reference(doc: &mut CadDocument, key: u64) -> Result<String, Strin
}
// Drop draw-order overrides owned by the unloaded block.
{
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let dead: Vec<Handle> = doc
.objects
.iter()
@@ -1459,7 +1459,7 @@ pub fn unload_reference(doc: &mut CadDocument, key: u64) -> Result<String, Strin
RefTarget::Image { name, .. } => Ok(name),
// A PDF keeps its unloaded state on the definition (saved with it).
RefTarget::Pdf { handle, name } => {
if let Some(acadrust::objects::ObjectType::UnderlayDefinition(def)) =
if let Some(codec::objects::ObjectType::UnderlayDefinition(def)) =
doc.objects.get_mut(&handle)
{
def.unloaded = true;
@@ -1513,7 +1513,7 @@ pub fn detach_reference(doc: &mut CadDocument, key: u64) -> Result<String, Strin
remove_pipe_symbols(doc, &name);
// Draw-order tables owned by the detached block.
{
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let dead: Vec<Handle> = doc
.objects
.iter()
@@ -1682,8 +1682,8 @@ fn load_bind_source(ref_name: &str, found: &Path) -> Result<CadDocument, String>
/// Reject PDF underlays anywhere in a bind closure: vector import is
/// unavailable, and silently dropping them would lose data.
fn check_no_pdf_underlays(source: &CadDocument) -> Result<(), String> {
use acadrust::entities::UnderlayType;
use acadrust::objects::ObjectType;
use codec::entities::UnderlayType;
use codec::objects::ObjectType;
let has_pdf = source.objects.values().any(|o| {
matches!(o, ObjectType::UnderlayDefinition(d) if d.underlay_type == UnderlayType::Pdf)
});
@@ -1794,7 +1794,7 @@ fn collect_bind_images(
host_dir: &Path,
only: Option<Handle>,
) -> Result<(), String> {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let defs: Vec<(Handle, String)> = source
.objects
.iter()
@@ -1973,7 +1973,7 @@ fn remove_dependent_symbols(doc: &mut CadDocument, name: &str, layers_too: bool)
doc.remove_entity(h);
}
{
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
let dead: Vec<Handle> = doc
.objects
.iter()
@@ -2060,7 +2060,7 @@ fn remove_dependent_symbols(doc: &mut CadDocument, name: &str, layers_too: bool)
fn snapshot_dependent_layers(
doc: &CadDocument,
name: &str,
) -> HashMap<String, acadrust::tables::Layer> {
) -> HashMap<String, codec::tables::Layer> {
let prefix = format!("{}|", name.to_uppercase());
doc.layers
.names()
@@ -2082,7 +2082,7 @@ fn snapshot_dependent_layers(
fn restore_dependent_layers(
doc: &mut CadDocument,
name: &str,
kept: HashMap<String, acadrust::tables::Layer>,
kept: HashMap<String, codec::tables::Layer>,
) {
if kept.is_empty() {
return;
@@ -2131,7 +2131,7 @@ pub fn set_ref_path(
Err(crate::t!("XREF: no loaded reference with that key.").to_string())
}
RefTarget::Image { handle, name } => {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
match doc.objects.get_mut(&handle) {
Some(ObjectType::ImageDefinition(def)) => {
def.file_name = new_raw.to_string();
@@ -2141,7 +2141,7 @@ pub fn set_ref_path(
}
}
RefTarget::Pdf { handle, name } => {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
match doc.objects.get_mut(&handle) {
Some(ObjectType::UnderlayDefinition(def)) => {
def.file_path = new_raw.to_string();
@@ -2168,14 +2168,14 @@ pub fn apply_pathtype(
.map(|b| b.xref_path.clone())
.unwrap_or_default(),
Some(RefTarget::Image { handle, .. }) => {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
match doc.objects.get(&handle) {
Some(ObjectType::ImageDefinition(def)) => def.file_name.clone(),
_ => String::new(),
}
}
Some(RefTarget::Pdf { handle, .. }) => {
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
match doc.objects.get(&handle) {
Some(ObjectType::UnderlayDefinition(def)) => def.file_path.clone(),
_ => String::new(),
@@ -2260,7 +2260,7 @@ pub fn replace_path_prefix(doc: &mut CadDocument, old: &str, new: &str) -> usize
}
}
{
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
for obj in doc.objects.values_mut() {
match obj {
ObjectType::ImageDefinition(def) if !def.file_name.is_empty() => {
@@ -2366,7 +2366,7 @@ pub fn rebase_relative_paths_for_save_as(
rebase(&mut br.xref_path);
}
{
use acadrust::objects::ObjectType;
use codec::objects::ObjectType;
for obj in doc.objects.values_mut() {
match obj {
ObjectType::ImageDefinition(def) if !def.file_name.is_empty() => {
@@ -2408,8 +2408,8 @@ mod tests {
std::fs::write(&tmp, b"").unwrap();
let saved = tmp.to_string_lossy().into_owned();
let mut doc = acadrust::CadDocument::new();
let mut br = acadrust::tables::BlockRecord::new("PLAN");
let mut doc = codec::CadDocument::new();
let mut br = codec::tables::BlockRecord::new("PLAN");
br.flags.is_xref = true;
br.xref_path = saved.clone();
br.handle = doc.allocate_handle();
@@ -2426,9 +2426,9 @@ mod tests {
assert_eq!(entries[0].kind, RefKind::DwgXref);
}
fn xref_doc_with(name: &str, saved: &str) -> (acadrust::CadDocument, u64) {
let mut doc = acadrust::CadDocument::new();
let mut br = acadrust::tables::BlockRecord::new(name);
fn xref_doc_with(name: &str, saved: &str) -> (codec::CadDocument, u64) {
let mut doc = codec::CadDocument::new();
let mut br = codec::tables::BlockRecord::new(name);
br.flags.is_xref = true;
br.xref_path = saved.to_string();
br.handle = doc.allocate_handle();
@@ -2454,8 +2454,8 @@ mod tests {
// mid.dwgxrefs inner.dwg; both must parse (enumeration reads the
// mid file's block-record table). Returns (mid, inner) abs paths.
std::fs::write(dir.join("inner.dwg"), b"inner-bytes").unwrap();
let mut mid = acadrust::CadDocument::new();
let mut br = acadrust::tables::BlockRecord::new("INNER");
let mut mid = codec::CadDocument::new();
let mut br = codec::tables::BlockRecord::new("INNER");
br.flags.is_xref = true;
br.xref_path = dir.join("inner.dwg").to_string_lossy().into_owned();
mid.block_records.add(br).unwrap();
@@ -2503,7 +2503,7 @@ mod tests {
// referenced image + unreferenced PDF): attach-equivalent list →
// unload → targeted reload → detach. Catches integration skew that
// unit tests on single ops cannot.
use acadrust::objects::{ImageDefinition, ObjectType, UnderlayDefinition};
use codec::objects::{ImageDefinition, ObjectType, UnderlayDefinition};
let dir = xref_tmpdir("e2e");
let (mid_path, _) = write_nested_fixture(&dir);
std::fs::write(dir.join("img.png"), b"fake-png").unwrap();
@@ -2514,14 +2514,14 @@ mod tests {
let mut img_def = ImageDefinition::with_dimensions("img.png", 8, 8);
img_def.handle = img_h;
doc.objects.insert(img_h, ObjectType::ImageDefinition(img_def));
let mut img = acadrust::entities::RasterImage::new(
let mut img = codec::entities::RasterImage::new(
"img.png",
acadrust::types::Vector3::ZERO,
codec::types::Vector3::ZERO,
8.0,
8.0,
);
img.definition_handle = Some(img_h);
doc.add_entity(acadrust::EntityType::RasterImage(img)).unwrap();
doc.add_entity(codec::EntityType::RasterImage(img)).unwrap();
// Unreferenced PDF definition → listed, never merged.
let pdf_h = doc.allocate_handle();
let mut pdf_def = UnderlayDefinition::pdf("doc.pdf", "1");
@@ -2594,8 +2594,8 @@ mod tests {
#[test]
fn overlay_on_image_errors() {
use acadrust::objects::{ImageDefinition, ObjectType};
let mut doc = acadrust::CadDocument::new();
use codec::objects::{ImageDefinition, ObjectType};
let mut doc = codec::CadDocument::new();
let h = doc.allocate_handle();
let mut def = ImageDefinition::with_dimensions("img.png", 8, 8);
def.handle = h;
@@ -2621,10 +2621,10 @@ mod tests {
fn unload_drops_merged_entities_keeps_definition() {
let (mut doc, key) = xref_doc_with("PLAN", "missing.dwg");
let br_h = doc.block_records.get("PLAN").unwrap().handle;
let mut line = acadrust::entities::Line::new();
line.common.handle = acadrust::types::Handle::NULL;
let mut line = codec::entities::Line::new();
line.common.handle = codec::types::Handle::NULL;
line.common.owner_handle = br_h;
doc.add_entity(acadrust::EntityType::Line(line)).unwrap();
doc.add_entity(codec::EntityType::Line(line)).unwrap();
assert_eq!(doc.block_records.get("PLAN").unwrap().entity_handles.len(), 1);
unload_reference(&mut doc, key).expect("unload");
assert!(doc.block_records.get("PLAN").is_some());
@@ -2638,13 +2638,13 @@ mod tests {
#[test]
fn detach_erases_inserts_and_definition() {
let (mut doc, key) = xref_doc_with("PLAN", "missing.dwg");
let ins = acadrust::entities::Insert::new("PLAN", acadrust::types::Vector3::ZERO);
doc.add_entity(acadrust::EntityType::Insert(ins)).unwrap();
let ins = codec::entities::Insert::new("PLAN", codec::types::Vector3::ZERO);
doc.add_entity(codec::EntityType::Insert(ins)).unwrap();
detach_reference(&mut doc, key).expect("detach");
assert!(doc.block_records.get("PLAN").is_none());
assert!(!doc.entities().any(|e| matches!(
e,
acadrust::EntityType::Insert(i) if i.block_name.eq_ignore_ascii_case("PLAN")
codec::EntityType::Insert(i) if i.block_name.eq_ignore_ascii_case("PLAN")
)));
}
@@ -2654,22 +2654,22 @@ mod tests {
// must survive purge + re-merge. Exercises the mechanism directly:
// snapshot → mutate to defaults → restore → overrides back.
let (mut doc, _) = xref_doc_with("PLAN", "plan.dwg");
let mut layer = acadrust::tables::Layer::new("PLAN|WALLS");
let mut layer = codec::tables::Layer::new("PLAN|WALLS");
layer.flags.off = true;
layer.flags.frozen = true;
layer.color = acadrust::types::Color::from_index(1);
layer.color = codec::types::Color::from_index(1);
doc.layers.add_or_replace(layer);
let kept = snapshot_dependent_layers(&doc, "PLAN");
assert!(kept.contains_key("PLAN|WALLS"));
// Simulate the purge + file-default re-merge.
doc.layers.remove("PLAN|WALLS");
doc.layers.add_or_replace(acadrust::tables::Layer::new("PLAN|WALLS"));
doc.layers.add_or_replace(codec::tables::Layer::new("PLAN|WALLS"));
assert!(!doc.layers.get("PLAN|WALLS").unwrap().flags.off);
restore_dependent_layers(&mut doc, "PLAN", kept);
let back = doc.layers.get("PLAN|WALLS").unwrap();
assert!(back.flags.off, "off override restored");
assert!(back.flags.frozen, "frozen override restored");
assert_eq!(back.color, acadrust::types::Color::from_index(1));
assert_eq!(back.color, codec::types::Color::from_index(1));
}
#[test]
@@ -2677,7 +2677,7 @@ mod tests {
// Layers gone from the new file version stay gone — restore only
// touches entries present post-merge, never resurrects.
let (mut doc, _) = xref_doc_with("PLAN", "plan.dwg");
let mut layer = acadrust::tables::Layer::new("PLAN|GONE");
let mut layer = codec::tables::Layer::new("PLAN|GONE");
layer.flags.off = true;
doc.layers.add_or_replace(layer);
let kept = snapshot_dependent_layers(&doc, "PLAN");
@@ -2789,9 +2789,9 @@ mod tests {
fn write_xref_file(
dir: &std::path::Path,
name: &str,
build: impl FnOnce(&mut acadrust::CadDocument),
build: impl FnOnce(&mut codec::CadDocument),
) -> String {
let mut doc = acadrust::CadDocument::new();
let mut doc = codec::CadDocument::new();
build(&mut doc);
let bytes = crate::io::save_to_bytes(&doc, "dwg", doc.version).expect("save xref");
let path = dir.join(name);
@@ -2799,15 +2799,15 @@ mod tests {
path.to_string_lossy().into_owned()
}
fn line_on(layer: &str) -> acadrust::EntityType {
let mut line = acadrust::entities::Line::new();
fn line_on(layer: &str) -> codec::EntityType {
let mut line = codec::entities::Line::new();
line.common.layer = layer.to_string();
acadrust::EntityType::Line(line)
codec::EntityType::Line(line)
}
fn add_layer(doc: &mut acadrust::CadDocument, name: &str) {
fn add_layer(doc: &mut codec::CadDocument, name: &str) {
doc.layers
.add(acadrust::tables::Layer::new(name))
.add(codec::tables::Layer::new(name))
.expect("add layer");
}
@@ -2868,7 +2868,7 @@ mod tests {
let plan_path = write_xref_file(&dir, "plan.dwg", |doc| {
add_layer(doc, "PLANLAYER");
doc.add_entity(line_on("PLANLAYER")).unwrap();
let mut br = acadrust::tables::BlockRecord::new("DETAIL");
let mut br = codec::tables::BlockRecord::new("DETAIL");
br.flags.is_xref = true;
br.xref_path = detail_path.clone();
doc.block_records.add(br).expect("add nested xref");
@@ -2893,30 +2893,30 @@ mod tests {
let xref_path = write_xref_file(&dir, "plan.dwg", |doc| {
add_layer(doc, "WALLS");
// Nested block with content + a model-space INSERT of it.
let mut door = acadrust::tables::BlockRecord::new("DOOR");
let mut door = codec::tables::BlockRecord::new("DOOR");
door.handle = doc.allocate_handle();
let door_h = door.handle;
doc.block_records.add(door).expect("add door");
let mut inner = acadrust::entities::Line::new();
let mut inner = codec::entities::Line::new();
inner.common.layer = "WALLS".to_string();
inner.common.owner_handle = door_h;
doc.add_entity(acadrust::EntityType::Line(inner)).unwrap();
let ins = acadrust::entities::Insert::new(
doc.add_entity(codec::EntityType::Line(inner)).unwrap();
let ins = codec::entities::Insert::new(
"DOOR",
acadrust::types::Vector3::ZERO,
codec::types::Vector3::ZERO,
);
doc.add_entity(acadrust::EntityType::Insert(ins)).unwrap();
doc.add_entity(codec::EntityType::Insert(ins)).unwrap();
// Hatch (pattern names have no table — preserved verbatim).
let mut hatch = acadrust::entities::Hatch::new();
let mut hatch = codec::entities::Hatch::new();
hatch.pattern.name = "ANSI31".to_string();
doc.add_entity(acadrust::EntityType::Hatch(hatch)).unwrap();
doc.add_entity(codec::EntityType::Hatch(hatch)).unwrap();
// Dim-style reference via a tolerance entity.
doc.dim_styles
.add(acadrust::tables::DimStyle::new("ARCH"))
.add(codec::tables::DimStyle::new("ARCH"))
.expect("add dimstyle");
let mut tol = acadrust::entities::Tolerance::new();
let mut tol = codec::entities::Tolerance::new();
tol.dimension_style_name = "ARCH".to_string();
doc.add_entity(acadrust::EntityType::Tolerance(tol)).unwrap();
doc.add_entity(codec::EntityType::Tolerance(tol)).unwrap();
});
let (mut host, key) = xref_doc_with("PLAN", &xref_path);
bind_reference(&mut host, key, &dir, &dir).expect("bind");
@@ -2925,7 +2925,7 @@ mod tests {
let inserts: Vec<_> = host
.entities()
.filter_map(|e| match e {
acadrust::EntityType::Insert(i) => Some(i.block_name.clone()),
codec::EntityType::Insert(i) => Some(i.block_name.clone()),
_ => None,
})
.collect();
@@ -2941,7 +2941,7 @@ mod tests {
.entities()
.filter(|e| {
e.common().owner_handle == br.handle
&& matches!(e, acadrust::EntityType::Hatch(_))
&& matches!(e, codec::EntityType::Hatch(_))
})
.collect();
assert_eq!(hatches.len(), 1);
@@ -2949,7 +2949,7 @@ mod tests {
let tols: Vec<_> = host
.entities()
.filter_map(|e| match e {
acadrust::EntityType::Tolerance(t) => Some(t.dimension_style_name.clone()),
codec::EntityType::Tolerance(t) => Some(t.dimension_style_name.clone()),
_ => None,
})
.collect();
@@ -2962,22 +2962,22 @@ mod tests {
let dir = bind_dir("styles");
let xref_path = write_xref_file(&dir, "plan.dwg", |doc| {
doc.text_styles
.add(acadrust::tables::TextStyle::new("NOTE"))
.add(codec::tables::TextStyle::new("NOTE"))
.expect("add text style");
let mut text = acadrust::entities::Text::new();
let mut text = codec::entities::Text::new();
text.style = "NOTE".to_string();
doc.add_entity(acadrust::EntityType::Text(text)).unwrap();
doc.add_entity(codec::EntityType::Text(text)).unwrap();
// Two foreign handles that cannot be remapped (plotstyle_flags
// set as a real writer would — otherwise the round-trip
// drops the handle).
let mut line = acadrust::entities::Line::new();
let mut line = codec::entities::Line::new();
line.common.plotstyle_flags = 0b11;
line.common.plotstyle_handle = Some(doc.allocate_handle());
doc.add_entity(acadrust::EntityType::Line(line)).unwrap();
let mut mline = acadrust::entities::MLine::new();
doc.add_entity(codec::EntityType::Line(line)).unwrap();
let mut mline = codec::entities::MLine::new();
mline.style_name = "STD".to_string();
mline.style_handle = Some(doc.allocate_handle());
doc.add_entity(acadrust::EntityType::MLine(mline)).unwrap();
doc.add_entity(codec::EntityType::MLine(mline)).unwrap();
});
let (mut host, key) = xref_doc_with("PLAN", &xref_path);
let outcome = bind_reference(&mut host, key, &dir, &dir).expect("bind");
@@ -2986,7 +2986,7 @@ mod tests {
let styles: Vec<_> = host
.entities()
.filter_map(|e| match e {
acadrust::EntityType::Text(t) => Some(t.style.clone()),
codec::EntityType::Text(t) => Some(t.style.clone()),
_ => None,
})
.collect();
@@ -2996,7 +2996,7 @@ mod tests {
.entities()
.filter(|e| {
e.common().owner_handle == br.handle
&& matches!(e, acadrust::EntityType::Line(_))
&& matches!(e, codec::EntityType::Line(_))
})
.collect();
assert_eq!(lines.len(), 1);
@@ -3004,7 +3004,7 @@ mod tests {
let mlines: Vec<_> = host
.entities()
.filter_map(|e| match e {
acadrust::EntityType::MLine(m) => Some(m.style_handle),
codec::EntityType::MLine(m) => Some(m.style_handle),
_ => None,
})
.collect();
@@ -3014,7 +3014,7 @@ mod tests {
#[test]
fn bind_image_collect_copies_file() {
use acadrust::objects::{ImageDefinition, ObjectType};
use codec::objects::{ImageDefinition, ObjectType};
let src_dir = bind_dir("imgsrc");
let host_dir = bind_dir("imghost");
std::fs::write(src_dir.join("pixel.png"), b"fakepng").unwrap();
@@ -3023,14 +3023,14 @@ mod tests {
let mut def = ImageDefinition::with_dimensions("pixel.png", 8, 8);
def.handle = h;
doc.objects.insert(h, ObjectType::ImageDefinition(def));
let mut img = acadrust::entities::RasterImage::new(
let mut img = codec::entities::RasterImage::new(
"pixel.png",
acadrust::types::Vector3::ZERO,
codec::types::Vector3::ZERO,
8.0,
8.0,
);
img.definition_handle = Some(h);
doc.add_entity(acadrust::EntityType::RasterImage(img)).unwrap();
doc.add_entity(codec::EntityType::RasterImage(img)).unwrap();
});
let (mut host, key) = xref_doc_with("PLAN", &xref_path);
bind_reference(&mut host, key, &src_dir, &host_dir).expect("bind");
@@ -3052,21 +3052,21 @@ mod tests {
#[test]
fn bind_image_missing_errors_without_partial_state() {
use acadrust::objects::{ImageDefinition, ObjectType};
use codec::objects::{ImageDefinition, ObjectType};
let dir = bind_dir("imgmissing");
let xref_path = write_xref_file(&dir, "plan.dwg", |doc| {
let h = doc.allocate_handle();
let mut def = ImageDefinition::with_dimensions("ghost.png", 8, 8);
def.handle = h;
doc.objects.insert(h, ObjectType::ImageDefinition(def));
let mut img = acadrust::entities::RasterImage::new(
let mut img = codec::entities::RasterImage::new(
"ghost.png",
acadrust::types::Vector3::ZERO,
codec::types::Vector3::ZERO,
8.0,
8.0,
);
img.definition_handle = Some(h);
doc.add_entity(acadrust::EntityType::RasterImage(img)).unwrap();
doc.add_entity(codec::EntityType::RasterImage(img)).unwrap();
});
let (mut host, key) = xref_doc_with("PLAN", &xref_path);
let err = bind_reference(&mut host, key, &dir, &dir).unwrap_err();
@@ -3081,8 +3081,8 @@ mod tests {
#[test]
fn bind_pdf_errors() {
use acadrust::objects::{ObjectType, UnderlayDefinition};
let mut host = acadrust::CadDocument::new();
use codec::objects::{ObjectType, UnderlayDefinition};
let mut host = codec::CadDocument::new();
let h = host.allocate_handle();
let mut def = UnderlayDefinition::pdf("doc.pdf", "1");
def.handle = h;
+3 -3
View File
@@ -750,9 +750,9 @@ mod tests {
// every SPIKE1 cell survived the round trip.
#[cfg(not(target_arch = "wasm32"))]
fn roundtrip_probe(ext: &str) {
use acadrust::objects::{ImageDefinition, ObjectType, UnderlayDefinition};
use acadrust::tables::BlockRecord;
use acadrust::CadDocument;
use codec::objects::{ImageDefinition, ObjectType, UnderlayDefinition};
use codec::tables::BlockRecord;
use codec::CadDocument;
let dir = std::env::temp_dir().join(format!(
"ocs_xref_probe_{}_{}",