mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
cad: export DXF through craft-dxf's drawing writer (#7847)
* cad: export DXF through craft-dxf's drawing writer Delete the local ASCII writer and map the document through craft_dxf::drawing. Public vectorcraft_cad::export, DxfOptions and DxfVersion stay so vectorcraft#3068 can rebase without a writer API change. Pin craft-dxf at the craft-libs#1588 branch tip; update the rev after that PR merges. Golden files lock shapes_doc() bytes for R12 and 2018 against the old writer. Part of storytold/craft-libs#791 and #778. * cad: pin craft-dxf to the #1588 tip with Dylan's writer fixes Point craft-dxf (and craft-codepage from the same rev) at 0b6244f2b9971deaeb6a79d932c818ff94479757, which includes the Windows golden line-ending fix, BYBLOCK and image-dictionary key changes, and the compile fix for the dictionary-key test. Update the pin again to the merge commit after #1588 lands. * cad: pin shared DXF writer to merged craft-libs #1588 --------- Co-authored-by: FredApps <FredApps@users.noreply.github.com>
This commit is contained in:
Generated
+2
-2
@@ -987,7 +987,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-codepage"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=6d620e971e820b517b11a99cad040ebac670e1e2#6d620e971e820b517b11a99cad040ebac670e1e2"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=8bd1412eaa7c0b58f3f9c401495d35fe12d6d85e#8bd1412eaa7c0b58f3f9c401495d35fe12d6d85e"
|
||||
|
||||
[[package]]
|
||||
name = "craft-docpath"
|
||||
@@ -997,7 +997,7 @@ source = "git+https://github.com/storytold/craft-libs?rev=9be9a63782298ab59cd9b7
|
||||
[[package]]
|
||||
name = "craft-dxf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=6d620e971e820b517b11a99cad040ebac670e1e2#6d620e971e820b517b11a99cad040ebac670e1e2"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=8bd1412eaa7c0b58f3f9c401495d35fe12d6d85e#8bd1412eaa7c0b58f3f9c401495d35fe12d6d85e"
|
||||
dependencies = [
|
||||
"craft-codepage",
|
||||
"encoding_rs",
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ craft-svg = { git = "https://github.com/storytold/craft-libs", rev = "6d620e971e
|
||||
# EPS container, previews, ASCII85 and DSC comments (craft-libs#778).
|
||||
craft-eps = { git = "https://github.com/storytold/craft-libs", rev = "6d620e971e820b517b11a99cad040ebac670e1e2" }
|
||||
# DXF tags, reader and writer (craft-libs#778, #926, #927).
|
||||
craft-dxf = { git = "https://github.com/storytold/craft-libs", rev = "6d620e971e820b517b11a99cad040ebac670e1e2" }
|
||||
craft-dxf = { git = "https://github.com/storytold/craft-libs", rev = "8bd1412eaa7c0b58f3f9c401495d35fe12d6d85e" }
|
||||
# WMF, EMF and EMF+ reader (craft-libs#778).
|
||||
craft-metafile = { git = "https://github.com/storytold/craft-libs", rev = "6d620e971e820b517b11a99cad040ebac670e1e2" }
|
||||
craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "e2dc681a3" }
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "vectorcraft-cad"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "VectorCraft CAD interchange: a hand-written ASCII DXF writer (R12 to 2018) and an HP-GL/2 plot file reader and writer"
|
||||
description = "VectorCraft CAD interchange: DXF export through craft-dxf and an HP-GL/2 plot file reader and writer"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
+19
-4
@@ -1,4 +1,4 @@
|
||||
//! CAD interchange: a hand-written ASCII DXF writer (versions R12 to 2018) and a reader on
|
||||
//! CAD interchange: a DXF writer (versions R12 to 2018) on `craft_dxf::drawing` and a reader on
|
||||
//! `craft_dxf::model`, and an HP-GL/2 plot file reader and writer.
|
||||
//!
|
||||
//! [`export`] writes the visible art of a document into one DXF drawing: each layer becomes a DXF
|
||||
@@ -15,7 +15,7 @@
|
||||
//! [`hpgl::import`] reads an HP-GL plotter file, and [`hpgl::export`] writes one: see [`hpgl`].
|
||||
//!
|
||||
//! - `aci`: the 256-colour index palette and the nearest index of a colour.
|
||||
//! - `writer`: group codes, handles and the file's sections, tables and objects.
|
||||
//! - The ASCII file around those entities is written by `craft_dxf::drawing`.
|
||||
//! - `scene`: the document walk that turns objects into entities.
|
||||
//! - `import`: the reader, entities to art, blocks to symbols.
|
||||
//! - `hpgl`: the HP-GL reader, the pen's moves to paths, and the writer, paths to pen moves.
|
||||
@@ -24,7 +24,6 @@ mod aci;
|
||||
pub mod hpgl;
|
||||
pub mod import;
|
||||
mod scene;
|
||||
mod writer;
|
||||
|
||||
use vectorcraft_doc::{Document, Unit};
|
||||
use vectorcraft_geom::Rect;
|
||||
@@ -136,11 +135,27 @@ impl DxfVersion {
|
||||
self >= Self::R2004
|
||||
}
|
||||
/// Text is UTF-8 (earlier versions escape what isn't ASCII as `\U+XXXX`).
|
||||
fn utf8(self) -> bool {
|
||||
pub fn utf8(self) -> bool {
|
||||
self >= Self::R2007
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DxfVersion> for craft_dxf::drawing::Format {
|
||||
fn from(v: DxfVersion) -> Self {
|
||||
match v {
|
||||
DxfVersion::R12 => Self::R12,
|
||||
DxfVersion::R13 => Self::R13,
|
||||
DxfVersion::R14 => Self::R14,
|
||||
DxfVersion::R2000 => Self::R2000,
|
||||
DxfVersion::R2004 => Self::R2004,
|
||||
DxfVersion::R2007 => Self::R2007,
|
||||
DxfVersion::R2010 => Self::R2010,
|
||||
DxfVersion::R2013 => Self::R2013,
|
||||
DxfVersion::R2018 => Self::R2018,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How many colours entities may use: indexed colours of the 256-colour palette, or true colour
|
||||
/// (with the nearest index for apps that read only the index).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
|
||||
@@ -14,8 +14,8 @@ use vectorcraft_effects::stroke;
|
||||
use vectorcraft_geom::{Affine, BezPath, FillRule, PathData, Point, Rect, Vec2};
|
||||
|
||||
use crate::aci::nearest_aci;
|
||||
use crate::writer::{Codes, Drawing, EntityStyle, Handle, ImageDef, Layer, LineType, fixed};
|
||||
use crate::{CAP_HEIGHT, ColorDepth, DxfImage, DxfOptions, DxfOutput, MAX_NEST, Preserve, RasterFormat};
|
||||
use craft_dxf::drawing::{Codes, Drawing, EntityStyle, Handle, ImageDef, Layer, LineType, STANDARD_STYLE, fixed};
|
||||
|
||||
/// Curves are flattened to within this distance (points) where DXF needs straight segments
|
||||
/// (hatch boundaries, R12 polylines).
|
||||
@@ -543,7 +543,7 @@ impl<'a> Scene<'a> {
|
||||
o,
|
||||
to_dxf: Affine::new([k, 0.0, 0.0, -k, -r.x0 * k, r.y1 * k]),
|
||||
k,
|
||||
e: Codes::new(o.version),
|
||||
e: Codes::new(o.version.into()),
|
||||
next: fixed::FIRST_FREE,
|
||||
layers: vec![],
|
||||
layer_names: Names::with(&["0", "Defpoints"]),
|
||||
@@ -551,7 +551,7 @@ impl<'a> Scene<'a> {
|
||||
flags: LayerFlags { off: false, locked: false, plot: true },
|
||||
linetypes: vec![],
|
||||
styles: vec![],
|
||||
style_names: Names::with(&[crate::writer::STANDARD_STYLE]),
|
||||
style_names: Names::with(&[STANDARD_STYLE]),
|
||||
images: vec![],
|
||||
files: vec![],
|
||||
by_key: HashMap::new(),
|
||||
@@ -579,7 +579,7 @@ impl<'a> Scene<'a> {
|
||||
let one = (self.o.scale - 1.0).abs() < 1e-12;
|
||||
let insunits = if one { crate::insunits_code(self.o.unit).unwrap_or(0) } else { 0 };
|
||||
let drawing = Drawing {
|
||||
version: self.o.version,
|
||||
version: self.o.version.into(),
|
||||
layers: self.layers,
|
||||
linetypes: self.linetypes,
|
||||
styles: self.styles,
|
||||
|
||||
@@ -1451,3 +1451,23 @@ fn symbols_fanning_out_into_empty_symbols_stop_at_the_symbol_budget() {
|
||||
assert!(entities(&out).is_empty());
|
||||
assert!(out.warnings.iter().any(|w| w.contains("symbols placed inside symbols")), "{:?}", out.warnings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_bytes_match_shared_writer_goldens() {
|
||||
use std::path::PathBuf;
|
||||
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data");
|
||||
let cases: [(&str, DxfVersion, Document); 2] =
|
||||
[("shapes_r12.dxf", DxfVersion::R12, shapes_doc()), ("shapes_r2018.dxf", DxfVersion::R2018, shapes_doc())];
|
||||
for (name, version, doc) in cases {
|
||||
let out = export(&doc, &DxfOptions { version, ..opts(&doc) }).unwrap();
|
||||
let got = std::str::from_utf8(&out.bytes).expect("UTF-8 DXF");
|
||||
let path = dir.join(name);
|
||||
if std::env::var_os("WRITE_DXF_GOLDENS").is_some() {
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(&path, got).unwrap();
|
||||
continue;
|
||||
}
|
||||
let expected = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
|
||||
assert_eq!(got, expected, "DXF bytes changed versus {}", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,674 +0,0 @@
|
||||
//! The ASCII DXF file: group codes, handles, and the sections, tables and objects around the
|
||||
//! entities. DXF R13 and later give every object a handle and subclass markers; R2000 and later
|
||||
//! add layouts and plot styles, which CAD apps expect to find.
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use crate::DxfVersion;
|
||||
|
||||
/// An object handle (written in hex).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct Handle(pub u32);
|
||||
|
||||
/// The handles of the fixed objects every drawing has. Entities and table entries count up from
|
||||
/// [`FIRST_FREE`].
|
||||
pub(crate) mod fixed {
|
||||
use super::Handle;
|
||||
pub const LAYER_TABLE: Handle = Handle(0x1);
|
||||
pub const LTYPE_TABLE: Handle = Handle(0x2);
|
||||
pub const APPID_TABLE: Handle = Handle(0x3);
|
||||
pub const DIMSTYLE_TABLE: Handle = Handle(0x4);
|
||||
pub const STYLE_TABLE: Handle = Handle(0x5);
|
||||
pub const UCS_TABLE: Handle = Handle(0x6);
|
||||
pub const VIEW_TABLE: Handle = Handle(0x7);
|
||||
pub const VPORT_TABLE: Handle = Handle(0x8);
|
||||
pub const BLOCK_RECORD_TABLE: Handle = Handle(0x9);
|
||||
pub const ROOT: Handle = Handle(0xA);
|
||||
pub const GROUPS: Handle = Handle(0xB);
|
||||
pub const LAYOUTS: Handle = Handle(0xC);
|
||||
pub const MODEL_RECORD: Handle = Handle(0xD);
|
||||
pub const PAPER_RECORD: Handle = Handle(0xE);
|
||||
pub const MODEL_BLOCK: Handle = Handle(0xF);
|
||||
pub const MODEL_END: Handle = Handle(0x10);
|
||||
pub const PAPER_BLOCK: Handle = Handle(0x11);
|
||||
pub const PAPER_END: Handle = Handle(0x12);
|
||||
pub const MODEL_LAYOUT: Handle = Handle(0x13);
|
||||
pub const PAPER_LAYOUT: Handle = Handle(0x14);
|
||||
pub const ACTIVE_VPORT: Handle = Handle(0x15);
|
||||
pub const ACAD_APPID: Handle = Handle(0x16);
|
||||
pub const STANDARD_DIMSTYLE: Handle = Handle(0x17);
|
||||
pub const IMAGE_DICT: Handle = Handle(0x18);
|
||||
pub const IMAGE_VARS: Handle = Handle(0x19);
|
||||
pub const PLOT_STYLES: Handle = Handle(0x1A);
|
||||
pub const PLOT_NORMAL: Handle = Handle(0x1B);
|
||||
pub const FIRST_FREE: u32 = 0x30;
|
||||
}
|
||||
|
||||
/// A number as DXF writes it: up to 6 decimals, no trailing zeros, 0 for anything not finite.
|
||||
pub(crate) fn num(v: f64) -> String {
|
||||
if !v.is_finite() {
|
||||
return "0".into();
|
||||
}
|
||||
let s = format!("{v:.6}");
|
||||
let s = s.trim_end_matches('0').trim_end_matches('.');
|
||||
if s == "-0" || s.is_empty() { "0".into() } else { s.into() }
|
||||
}
|
||||
|
||||
/// Text as a DXF string value: one line, `^` escaped, and before DXF 2007 (which reads UTF-8)
|
||||
/// every character outside ASCII as `\U+XXXX`.
|
||||
pub(crate) fn text(s: &str, utf8: bool) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\r' | '\n' | '\t' => out.push(' '),
|
||||
c if c.is_control() => {}
|
||||
'^' => out.push_str("^ "),
|
||||
c if !utf8 && !c.is_ascii() => match u16::try_from(u32::from(c)) {
|
||||
Ok(u) => _ = write!(out, "\\U+{u:04X}"),
|
||||
Err(_) => out.push('?'),
|
||||
},
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Group-code/value pairs in the order they are written.
|
||||
pub(crate) struct Codes {
|
||||
pub v: DxfVersion,
|
||||
out: String,
|
||||
}
|
||||
|
||||
impl Codes {
|
||||
pub fn new(v: DxfVersion) -> Self {
|
||||
Self { v, out: String::new() }
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.out
|
||||
}
|
||||
|
||||
pub fn append(&mut self, other: &str) {
|
||||
self.out.push_str(other);
|
||||
}
|
||||
|
||||
/// A value written as it is (names and keywords the writer itself chose).
|
||||
pub fn raw(&mut self, code: u16, value: &str) {
|
||||
_ = writeln!(self.out, "{code:>3}\n{value}");
|
||||
}
|
||||
|
||||
/// Text from the document, encoded for the version (see [`text`]).
|
||||
pub fn str(&mut self, code: u16, value: &str) {
|
||||
let t = text(value, self.v.utf8());
|
||||
self.raw(code, &t);
|
||||
}
|
||||
|
||||
pub fn int(&mut self, code: u16, value: i64) {
|
||||
_ = writeln!(self.out, "{code:>3}\n{value}");
|
||||
}
|
||||
|
||||
pub fn num(&mut self, code: u16, value: f64) {
|
||||
_ = writeln!(self.out, "{code:>3}\n{}", num(value));
|
||||
}
|
||||
|
||||
pub fn handle(&mut self, code: u16, h: Handle) {
|
||||
_ = writeln!(self.out, "{code:>3}\n{:X}", h.0);
|
||||
}
|
||||
|
||||
/// A 2D point at `code` (x), `code + 10` (y).
|
||||
pub fn xy(&mut self, code: u16, x: f64, y: f64) {
|
||||
self.num(code, x);
|
||||
self.num(code + 10, y);
|
||||
}
|
||||
|
||||
/// A 3D point at `code`, `code + 10`, `code + 20` (z = 0).
|
||||
pub fn xyz(&mut self, code: u16, x: f64, y: f64) {
|
||||
self.xy(code, x, y);
|
||||
self.num(code + 20, 0.0);
|
||||
}
|
||||
|
||||
/// The extrusion direction of planar entities: the z axis.
|
||||
pub fn normal(&mut self) {
|
||||
self.num(210, 0.0);
|
||||
self.num(220, 0.0);
|
||||
self.num(230, 1.0);
|
||||
}
|
||||
|
||||
/// The start of an object: its type, and from R13 its handle, owner and first subclass.
|
||||
fn object(&mut self, kind: &str, h: Handle, owner: Handle, subclass: &str) {
|
||||
self.raw(0, kind);
|
||||
if self.v.handles() {
|
||||
self.handle(if kind == "DIMSTYLE" { 105 } else { 5 }, h);
|
||||
self.handle(330, owner);
|
||||
self.raw(100, subclass);
|
||||
}
|
||||
}
|
||||
|
||||
/// A table entry: the shared record subclass, then the table's own (from R13).
|
||||
fn record(&mut self, kind: &str, h: Handle, table: Handle, subclass: &str) {
|
||||
self.object(kind, h, table, "AcDbSymbolTableRecord");
|
||||
if self.v.handles() {
|
||||
self.raw(100, subclass);
|
||||
}
|
||||
}
|
||||
|
||||
fn section(&mut self, name: &str) {
|
||||
self.raw(0, "SECTION");
|
||||
self.raw(2, name);
|
||||
}
|
||||
|
||||
fn end_section(&mut self) {
|
||||
self.raw(0, "ENDSEC");
|
||||
}
|
||||
|
||||
fn table(&mut self, name: &str, h: Handle, count: usize) {
|
||||
self.raw(0, "TABLE");
|
||||
self.raw(2, name);
|
||||
if self.v.handles() {
|
||||
self.handle(5, h);
|
||||
self.raw(330, "0");
|
||||
self.raw(100, "AcDbSymbolTable");
|
||||
}
|
||||
self.int(70, count as i64);
|
||||
if name == "DIMSTYLE" && self.v.r2000() {
|
||||
self.raw(100, "AcDbDimStyleTable");
|
||||
}
|
||||
}
|
||||
|
||||
fn end_table(&mut self) {
|
||||
self.raw(0, "ENDTAB");
|
||||
}
|
||||
}
|
||||
|
||||
/// How an entity is drawn: its colour index, true colour, lineweight, linetype and transparency.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct EntityStyle {
|
||||
pub aci: u8,
|
||||
/// `0xRRGGBB` (DXF 2004 and later).
|
||||
pub rgb: Option<u32>,
|
||||
/// Hundredths of a millimetre (DXF 2000 and later).
|
||||
pub lineweight: Option<i32>,
|
||||
pub linetype: Option<String>,
|
||||
/// Opacity 0–255 (DXF 2004 and later; opaque is left out).
|
||||
pub alpha: Option<u8>,
|
||||
}
|
||||
|
||||
impl Codes {
|
||||
/// The start of an entity of `kind` owned by `owner` (model space, or a polyline for its
|
||||
/// vertices) on `layer` with its style, ending with `subclass` (none when empty).
|
||||
pub fn entity(&mut self, kind: &str, h: Handle, owner: Handle, layer: &str, style: &EntityStyle, subclass: &str) {
|
||||
self.object(kind, h, owner, "AcDbEntity");
|
||||
self.str(8, layer);
|
||||
if let Some(lt) = &style.linetype {
|
||||
self.str(6, lt);
|
||||
}
|
||||
self.int(62, i64::from(style.aci.max(1)));
|
||||
if self.v.r2000()
|
||||
&& let Some(lw) = style.lineweight
|
||||
{
|
||||
self.int(370, i64::from(lw));
|
||||
}
|
||||
if self.v.true_color() {
|
||||
if let Some(rgb) = style.rgb {
|
||||
self.int(420, i64::from(rgb));
|
||||
}
|
||||
if let Some(a) = style.alpha {
|
||||
self.int(440, 0x0200_0000 | i64::from(a));
|
||||
}
|
||||
}
|
||||
if self.v.handles() && !subclass.is_empty() {
|
||||
self.raw(100, subclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A DXF layer.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Layer {
|
||||
pub name: String,
|
||||
pub aci: u8,
|
||||
/// Switched off (a hidden layer).
|
||||
pub off: bool,
|
||||
pub locked: bool,
|
||||
/// Plotted (printable).
|
||||
pub plot: bool,
|
||||
}
|
||||
|
||||
/// A dash pattern: dash, gap, dash… lengths in drawing units.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct LineType {
|
||||
pub name: String,
|
||||
pub pattern: Vec<f64>,
|
||||
}
|
||||
|
||||
/// A placed image file and the image entities that show it.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ImageDef {
|
||||
pub name: String,
|
||||
pub size: (u32, u32),
|
||||
pub handle: Handle,
|
||||
/// `(reactor, image entity)` per image entity.
|
||||
pub reactors: Vec<(Handle, Handle)>,
|
||||
}
|
||||
|
||||
/// Everything around the entities, collected while they were written.
|
||||
pub(crate) struct Drawing {
|
||||
pub version: DxfVersion,
|
||||
pub layers: Vec<Layer>,
|
||||
pub linetypes: Vec<LineType>,
|
||||
/// Text styles: (style name, font family).
|
||||
pub styles: Vec<(String, String)>,
|
||||
pub images: Vec<ImageDef>,
|
||||
/// The entities' group codes.
|
||||
pub entities: String,
|
||||
/// `[x0, y0, x1, y1]` of everything written (drawing units).
|
||||
pub extents: [f64; 4],
|
||||
/// The export region (drawing units).
|
||||
pub limits: [f64; 4],
|
||||
/// `$INSUNITS`.
|
||||
pub insunits: u8,
|
||||
pub metric: bool,
|
||||
/// The next free handle.
|
||||
pub next: u32,
|
||||
}
|
||||
|
||||
/// The text style every drawing has.
|
||||
pub(crate) const STANDARD_STYLE: &str = "Standard";
|
||||
|
||||
impl Drawing {
|
||||
/// The whole file.
|
||||
pub fn write(self) -> String {
|
||||
let v = self.version;
|
||||
let mut c = Codes::new(v);
|
||||
// Table entries get the handles after the entities'.
|
||||
let mut next = self.next;
|
||||
let mut take = |n: usize| {
|
||||
let start = next;
|
||||
next = next.saturating_add(u32::try_from(n).unwrap_or(u32::MAX));
|
||||
start
|
||||
};
|
||||
let ltype_base = take(3 + self.linetypes.len());
|
||||
let layer_base = take(1 + self.layers.len());
|
||||
let style_base = take(1 + self.styles.len());
|
||||
self.header(&mut c, next);
|
||||
if v.handles() {
|
||||
self.classes(&mut c);
|
||||
}
|
||||
c.section("TABLES");
|
||||
self.vport(&mut c);
|
||||
self.ltypes(&mut c, ltype_base);
|
||||
self.layer_table(&mut c, layer_base);
|
||||
self.style_table(&mut c, style_base);
|
||||
if v.handles() {
|
||||
for (name, h) in [("VIEW", fixed::VIEW_TABLE), ("UCS", fixed::UCS_TABLE)] {
|
||||
c.table(name, h, 0);
|
||||
c.end_table();
|
||||
}
|
||||
c.table("APPID", fixed::APPID_TABLE, 1);
|
||||
c.record("APPID", fixed::ACAD_APPID, fixed::APPID_TABLE, "AcDbRegAppTableRecord");
|
||||
c.raw(2, "ACAD");
|
||||
c.int(70, 0);
|
||||
c.end_table();
|
||||
c.table("DIMSTYLE", fixed::DIMSTYLE_TABLE, 1);
|
||||
c.record("DIMSTYLE", fixed::STANDARD_DIMSTYLE, fixed::DIMSTYLE_TABLE, "AcDbDimStyleTableRecord");
|
||||
c.raw(2, "Standard");
|
||||
c.int(70, 0);
|
||||
c.end_table();
|
||||
c.table("BLOCK_RECORD", fixed::BLOCK_RECORD_TABLE, 2);
|
||||
for (name, h, layout) in
|
||||
[("*Model_Space", fixed::MODEL_RECORD, fixed::MODEL_LAYOUT), ("*Paper_Space", fixed::PAPER_RECORD, fixed::PAPER_LAYOUT)]
|
||||
{
|
||||
c.record("BLOCK_RECORD", h, fixed::BLOCK_RECORD_TABLE, "AcDbBlockTableRecord");
|
||||
c.raw(2, name);
|
||||
if v.r2000() {
|
||||
c.handle(340, layout);
|
||||
c.int(70, 0);
|
||||
c.int(280, 1);
|
||||
c.int(281, 0);
|
||||
}
|
||||
}
|
||||
c.end_table();
|
||||
}
|
||||
c.end_section();
|
||||
if v.handles() {
|
||||
c.section("BLOCKS");
|
||||
for (name, record, begin, end) in [
|
||||
("*Model_Space", fixed::MODEL_RECORD, fixed::MODEL_BLOCK, fixed::MODEL_END),
|
||||
("*Paper_Space", fixed::PAPER_RECORD, fixed::PAPER_BLOCK, fixed::PAPER_END),
|
||||
] {
|
||||
c.object("BLOCK", begin, record, "AcDbEntity");
|
||||
c.raw(8, "0");
|
||||
c.raw(100, "AcDbBlockBegin");
|
||||
c.raw(2, name);
|
||||
c.int(70, 0);
|
||||
c.xyz(10, 0.0, 0.0);
|
||||
c.raw(3, name);
|
||||
c.raw(1, "");
|
||||
c.object("ENDBLK", end, record, "AcDbEntity");
|
||||
c.raw(8, "0");
|
||||
c.raw(100, "AcDbBlockEnd");
|
||||
}
|
||||
c.end_section();
|
||||
}
|
||||
c.section("ENTITIES");
|
||||
c.append(&self.entities);
|
||||
c.end_section();
|
||||
if v.handles() {
|
||||
self.objects(&mut c);
|
||||
}
|
||||
c.raw(0, "EOF");
|
||||
c.into_string()
|
||||
}
|
||||
|
||||
fn header(&self, c: &mut Codes, handseed: u32) {
|
||||
let v = self.version;
|
||||
let var = |c: &mut Codes, name: &str| c.raw(9, name);
|
||||
c.section("HEADER");
|
||||
var(c, "$ACADVER");
|
||||
c.raw(1, v.acadver());
|
||||
var(c, "$DWGCODEPAGE");
|
||||
c.raw(3, "ANSI_1252");
|
||||
var(c, "$INSBASE");
|
||||
c.xyz(10, 0.0, 0.0);
|
||||
let [x0, y0, x1, y1] = self.extents;
|
||||
var(c, "$EXTMIN");
|
||||
c.xyz(10, x0, y0);
|
||||
var(c, "$EXTMAX");
|
||||
c.xyz(10, x1, y1);
|
||||
let [lx0, ly0, lx1, ly1] = self.limits;
|
||||
var(c, "$LIMMIN");
|
||||
c.xy(10, lx0, ly0);
|
||||
var(c, "$LIMMAX");
|
||||
c.xy(10, lx1, ly1);
|
||||
if v.r2000() {
|
||||
var(c, "$LWDISPLAY");
|
||||
c.int(290, 1);
|
||||
var(c, "$INSUNITS");
|
||||
c.int(70, i64::from(self.insunits));
|
||||
var(c, "$MEASUREMENT");
|
||||
c.int(70, i64::from(self.metric));
|
||||
}
|
||||
if v.handles() {
|
||||
var(c, "$HANDSEED");
|
||||
c.handle(5, Handle(handseed));
|
||||
}
|
||||
c.end_section();
|
||||
}
|
||||
|
||||
fn classes(&self, c: &mut Codes) {
|
||||
let mut classes: Vec<(&str, &str, &str, i64, bool)> = vec![];
|
||||
if self.version.r2000() {
|
||||
classes.extend([
|
||||
("ACDBDICTIONARYWDFLT", "AcDbDictionaryWithDefault", "ObjectDBX Classes", 0, false),
|
||||
("ACDBPLACEHOLDER", "AcDbPlaceHolder", "ObjectDBX Classes", 0, false),
|
||||
("LAYOUT", "AcDbLayout", "ObjectDBX Classes", 0, false),
|
||||
]);
|
||||
}
|
||||
if !self.images.is_empty() {
|
||||
classes.extend([
|
||||
("RASTERVARIABLES", "AcDbRasterVariables", "ISM", 0, false),
|
||||
("IMAGE", "AcDbRasterImage", "ISM", 2175, true),
|
||||
("IMAGEDEF", "AcDbRasterImageDef", "ISM", 0, false),
|
||||
("IMAGEDEF_REACTOR", "AcDbRasterImageDefReactor", "ISM", 1, false),
|
||||
]);
|
||||
}
|
||||
c.section("CLASSES");
|
||||
for (name, cpp, app, flags, entity) in classes {
|
||||
c.raw(0, "CLASS");
|
||||
c.raw(1, name);
|
||||
c.raw(2, cpp);
|
||||
c.raw(3, app);
|
||||
c.int(90, flags);
|
||||
if self.version.true_color() {
|
||||
c.int(91, 0);
|
||||
}
|
||||
c.int(280, 0);
|
||||
c.int(281, i64::from(entity));
|
||||
}
|
||||
c.end_section();
|
||||
}
|
||||
|
||||
/// The active viewport, zoomed to the drawing's extents.
|
||||
fn vport(&self, c: &mut Codes) {
|
||||
let [x0, y0, x1, y1] = self.extents;
|
||||
c.table("VPORT", fixed::VPORT_TABLE, 1);
|
||||
c.record("VPORT", fixed::ACTIVE_VPORT, fixed::VPORT_TABLE, "AcDbViewportTableRecord");
|
||||
c.raw(2, if self.version.handles() { "*Active" } else { "*ACTIVE" });
|
||||
c.int(70, 0);
|
||||
c.xy(10, 0.0, 0.0);
|
||||
c.xy(11, 1.0, 1.0);
|
||||
c.xy(12, (x0 + x1) / 2.0, (y0 + y1) / 2.0);
|
||||
c.xy(13, 0.0, 0.0);
|
||||
c.xy(14, 1.0, 1.0);
|
||||
c.xy(15, 10.0, 10.0);
|
||||
// The view direction looks down the z axis: (0, 0, 1), each code once.
|
||||
c.xy(16, 0.0, 0.0);
|
||||
c.num(36, 1.0);
|
||||
c.xyz(17, 0.0, 0.0);
|
||||
let (w, h) = ((x1 - x0).abs(), (y1 - y0).abs());
|
||||
// The view's height, with a margin; its width follows from the aspect ratio.
|
||||
c.num(40, (h.max(w / 1.5) * 1.1).max(1.0));
|
||||
c.num(41, 1.5);
|
||||
c.num(42, 50.0);
|
||||
c.num(43, 0.0);
|
||||
c.num(44, 0.0);
|
||||
c.num(50, 0.0);
|
||||
c.num(51, 0.0);
|
||||
for (code, value) in [(71, 0), (72, 1000), (73, 1), (74, 3), (75, 0), (76, 0), (77, 0), (78, 0)] {
|
||||
c.int(code, value);
|
||||
}
|
||||
if self.version.r2000() {
|
||||
c.int(281, 0);
|
||||
c.int(65, 0);
|
||||
c.num(146, 0.0);
|
||||
}
|
||||
c.end_table();
|
||||
}
|
||||
|
||||
fn ltypes(&self, c: &mut Codes, base: u32) {
|
||||
c.table("LTYPE", fixed::LTYPE_TABLE, 3 + self.linetypes.len());
|
||||
let solid = [("ByBlock", ""), ("ByLayer", ""), ("Continuous", "Solid line")];
|
||||
let rows = solid.iter().map(|(n, d)| (*n, *d, &[][..])).chain(self.linetypes.iter().map(|l| (l.name.as_str(), "", l.pattern.as_slice())));
|
||||
for (i, (name, desc, pattern)) in rows.enumerate() {
|
||||
c.record("LTYPE", Handle(base.saturating_add(i as u32)), fixed::LTYPE_TABLE, "AcDbLinetypeTableRecord");
|
||||
c.str(2, name);
|
||||
c.int(70, 0);
|
||||
c.str(3, if pattern.is_empty() { desc } else { "Dashed" });
|
||||
c.int(72, 65);
|
||||
c.int(73, pattern.len() as i64);
|
||||
c.num(40, pattern.iter().map(|v| v.abs()).sum());
|
||||
for v in pattern {
|
||||
c.num(49, *v);
|
||||
if self.version.handles() {
|
||||
c.int(74, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
c.end_table();
|
||||
}
|
||||
|
||||
fn layer_table(&self, c: &mut Codes, base: u32) {
|
||||
let zero = Layer { name: "0".into(), aci: 7, off: false, locked: false, plot: true };
|
||||
c.table("LAYER", fixed::LAYER_TABLE, 1 + self.layers.len());
|
||||
for (i, l) in std::iter::once(&zero).chain(&self.layers).enumerate() {
|
||||
c.record("LAYER", Handle(base.saturating_add(i as u32)), fixed::LAYER_TABLE, "AcDbLayerTableRecord");
|
||||
c.str(2, &l.name);
|
||||
c.int(70, if l.locked { 4 } else { 0 });
|
||||
// A negative colour switches the layer off.
|
||||
let aci = i64::from(l.aci.max(1));
|
||||
c.int(62, if l.off { -aci } else { aci });
|
||||
c.raw(6, "Continuous");
|
||||
if self.version.r2000() {
|
||||
c.int(290, i64::from(l.plot));
|
||||
c.int(370, -3);
|
||||
c.handle(390, fixed::PLOT_NORMAL);
|
||||
}
|
||||
}
|
||||
c.end_table();
|
||||
}
|
||||
|
||||
fn style_table(&self, c: &mut Codes, base: u32) {
|
||||
c.table("STYLE", fixed::STYLE_TABLE, 1 + self.styles.len());
|
||||
let standard = (STANDARD_STYLE.to_string(), String::new());
|
||||
for (i, (name, family)) in std::iter::once(&standard).chain(&self.styles).enumerate() {
|
||||
c.record("STYLE", Handle(base.saturating_add(i as u32)), fixed::STYLE_TABLE, "AcDbTextStyleTableRecord");
|
||||
c.str(2, name);
|
||||
c.int(70, 0);
|
||||
c.num(40, 0.0);
|
||||
c.num(41, 1.0);
|
||||
c.num(50, 0.0);
|
||||
c.int(71, 0);
|
||||
c.num(42, 2.5);
|
||||
// The font file; from 2000 the family name too, which is how CAD apps find a TrueType font.
|
||||
if family.is_empty() {
|
||||
c.raw(3, "txt");
|
||||
} else {
|
||||
c.str(3, &format!("{family}.ttf"));
|
||||
}
|
||||
c.raw(4, "");
|
||||
if self.version.r2000() && !family.is_empty() {
|
||||
c.raw(1001, "ACAD");
|
||||
c.str(1000, family);
|
||||
c.int(1071, 0);
|
||||
}
|
||||
}
|
||||
c.end_table();
|
||||
}
|
||||
|
||||
fn objects(&self, c: &mut Codes) {
|
||||
let v = self.version;
|
||||
let images = !self.images.is_empty();
|
||||
c.section("OBJECTS");
|
||||
let dict = |c: &mut Codes, h: Handle, owner: Handle, entries: &[(&str, Handle)]| {
|
||||
c.object("DICTIONARY", h, owner, "AcDbDictionary");
|
||||
c.int(281, 1);
|
||||
for (name, e) in entries {
|
||||
c.str(3, name);
|
||||
c.handle(350, *e);
|
||||
}
|
||||
};
|
||||
let mut root = vec![("ACAD_GROUP", fixed::GROUPS)];
|
||||
if images {
|
||||
root.extend([("ACAD_IMAGE_DICT", fixed::IMAGE_DICT), ("ACAD_IMAGE_VARS", fixed::IMAGE_VARS)]);
|
||||
}
|
||||
if v.r2000() {
|
||||
root.extend([("ACAD_LAYOUT", fixed::LAYOUTS), ("ACAD_PLOTSTYLENAME", fixed::PLOT_STYLES)]);
|
||||
}
|
||||
dict(c, fixed::ROOT, Handle(0), &root);
|
||||
dict(c, fixed::GROUPS, fixed::ROOT, &[]);
|
||||
if v.r2000() {
|
||||
dict(c, fixed::LAYOUTS, fixed::ROOT, &[("Layout1", fixed::PAPER_LAYOUT), ("Model", fixed::MODEL_LAYOUT)]);
|
||||
c.object("ACDBDICTIONARYWDFLT", fixed::PLOT_STYLES, fixed::ROOT, "AcDbDictionary");
|
||||
c.int(281, 1);
|
||||
c.raw(3, "Normal");
|
||||
c.handle(350, fixed::PLOT_NORMAL);
|
||||
c.raw(100, "AcDbDictionaryWithDefault");
|
||||
c.handle(340, fixed::PLOT_NORMAL);
|
||||
// A placeholder has no data and no subclass marker of its own.
|
||||
c.raw(0, "ACDBPLACEHOLDER");
|
||||
c.handle(5, fixed::PLOT_NORMAL);
|
||||
c.handle(330, fixed::PLOT_STYLES);
|
||||
self.layout(c, fixed::MODEL_LAYOUT, "Model", 0, fixed::MODEL_RECORD);
|
||||
self.layout(c, fixed::PAPER_LAYOUT, "Layout1", 1, fixed::PAPER_RECORD);
|
||||
}
|
||||
if images {
|
||||
c.object("RASTERVARIABLES", fixed::IMAGE_VARS, fixed::ROOT, "AcDbRasterVariables");
|
||||
c.int(90, 0);
|
||||
c.int(70, 0);
|
||||
c.int(71, 1);
|
||||
c.int(72, 0);
|
||||
let entries: Vec<(&str, Handle)> = self.images.iter().map(|i| (i.name.as_str(), i.handle)).collect();
|
||||
dict(c, fixed::IMAGE_DICT, fixed::ROOT, &entries);
|
||||
for img in &self.images {
|
||||
c.raw(0, "IMAGEDEF");
|
||||
c.handle(5, img.handle);
|
||||
c.raw(102, "{ACAD_REACTORS");
|
||||
c.handle(330, fixed::IMAGE_DICT);
|
||||
for (reactor, _) in &img.reactors {
|
||||
c.handle(330, *reactor);
|
||||
}
|
||||
c.raw(102, "}");
|
||||
c.handle(330, fixed::IMAGE_DICT);
|
||||
c.raw(100, "AcDbRasterImageDef");
|
||||
c.int(90, 0);
|
||||
c.str(1, &img.name);
|
||||
c.xy(10, f64::from(img.size.0), f64::from(img.size.1));
|
||||
c.xy(11, 1.0, 1.0);
|
||||
c.int(280, 1);
|
||||
c.int(281, 0);
|
||||
for (reactor, image) in &img.reactors {
|
||||
c.object("IMAGEDEF_REACTOR", *reactor, *image, "AcDbRasterImageDefReactor");
|
||||
c.int(90, 2);
|
||||
c.handle(330, *image);
|
||||
}
|
||||
}
|
||||
}
|
||||
c.end_section();
|
||||
}
|
||||
|
||||
/// A layout (DXF 2000 and later): its plot settings, then the layout itself.
|
||||
fn layout(&self, c: &mut Codes, h: Handle, name: &str, tab: i64, record: Handle) {
|
||||
let [x0, y0, x1, y1] = self.extents;
|
||||
c.object("LAYOUT", h, fixed::LAYOUTS, "AcDbPlotSettings");
|
||||
c.raw(1, "");
|
||||
c.raw(2, "none_device");
|
||||
c.raw(4, "");
|
||||
c.raw(6, "");
|
||||
for code in 40..=49 {
|
||||
c.num(code, 0.0);
|
||||
}
|
||||
c.num(140, 0.0);
|
||||
c.num(141, 0.0);
|
||||
c.num(142, 1.0);
|
||||
c.num(143, 1.0);
|
||||
// Plot flags; 1024 marks the model space layout.
|
||||
c.int(70, if record == fixed::MODEL_RECORD { 688 | 1024 } else { 688 });
|
||||
c.int(72, 1);
|
||||
c.int(73, 0);
|
||||
c.int(74, 5);
|
||||
c.raw(7, "");
|
||||
c.int(75, 16);
|
||||
if self.version.true_color() {
|
||||
// Shade plot mode (as displayed), resolution level (normal) and custom DPI.
|
||||
c.int(76, 0);
|
||||
c.int(77, 2);
|
||||
c.int(78, 300);
|
||||
}
|
||||
c.num(147, 1.0);
|
||||
// The paper image origin is 148 (x) and 149 (y), not a point at 148 and 158.
|
||||
c.num(148, 0.0);
|
||||
c.num(149, 0.0);
|
||||
c.raw(100, "AcDbLayout");
|
||||
c.raw(1, name);
|
||||
c.int(70, 1);
|
||||
c.int(71, tab);
|
||||
let [lx0, ly0, lx1, ly1] = self.limits;
|
||||
c.xy(10, lx0, ly0);
|
||||
c.xy(11, lx1, ly1);
|
||||
c.xyz(12, 0.0, 0.0);
|
||||
c.xyz(14, x0, y0);
|
||||
c.xyz(15, x1, y1);
|
||||
c.num(146, 0.0);
|
||||
c.xyz(13, 0.0, 0.0);
|
||||
c.xyz(16, 1.0, 0.0);
|
||||
c.xyz(17, 0.0, 1.0);
|
||||
c.int(76, 1);
|
||||
c.handle(330, record);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn numbers_and_text_are_written_plainly() {
|
||||
assert_eq!(num(1.5), "1.5");
|
||||
assert_eq!(num(-0.0000001), "0");
|
||||
assert_eq!(num(2.0), "2");
|
||||
assert_eq!(num(f64::NAN), "0");
|
||||
assert_eq!(text("a^b\nc", true), "a^ b c");
|
||||
assert_eq!(text("Größe 😀", false), "Gr\\U+00F6\\U+00DFe ?");
|
||||
assert_eq!(text("Größe", true), "Größe");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
0
|
||||
SECTION
|
||||
2
|
||||
HEADER
|
||||
9
|
||||
$ACADVER
|
||||
1
|
||||
AC1009
|
||||
9
|
||||
$DWGCODEPAGE
|
||||
3
|
||||
ANSI_1252
|
||||
9
|
||||
$INSBASE
|
||||
10
|
||||
0
|
||||
20
|
||||
0
|
||||
30
|
||||
0
|
||||
9
|
||||
$EXTMIN
|
||||
10
|
||||
10
|
||||
20
|
||||
20
|
||||
30
|
||||
0
|
||||
9
|
||||
$EXTMAX
|
||||
10
|
||||
180
|
||||
20
|
||||
90
|
||||
30
|
||||
0
|
||||
9
|
||||
$LIMMIN
|
||||
10
|
||||
0
|
||||
20
|
||||
0
|
||||
9
|
||||
$LIMMAX
|
||||
10
|
||||
200
|
||||
20
|
||||
100
|
||||
0
|
||||
ENDSEC
|
||||
0
|
||||
SECTION
|
||||
2
|
||||
TABLES
|
||||
0
|
||||
TABLE
|
||||
2
|
||||
VPORT
|
||||
70
|
||||
1
|
||||
0
|
||||
VPORT
|
||||
2
|
||||
*ACTIVE
|
||||
70
|
||||
0
|
||||
10
|
||||
0
|
||||
20
|
||||
0
|
||||
11
|
||||
1
|
||||
21
|
||||
1
|
||||
12
|
||||
95
|
||||
22
|
||||
55
|
||||
13
|
||||
0
|
||||
23
|
||||
0
|
||||
14
|
||||
1
|
||||
24
|
||||
1
|
||||
15
|
||||
10
|
||||
25
|
||||
10
|
||||
16
|
||||
0
|
||||
26
|
||||
0
|
||||
36
|
||||
1
|
||||
17
|
||||
0
|
||||
27
|
||||
0
|
||||
37
|
||||
0
|
||||
40
|
||||
124.666667
|
||||
41
|
||||
1.5
|
||||
42
|
||||
50
|
||||
43
|
||||
0
|
||||
44
|
||||
0
|
||||
50
|
||||
0
|
||||
51
|
||||
0
|
||||
71
|
||||
0
|
||||
72
|
||||
1000
|
||||
73
|
||||
1
|
||||
74
|
||||
3
|
||||
75
|
||||
0
|
||||
76
|
||||
0
|
||||
77
|
||||
0
|
||||
78
|
||||
0
|
||||
0
|
||||
ENDTAB
|
||||
0
|
||||
TABLE
|
||||
2
|
||||
LTYPE
|
||||
70
|
||||
3
|
||||
0
|
||||
LTYPE
|
||||
2
|
||||
ByBlock
|
||||
70
|
||||
0
|
||||
3
|
||||
|
||||
72
|
||||
65
|
||||
73
|
||||
0
|
||||
40
|
||||
0
|
||||
0
|
||||
LTYPE
|
||||
2
|
||||
ByLayer
|
||||
70
|
||||
0
|
||||
3
|
||||
|
||||
72
|
||||
65
|
||||
73
|
||||
0
|
||||
40
|
||||
0
|
||||
0
|
||||
LTYPE
|
||||
2
|
||||
Continuous
|
||||
70
|
||||
0
|
||||
3
|
||||
Solid line
|
||||
72
|
||||
65
|
||||
73
|
||||
0
|
||||
40
|
||||
0
|
||||
0
|
||||
ENDTAB
|
||||
0
|
||||
TABLE
|
||||
2
|
||||
LAYER
|
||||
70
|
||||
2
|
||||
0
|
||||
LAYER
|
||||
2
|
||||
0
|
||||
70
|
||||
0
|
||||
62
|
||||
7
|
||||
6
|
||||
Continuous
|
||||
0
|
||||
LAYER
|
||||
2
|
||||
LAYER_1
|
||||
70
|
||||
0
|
||||
62
|
||||
171
|
||||
6
|
||||
Continuous
|
||||
0
|
||||
ENDTAB
|
||||
0
|
||||
TABLE
|
||||
2
|
||||
STYLE
|
||||
70
|
||||
1
|
||||
0
|
||||
STYLE
|
||||
2
|
||||
Standard
|
||||
70
|
||||
0
|
||||
40
|
||||
0
|
||||
41
|
||||
1
|
||||
50
|
||||
0
|
||||
71
|
||||
0
|
||||
42
|
||||
2.5
|
||||
3
|
||||
txt
|
||||
4
|
||||
|
||||
0
|
||||
ENDTAB
|
||||
0
|
||||
ENDSEC
|
||||
0
|
||||
SECTION
|
||||
2
|
||||
ENTITIES
|
||||
0
|
||||
POLYLINE
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
1
|
||||
66
|
||||
1
|
||||
10
|
||||
0
|
||||
20
|
||||
0
|
||||
30
|
||||
0
|
||||
70
|
||||
1
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
1
|
||||
10
|
||||
10
|
||||
20
|
||||
90
|
||||
30
|
||||
0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
1
|
||||
10
|
||||
110
|
||||
20
|
||||
90
|
||||
30
|
||||
0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
1
|
||||
10
|
||||
110
|
||||
20
|
||||
40
|
||||
30
|
||||
0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
1
|
||||
10
|
||||
10
|
||||
20
|
||||
40
|
||||
30
|
||||
0
|
||||
0
|
||||
SEQEND
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
1
|
||||
0
|
||||
POLYLINE
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
7
|
||||
66
|
||||
1
|
||||
10
|
||||
0
|
||||
20
|
||||
0
|
||||
30
|
||||
0
|
||||
70
|
||||
1
|
||||
40
|
||||
1
|
||||
41
|
||||
1
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
7
|
||||
10
|
||||
10
|
||||
20
|
||||
90
|
||||
30
|
||||
0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
7
|
||||
10
|
||||
110
|
||||
20
|
||||
90
|
||||
30
|
||||
0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
7
|
||||
10
|
||||
110
|
||||
20
|
||||
40
|
||||
30
|
||||
0
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
7
|
||||
10
|
||||
10
|
||||
20
|
||||
40
|
||||
30
|
||||
0
|
||||
0
|
||||
SEQEND
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
7
|
||||
0
|
||||
POLYLINE
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
5
|
||||
66
|
||||
1
|
||||
10
|
||||
0
|
||||
20
|
||||
0
|
||||
30
|
||||
0
|
||||
70
|
||||
1
|
||||
40
|
||||
2
|
||||
41
|
||||
2
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
5
|
||||
10
|
||||
120
|
||||
20
|
||||
50
|
||||
30
|
||||
0
|
||||
42
|
||||
-2.414214
|
||||
0
|
||||
VERTEX
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
5
|
||||
10
|
||||
150
|
||||
20
|
||||
20
|
||||
30
|
||||
0
|
||||
42
|
||||
-0.414214
|
||||
0
|
||||
SEQEND
|
||||
8
|
||||
LAYER_1
|
||||
62
|
||||
5
|
||||
0
|
||||
ENDSEC
|
||||
0
|
||||
EOF
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user