feat: WBLOCK — write block or selected entities to external file
- src/modules/insert/wblock.rs: extract_block_to_doc() copies a named
block's entities into a fresh CadDocument; extract_entities_to_doc()
does the same for a selection set; layer definitions are carried over
- WBLOCK <name> writes the named block; WBLOCK * writes selected entities
- WblockSave / WblockSaveResult messages drive the async save-dialog flow
- ROADMAP.md: 1.10 WBLOCK marked ✅
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
64837e9aed
commit
b15be3af3d
6 changed files with 176 additions and 2 deletions
|
|
@ -19,7 +19,7 @@ Durum simgeleri: ✅ Tamamlandı · 🔧 Kısmen yapıldı · ⬜ Yapılmadı
|
||||||
| 1.7 | PDF dışa aktarma (CTB/STB plot style) | ✅ |
|
| 1.7 | PDF dışa aktarma (CTB/STB plot style) | ✅ |
|
||||||
| 1.8 | Fiziksel yazıcıya yazdırma | ⬜ |
|
| 1.8 | Fiziksel yazıcıya yazdırma | ⬜ |
|
||||||
| 1.9 | XREF (dış referans) yönetimi | ✅ Auto-resolve on open, XATTACH/XREF/XRELOAD commands |
|
| 1.9 | XREF (dış referans) yönetimi | ✅ Auto-resolve on open, XATTACH/XREF/XRELOAD commands |
|
||||||
| 1.10 | WBLOCK — bloğu dış dosyaya yazma | ⬜ |
|
| 1.10 | WBLOCK — bloğu dış dosyaya yazma | ✅ Block name or selected entities → DWG/DXF |
|
||||||
| 1.11 | Serde entegrasyonu (JSON/alternatif I/O) | ⬜ |
|
| 1.11 | Serde entegrasyonu (JSON/alternatif I/O) | ⬜ |
|
||||||
| 1.12 | Bozuk DWG kurtarma (failsafe parse) | ⬜ |
|
| 1.12 | Bozuk DWG kurtarma (failsafe parse) | ⬜ |
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ Underlay (PDF/DWF/DGN)
|
||||||
| 3D Cylinder primitive | ✅ |
|
| 3D Cylinder primitive | ✅ |
|
||||||
| OBJ dosyası içe aktarma | ✅ |
|
| OBJ dosyası içe aktarma | ✅ |
|
||||||
| REFEDIT (block yerinde düzenleme) | ⬜ |
|
| REFEDIT (block yerinde düzenleme) | ⬜ |
|
||||||
| WBLOCK (bloğu dış dosyaya yaz) | ⬜ |
|
| WBLOCK (bloğu dış dosyaya yaz) | ✅ |
|
||||||
| Attributeli INSERT akışı (ATTREQ) | ⬜ |
|
| Attributeli INSERT akışı (ATTREQ) | ⬜ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,23 @@ impl H7CAD {
|
||||||
return Task::done(Message::XAttachPick);
|
return Task::done(Message::XAttachPick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd if cmd == "WBLOCK" || cmd == "WB" || cmd.starts_with("WBLOCK ") => {
|
||||||
|
let arg = cmd.splitn(2, ' ').nth(1).unwrap_or("").trim();
|
||||||
|
if arg.is_empty() {
|
||||||
|
// No argument: use selected entities (*) if any, else ask.
|
||||||
|
let sel: Vec<_> = self.tabs[i].scene.selected.iter().copied().collect();
|
||||||
|
if sel.is_empty() {
|
||||||
|
self.command_line.push_error(
|
||||||
|
"WBLOCK Select entities first, or: WBLOCK <block name> or WBLOCK *",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Task::done(Message::WblockSave("*".to_string()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Task::done(Message::WblockSave(arg.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"XREF" | "XR" => {
|
"XREF" | "XR" => {
|
||||||
// List all xref blocks in the current drawing.
|
// List all xref blocks in the current drawing.
|
||||||
let xrefs: Vec<String> = self.tabs[i]
|
let xrefs: Vec<String> = self.tabs[i]
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,11 @@ pub enum Message {
|
||||||
XAttachPick,
|
XAttachPick,
|
||||||
/// Result of the XATTACH file picker.
|
/// Result of the XATTACH file picker.
|
||||||
XAttachPickResult(Result<std::path::PathBuf, String>),
|
XAttachPickResult(Result<std::path::PathBuf, String>),
|
||||||
|
// ── WBLOCK ────────────────────────────────────────────────────────────
|
||||||
|
/// Trigger the WBLOCK save dialog for `block_name` (or `*` = selection).
|
||||||
|
WblockSave(String),
|
||||||
|
/// Result of the WBLOCK save path dialog.
|
||||||
|
WblockSaveResult(String, Option<std::path::PathBuf>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl H7CAD {
|
impl H7CAD {
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,50 @@ impl H7CAD {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::WblockSave(block_name) => {
|
||||||
|
let name = block_name.clone();
|
||||||
|
Task::perform(
|
||||||
|
async move {
|
||||||
|
let path = crate::io::pick_save_path().await;
|
||||||
|
(name, path)
|
||||||
|
},
|
||||||
|
|(name, path)| Message::WblockSaveResult(name, path),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::WblockSaveResult(block_name, Some(path)) => {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let result = if block_name == "*" {
|
||||||
|
let handles: Vec<_> = self.tabs[i].scene.selected.iter().copied().collect();
|
||||||
|
crate::modules::insert::wblock::extract_entities_to_doc(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
&handles,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
crate::modules::insert::wblock::extract_block_to_doc(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
&block_name,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(doc) => match crate::io::save(&doc, &path) {
|
||||||
|
Ok(()) => {
|
||||||
|
let fname = path
|
||||||
|
.file_name()
|
||||||
|
.map(|n| n.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|| path.to_string_lossy().into_owned());
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("WBLOCK Saved \"{block_name}\" → \"{fname}\""));
|
||||||
|
}
|
||||||
|
Err(e) => self.command_line.push_error(&format!("WBLOCK save failed: {e}")),
|
||||||
|
},
|
||||||
|
Err(e) => self.command_line.push_error(&format!("WBLOCK: {e}")),
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
Message::WblockSaveResult(_, None) => Task::none(),
|
||||||
|
|
||||||
Message::SaveFile => {
|
Message::SaveFile => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
if let Some(path) = &self.tabs[i].current_path {
|
if let Some(path) = &self.tabs[i].current_path {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ mod cylinder;
|
||||||
pub(crate) mod insert_block;
|
pub(crate) mod insert_block;
|
||||||
mod open_obj;
|
mod open_obj;
|
||||||
mod sphere;
|
mod sphere;
|
||||||
|
pub(crate) mod wblock;
|
||||||
pub(crate) mod xattach;
|
pub(crate) mod xattach;
|
||||||
|
|
||||||
use crate::modules::{CadModule, RibbonGroup};
|
use crate::modules::{CadModule, RibbonGroup};
|
||||||
|
|
@ -40,6 +41,7 @@ impl CadModule for InsertModule {
|
||||||
tools: vec![
|
tools: vec![
|
||||||
create_block::tool().into(),
|
create_block::tool().into(),
|
||||||
insert_block::tool().into(),
|
insert_block::tool().into(),
|
||||||
|
wblock::tool().into(),
|
||||||
xattach::tool().into(),
|
xattach::tool().into(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
106
src/modules/insert/wblock.rs
Normal file
106
src/modules/insert/wblock.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
// WBLOCK — write a block (or selected entities) to an external DWG/DXF file.
|
||||||
|
//
|
||||||
|
// Two modes:
|
||||||
|
// block name → copies the named block definition to a new document
|
||||||
|
// * → copies currently selected model-space entities
|
||||||
|
|
||||||
|
use acadrust::{CadDocument, EntityType};
|
||||||
|
|
||||||
|
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||||
|
|
||||||
|
pub fn tool() -> ToolDef {
|
||||||
|
ToolDef {
|
||||||
|
id: "WBLOCK",
|
||||||
|
label: "Write Block",
|
||||||
|
icon: IconKind::Svg(include_bytes!("../../../assets/icons/blocks/insert.svg")),
|
||||||
|
event: ModuleEvent::Command("WBLOCK".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a standalone `CadDocument` containing the named block's entities
|
||||||
|
/// extracted into model space.
|
||||||
|
///
|
||||||
|
/// Returns `Err` if the block is not found or has no entities.
|
||||||
|
pub fn extract_block_to_doc(
|
||||||
|
src: &CadDocument,
|
||||||
|
block_name: &str,
|
||||||
|
) -> Result<CadDocument, String> {
|
||||||
|
let br = src
|
||||||
|
.block_records
|
||||||
|
.get(block_name)
|
||||||
|
.ok_or_else(|| format!("Block \"{block_name}\" not found."))?;
|
||||||
|
|
||||||
|
let handles = br.entity_handles.clone();
|
||||||
|
if handles.is_empty() {
|
||||||
|
return Err(format!("Block \"{block_name}\" has no entities."));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = CadDocument::new();
|
||||||
|
// Copy layers referenced by the block entities.
|
||||||
|
for h in &handles {
|
||||||
|
if let Some(e) = src.get_entity(*h) {
|
||||||
|
let layer = e.common().layer.clone();
|
||||||
|
if !layer.is_empty() && !layer.eq("0") && out.layers.get(&layer).is_none() {
|
||||||
|
if let Some(src_layer) = src.layers.get(&layer) {
|
||||||
|
let _ = out.layers.add(src_layer.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for h in handles {
|
||||||
|
if let Some(entity) = src.get_entity(h) {
|
||||||
|
if matches!(entity, EntityType::Block(_) | EntityType::BlockEnd(_)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut clone = entity.clone();
|
||||||
|
clone.common_mut().handle = acadrust::types::Handle::NULL;
|
||||||
|
clone.common_mut().owner_handle = acadrust::types::Handle::NULL;
|
||||||
|
let _ = out.add_entity(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.entities().count() == 0 {
|
||||||
|
return Err(format!("Block \"{block_name}\" produced no exportable entities."));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a standalone `CadDocument` from an explicit list of entity handles
|
||||||
|
/// (the "selected entities" mode, `*`).
|
||||||
|
pub fn extract_entities_to_doc(
|
||||||
|
src: &CadDocument,
|
||||||
|
handles: &[acadrust::Handle],
|
||||||
|
) -> Result<CadDocument, String> {
|
||||||
|
if handles.is_empty() {
|
||||||
|
return Err("No entities selected for WBLOCK.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = CadDocument::new();
|
||||||
|
|
||||||
|
for &h in handles {
|
||||||
|
if let Some(entity) = src.get_entity(h) {
|
||||||
|
if matches!(entity, EntityType::Block(_) | EntityType::BlockEnd(_)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Copy layer definition.
|
||||||
|
let layer = entity.common().layer.clone();
|
||||||
|
if !layer.is_empty() && !layer.eq("0") && out.layers.get(&layer).is_none() {
|
||||||
|
if let Some(src_layer) = src.layers.get(&layer) {
|
||||||
|
let _ = out.layers.add(src_layer.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut clone = entity.clone();
|
||||||
|
clone.common_mut().handle = acadrust::types::Handle::NULL;
|
||||||
|
clone.common_mut().owner_handle = acadrust::types::Handle::NULL;
|
||||||
|
let _ = out.add_entity(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.entities().count() == 0 {
|
||||||
|
return Err("None of the selected entities could be exported.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue