Merge pull request #1793 from ramox81/codex/block-definition-command-line

Block Definition: command-line forms, block record data and dialog defaults

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 00:14:21 +03:00
commit 838e397a6c
23 files changed
+1860 -435

No files matched your search

+33
View File
@@ -1975,6 +1975,39 @@ pub fn update_entity_from_annotation_style(
true
}
/// Whether the block definition `name` is annotative: its block record
/// carries `AcadAnnotative` (`AnnotativeData { 1 1 }`), or, in drawings an
/// earlier build wrote, its BLOCK entity does.
pub fn block_is_annotative(doc: &CadDocument, name: &str) -> bool {
let Some(br) = doc.block_records.get(name) else {
return false;
};
let flag = |record: &codec::xdata::ExtendedDataRecord| {
record
.values
.iter()
.filter_map(|value| match value {
codec::xdata::XDataValue::Integer16(value) => Some(*value),
_ => None,
})
.last()
.is_some_and(|value| value != 0)
};
if let Some(record) = doc
.object_xdata(br.handle)
.and_then(|xdata| xdata.get_record("AcadAnnotative"))
{
return flag(record);
}
doc.get_entity(br.block_entity_handle).is_some_and(|block| {
block
.common()
.extended_data
.get_record("AcadAnnotative")
.is_some_and(flag)
})
}
/// Whether an entity participates in annotation scaling.
pub fn is_annotative(doc: &CadDocument, entity: &EntityType) -> bool {
// Per-object annotation context (works regardless of style).
+161 -46
View File
@@ -993,7 +993,7 @@ impl Scene {
.get(name)
.map(|br| (br.handle, br.block_entity_handle));
let (br_handle, _block_handle) = if let Some((existing_br_h, existing_blk_h)) = existing {
let (br_handle, block_handle) = if let Some((existing_br_h, existing_blk_h)) = existing {
if !options.redefine {
return Err(format!("Block \"{name}\" already exists."));
}
@@ -1015,7 +1015,7 @@ impl Scene {
br.units = options.unit;
br.description = options.description.clone();
br.explodable = options.allow_exploding;
br.scale_uniformly = options.scale_uniformly;
br.scale_uniformly = options.scale_uniformly || options.annotative;
}
// Update existing Block entity fields
if let Some(EntityType::Block(b)) = self.document.get_entity_mut(existing_blk_h) {
@@ -1026,26 +1026,6 @@ impl Scene {
// base point here would subtract the picked base twice.
b.base_point = codec::types::Vector3::ZERO;
}
if options.annotative {
let annotative_vals = vec![codec::xdata::XDataValue::Integer16(1)];
view::dispatch::set_entity_xdata(
&mut self.document,
existing_blk_h,
"AcadAnnotative",
Some(annotative_vals),
);
}
if !options.hyperlink_url.is_empty() {
let link_vals = vec![codec::xdata::XDataValue::String(
options.hyperlink_url.clone(),
)];
view::dispatch::set_entity_xdata(
&mut self.document,
existing_blk_h,
"PE_URL",
Some(link_vals),
);
}
(existing_br_h, existing_blk_h)
} else {
let next = self.document.next_handle();
@@ -1060,7 +1040,7 @@ impl Scene {
block_record.units = options.unit;
block_record.description = options.description.clone();
block_record.explodable = options.allow_exploding;
block_record.scale_uniformly = options.scale_uniformly;
block_record.scale_uniformly = options.scale_uniformly || options.annotative;
self.document
.block_records
.add(block_record)
@@ -1080,36 +1060,19 @@ impl Scene {
self.document
.add_entity(EntityType::BlockEnd(block_end))
.map_err(|e| e.to_string())?;
if options.annotative {
let annotative_vals = vec![codec::xdata::XDataValue::Integer16(1)];
view::dispatch::set_entity_xdata(
&mut self.document,
block_handle,
"AcadAnnotative",
Some(annotative_vals),
);
}
if !options.hyperlink_url.is_empty() {
let link_vals = vec![codec::xdata::XDataValue::String(
options.hyperlink_url.clone(),
)];
view::dispatch::set_entity_xdata(
&mut self.document,
block_handle,
"PE_URL",
Some(link_vals),
);
}
(br_handle, block_handle)
};
self.write_block_definition_xdata(br_handle, block_handle, &options);
let source_entities: Vec<_> = options
.handles
.iter()
.filter_map(|&h| self.document.get_entity(h).cloned().map(|e| (h, e)))
.collect();
if source_entities.is_empty() {
// No objects at all defines an empty block (the reference's
// "Continue" / an empty selection at -BLOCK); objects that all vanished
// are an error.
if source_entities.is_empty() && !options.handles.is_empty() {
return Err("No valid entities selected for block creation.".into());
}
@@ -1128,17 +1091,169 @@ impl Scene {
}
let mut insert_handle = Handle::NULL;
if options.mode == crate::ui::window::block_definition::BlockObjectMode::Convert {
// An empty definition has nothing to convert into a reference.
if options.mode == crate::ui::window::block_definition::BlockObjectMode::Convert
&& !options.handles.is_empty()
{
let mut insert = DxfInsert::new(name, codec::types::Vector3::ZERO);
codec::Entity::apply_transform(&mut insert, &options.block_to_world);
insert_handle = self.add_entity(EntityType::Insert(insert));
}
// The definition's thumbnail (BLOCK_RECORD 310), as the format keeps it.
let thumbnail = self.block_thumbnail(name);
if let Some(br) = self.document.block_records.get_mut(name) {
br.preview_data = thumbnail;
}
// Advance the block epoch so all consumers and viewport renderers re-tessellate
self.bump_geometry();
Ok(insert_handle)
}
/// A 40 × 40, 32-bit bottom-up DIB of the block's geometry: white strokes
/// on the dark preview background, fitted with a one-pixel margin. Empty
/// for a block without drawable geometry.
pub fn block_thumbnail(&self, name: &str) -> Vec<u8> {
const SIZE: usize = 40;
let wires = self.block_preview_wires(name);
let mut lo = [f64::INFINITY; 2];
let mut hi = [f64::NEG_INFINITY; 2];
for wire in &wires {
for p in &wire.points {
if p[0].is_finite() && p[1].is_finite() {
lo = [lo[0].min(p[0] as f64), lo[1].min(p[1] as f64)];
hi = [hi[0].max(p[0] as f64), hi[1].max(p[1] as f64)];
}
}
}
if !lo[0].is_finite() {
return Vec::new();
}
let span = (hi[0] - lo[0]).max(hi[1] - lo[1]).max(1e-9);
let scale = (SIZE as f64 - 3.0) / span;
let offset = [
(SIZE as f64 - (hi[0] - lo[0]) * scale) / 2.0,
(SIZE as f64 - (hi[1] - lo[1]) * scale) / 2.0,
];
let to_px = |p: &[f32; 3]| {
(
((p[0] as f64 - lo[0]) * scale + offset[0]).floor() as i64,
((p[1] as f64 - lo[1]) * scale + offset[1]).floor() as i64,
)
};
// Bottom-up rows of BGRA pixels.
let mut pixels = vec![[0x30u8, 0x28, 0x21, 0xFF]; SIZE * SIZE];
let mut plot = |x: i64, y: i64| {
if (0..SIZE as i64).contains(&x) && (0..SIZE as i64).contains(&y) {
pixels[y as usize * SIZE + x as usize] = [0xFF; 4];
}
};
for wire in &wires {
let mut prev: Option<(i64, i64)> = None;
for p in &wire.points {
if !(p[0].is_finite() && p[1].is_finite()) {
prev = None;
continue;
}
let (x1, y1) = to_px(p);
let (mut x0, mut y0) = prev.unwrap_or((x1, y1));
let (dx, dy) = ((x1 - x0).abs(), -(y1 - y0).abs());
let (sx, sy) = (if x0 < x1 { 1 } else { -1 }, if y0 < y1 { 1 } else { -1 });
let mut err = dx + dy;
loop {
plot(x0, y0);
if x0 == x1 && y0 == y1 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x0 += sx;
}
if e2 <= dx {
err += dx;
y0 += sy;
}
}
prev = Some((x1, y1));
}
}
let mut dib = Vec::with_capacity(40 + SIZE * SIZE * 4);
for value in [40u32, SIZE as u32, SIZE as u32] {
dib.extend_from_slice(&value.to_le_bytes());
}
dib.extend_from_slice(&1u16.to_le_bytes());
dib.extend_from_slice(&32u16.to_le_bytes());
for value in [0u32, (SIZE * SIZE * 4) as u32, 1, 1, 0, 0] {
dib.extend_from_slice(&value.to_le_bytes());
}
for pixel in pixels {
dib.extend_from_slice(&pixel);
}
dib
}
/// The definition-level xdata of a block, kept on its block record the
/// way the format stores it: `AcadAnnotative` (`AnnotativeData { 1 1 }`)
/// for an annotative block, `AcadAnnoPO` when its orientation matches the
/// layout, and the `PE_URL` hyperlink. Markers an older definition kept
/// on its BLOCK entity are dropped.
pub fn write_block_definition_xdata(
&mut self,
br_handle: Handle,
block_handle: Handle,
options: &CreateBlockOptions,
) {
use codec::xdata::{ExtendedDataRecord, XDataValue as V};
for app in ["AcadAnnotative", "AcadAnnoPO", "PE_URL"] {
view::dispatch::set_entity_xdata(&mut self.document, block_handle, app, None);
self.document.remove_object_xdata_record(br_handle, app);
}
let mut records = Vec::new();
if options.annotative {
let mut anno = ExtendedDataRecord::new("AcadAnnotative");
for value in [
V::String("AnnotativeData".into()),
V::ControlString("{".into()),
V::Integer16(1),
V::Integer16(1),
V::ControlString("}".into()),
] {
anno.add_value(value);
}
records.push(anno);
if options.match_orientation {
let mut po = ExtendedDataRecord::new("AcadAnnoPO");
po.add_value(V::Integer16(1));
records.push(po);
}
}
let url = options.hyperlink_url.trim();
if !url.is_empty() {
let mut link = ExtendedDataRecord::new("PE_URL");
link.add_value(V::String(url.to_string()));
link.add_value(V::ControlString("{".into()));
let description = options.hyperlink_desc.trim();
if !description.is_empty() {
link.add_value(V::String(description.to_string()));
}
link.add_value(V::ControlString("{".into()));
link.add_value(V::Integer32(0));
link.add_value(V::ControlString("}".into()));
link.add_value(V::ControlString("}".into()));
records.push(link);
}
for record in records {
if !self.document.app_ids.contains(&record.application_name) {
let mut app = codec::tables::AppId::new(&record.application_name);
app.handle = self.document.allocate_handle();
let _ = self.document.app_ids.add(app);
}
self.document.set_object_xdata_record(br_handle, record);
}
}
/// Define a new block named `name` from `entities` (owned, not yet in the
/// document), with `base` as its insertion origin. Unlike
/// [`create_block_from_entities`] this does NOT place an insert — the