├── .czrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ ├── cd.yml
│ └── ci.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── __tests__
├── __snapshots__
│ ├── document.test.ts.snap
│ └── writer.test.ts.snap
├── blocks
│ ├── __snapshots__
│ │ ├── block.test.ts.snap
│ │ ├── blocks.test.ts.snap
│ │ └── endblk.test.ts.snap
│ ├── block.test.ts
│ ├── blocks.test.ts
│ └── endblk.test.ts
├── document.test.ts
├── entities
│ ├── __snapshots__
│ │ ├── entity.test.ts.snap
│ │ ├── face.test.ts.snap
│ │ ├── line.test.ts.snap
│ │ └── polyline.test.ts.snap
│ ├── __snapshots__line.test.ts.snap
│ ├── dimension
│ │ ├── __snapshots__
│ │ │ └── arc.test.ts.snap
│ │ └── arc.test.ts
│ ├── entities.test.ts
│ ├── entity.test.ts
│ ├── face.test.ts
│ ├── line.test.ts
│ ├── manager.test.ts
│ ├── polyline.test.ts
│ └── xdata.test.ts
├── header
│ ├── __snapshots__
│ │ └── header.test.ts.snap
│ ├── header.test.ts
│ └── variable.test.ts
├── helpers
│ ├── angles.test.ts
│ └── primitives
│ │ ├── arc.test.ts
│ │ └── line.test.ts
├── objects
│ ├── __snapshots__
│ │ └── objects.test.ts.snap
│ ├── object.test.ts
│ └── objects.test.ts
├── tables
│ ├── __snapshots__
│ │ └── tables.test.ts.snap
│ ├── entry.test.ts
│ └── tables.test.ts
├── utils
│ ├── application.test.ts
│ ├── bbox.test.ts
│ ├── color.test.ts
│ ├── functions.test.ts
│ ├── seeder.test.ts
│ ├── tags.test.ts
│ └── text.test.ts
└── writer.test.ts
├── docs
├── .vitepress
│ ├── config.ts
│ └── theme
│ │ ├── index.ts
│ │ └── main.css
├── _media
│ ├── contributors.svg
│ └── sponsors.svg
├── guides
│ ├── lineweights.md
│ └── mtext.md
├── index.md
├── public
│ └── logo.svg
├── sections
│ ├── entities.md
│ ├── header.md
│ ├── objects.md
│ └── tables.md
├── start.md
└── v2
│ ├── guide
│ ├── _media
│ │ ├── ellipse-demo.png
│ │ └── linetype-axes.png
│ ├── blocks.md
│ ├── entities.md
│ ├── header.md
│ ├── index.md
│ ├── objects.md
│ └── tables.md
│ ├── index.md
│ └── tutoriels
│ └── xdata.md
├── examples
├── blocks.ts
├── color.ts
├── dimension.ts
├── hatch.ts
├── index.ts
├── leader.ts
├── lwpolyline.ts
├── mesh.ts
├── mleader.ts
├── mtext.ts
├── paper-space.ts
├── polyline.ts
├── quick-start.ts
├── rectangle.ts
├── svg.ts
├── table.ts
├── text.ts
└── utils.ts
├── package.json
├── pnpm-lock.yaml
├── src
├── blocks
│ ├── block.ts
│ ├── blocks.ts
│ ├── endblk.ts
│ └── index.ts
├── classes
│ ├── classes.ts
│ └── index.ts
├── document.ts
├── entities
│ ├── arc.ts
│ ├── attdef.ts
│ ├── attrib.ts
│ ├── circle.ts
│ ├── dimension
│ │ ├── aligned.ts
│ │ ├── angular
│ │ │ ├── index.ts
│ │ │ ├── lines.ts
│ │ │ └── points.ts
│ │ ├── arc.ts
│ │ ├── diameter.ts
│ │ ├── dimension.ts
│ │ ├── index.ts
│ │ ├── linear.ts
│ │ ├── radial.ts
│ │ └── render
│ │ │ ├── arrow.ts
│ │ │ ├── index.ts
│ │ │ └── renderer.ts
│ ├── ellipse.ts
│ ├── entities.ts
│ ├── entity.ts
│ ├── face.ts
│ ├── hatch
│ │ ├── arc.ts
│ │ ├── boundary.ts
│ │ ├── edges.ts
│ │ ├── ellipse.ts
│ │ ├── gradient.ts
│ │ ├── hatch.ts
│ │ ├── index.ts
│ │ ├── line.ts
│ │ ├── pattern.ts
│ │ └── polyline.ts
│ ├── index.ts
│ ├── insert.ts
│ ├── leader.ts
│ ├── line.ts
│ ├── lwpolyline.ts
│ ├── manager.ts
│ ├── mesh.ts
│ ├── mleader.ts
│ ├── mtext.ts
│ ├── point.ts
│ ├── polyline.ts
│ ├── ray.ts
│ ├── seqend.ts
│ ├── solid.ts
│ ├── spline.ts
│ ├── table.ts
│ ├── text.ts
│ └── vertex.ts
├── header
│ ├── header.ts
│ ├── index.ts
│ └── variable.ts
├── helpers
│ ├── angles.ts
│ ├── constants.ts
│ ├── index.ts
│ ├── periodic.ts
│ ├── primitives
│ │ ├── arc.ts
│ │ ├── index.ts
│ │ ├── line.ts
│ │ └── vector.ts
│ ├── transform.ts
│ └── types.ts
├── index.ts
├── objects
│ ├── dictionary.ts
│ ├── index.ts
│ ├── object.ts
│ └── objects.ts
├── shapes
│ ├── index.ts
│ └── rectangle.ts
├── svg
│ ├── colors.ts
│ ├── elements.ts
│ ├── exporter.ts
│ ├── guards.ts
│ └── index.ts
├── tables
│ ├── appid.ts
│ ├── block.ts
│ ├── dimstyle.ts
│ ├── entry.ts
│ ├── index.ts
│ ├── layer.ts
│ ├── ltype.ts
│ ├── style.ts
│ ├── table.ts
│ ├── tables.ts
│ ├── ucs.ts
│ ├── view.ts
│ └── vport.ts
├── types.ts
├── utils
│ ├── application.ts
│ ├── bbox.ts
│ ├── color.ts
│ ├── constants.ts
│ ├── functions.ts
│ ├── index.ts
│ ├── seeder.ts
│ ├── tags.ts
│ ├── text.ts
│ └── xdata.ts
└── writer.ts
├── tsconfig.json
├── tsup.config.ts
├── vercel.json
└── vitest.config.ts
/.czrc:
--------------------------------------------------------------------------------
1 | {
2 | "path": "cz-conventional-changelog"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_size = 2
8 | indent_style = space
9 | trim_trailing_whitespace = true
10 | tab_width = 2
11 | charset = utf-8
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /examples/*.js
2 | /lib/**/*
3 | /dist/**/*
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/recommended"
10 | ],
11 | "parser": "@typescript-eslint/parser",
12 | "parserOptions": {
13 | "ecmaVersion": "latest",
14 | "sourceType": "module"
15 | },
16 | "plugins": ["@typescript-eslint"],
17 | "rules": {
18 | "indent": ["error", 2],
19 | "linebreak-style": ["error", "unix"],
20 | "quotes": ["error", "double"],
21 | "semi": ["error", "always"],
22 | "eol-last": [ "error", "unix"],
23 | "sort-imports": ["error", {}]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [dxfjs]
4 | patreon: tarikjabiri
5 | #open_collective: # Replace with a single Open Collective username
6 | #ko_fi: # Replace with a single Ko-fi username
7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | #liberapay: # Replace with a single Liberapay username
10 | #issuehunt: # Replace with a single IssueHunt username
11 | #otechie: # Replace with a single Otechie username
12 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yml:
--------------------------------------------------------------------------------
1 | name: CD
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | CD:
9 | name: CD
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-node@v3
14 | with:
15 | node-version: 16
16 | registry-url: "https://registry.npmjs.org"
17 | - run: yarn install
18 | - run: yarn test
19 | - run: yarn build
20 | - run: yarn publish
21 | env:
22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | test-build:
7 | name: Continuous Integration
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-node@v3
12 | with:
13 | node-version: 18
14 | - uses: pnpm/action-setup@v2
15 | with:
16 | version: 8.2.0
17 | - run: pnpm install
18 | - run: pnpm test -- --coverage
19 | - run: pnpm build
20 | - name: Upload coverage reports to Codecov
21 | run: |
22 | curl -Os https://uploader.codecov.io/latest/linux/codecov
23 | chmod +x codecov
24 | ./codecov -t ${CODECOV_TOKEN}
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders
2 | *node_modules*
3 | *lib*
4 | *dist*
5 | *cache*
6 |
7 | coverage
8 |
9 | .idea
10 |
11 | # Files
12 | **/*.dxf
13 | **/*.bak
14 | **/*.dwl
15 | **/*.dwl2
16 | **/*.err
17 | **/*.py
18 | **/*.svg
19 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021-present EL JABIRI Tarik
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # writer [](https://openbase.com/js/@tarikjabiri/dxf?utm_source=embedded&utm_medium=badge&utm_campaign=rate-badge)
2 |
3 | A Javascript dxf generator, written in Typescript.
4 |
5 | 
6 | 
7 | [](https://codecov.io/gh/dxfjs/writer)
8 |
9 |
10 | 
11 | 
12 | 
13 |
14 |
15 |
16 | ## Installation
17 |
18 | ```bash
19 | yarn add @tarikjabiri/dxf
20 | # Or npm
21 | npm i @tarikjabiri/dxf
22 | # Or pnpm
23 | pnpm add @tarikjabiri/dxf
24 | ```
25 |
26 | ## Getting started
27 |
28 | ```js
29 | import { Writer, point } from "@tarikjabiri/dxf";
30 |
31 | const writer = new Writer();
32 | const modelSpace = writer.document.modelSpace;
33 |
34 | // Add entites to the model space
35 | modelSpace.addLine({
36 | start: point(),
37 | end: point(100, 100),
38 | // Other options...
39 | });
40 |
41 | // To get the dxf content just call the stringify() method
42 | const content = writer.stringify();
43 | ```
44 |
45 | ## More informations
46 |
47 | - [Documentation](https://dxf.vercel.app/)
48 |
49 | ## Sponsors
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/__tests__/blocks/__snapshots__/block.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Block class > should create an empty block 1`] = `
4 | "0
5 | BLOCK
6 | 5
7 | 2
8 | 330
9 | 0
10 | 100
11 | AcDbEntity
12 | 8
13 | 0
14 | 100
15 | AcDbBlockBegin
16 | 2
17 | *Model_Space
18 | 70
19 | 0
20 | 10
21 | 0
22 | 20
23 | 0
24 | 30
25 | 0
26 | 3
27 | *Model_Space
28 | 1
29 |
30 | 0
31 | ENDBLK
32 | 5
33 | 3
34 | 330
35 | 0
36 | 100
37 | AcDbEntity
38 | 8
39 | 0
40 | 100
41 | AcDbBlockEnd"
42 | `;
43 |
44 | exports[`Block class > should create an empty block 2`] = `
45 | "0
46 | BLOCK
47 | 5
48 | 2
49 | 330
50 | 0
51 | 100
52 | AcDbEntity
53 | 8
54 | 0
55 | 100
56 | AcDbBlockBegin
57 | 2
58 | *Model_Space
59 | 70
60 | 0
61 | 10
62 | 0
63 | 20
64 | 0
65 | 30
66 | 0
67 | 3
68 | *Model_Space
69 | 1
70 |
71 | 0
72 | ENDBLK
73 | 5
74 | 3
75 | 330
76 | 0
77 | 100
78 | AcDbEntity
79 | 8
80 | 0
81 | 100
82 | AcDbBlockEnd"
83 | `;
84 |
--------------------------------------------------------------------------------
/__tests__/blocks/__snapshots__/blocks.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Blocks class > should create a blocks section 1`] = `
4 | "0
5 | SECTION
6 | 2
7 | BLOCKS
8 | 0
9 | BLOCK
10 | 5
11 | 13
12 | 330
13 | 12
14 | 100
15 | AcDbEntity
16 | 8
17 | 0
18 | 100
19 | AcDbBlockBegin
20 | 2
21 | *Model_Space
22 | 70
23 | 0
24 | 10
25 | 0
26 | 20
27 | 0
28 | 30
29 | 0
30 | 3
31 | *Model_Space
32 | 1
33 |
34 | 0
35 | ENDBLK
36 | 5
37 | 14
38 | 330
39 | 12
40 | 100
41 | AcDbEntity
42 | 8
43 | 0
44 | 100
45 | AcDbBlockEnd
46 | 0
47 | BLOCK
48 | 5
49 | 16
50 | 330
51 | 15
52 | 100
53 | AcDbEntity
54 | 8
55 | 0
56 | 100
57 | AcDbBlockBegin
58 | 2
59 | *Paper_Space
60 | 70
61 | 0
62 | 10
63 | 0
64 | 20
65 | 0
66 | 30
67 | 0
68 | 3
69 | *Paper_Space
70 | 1
71 |
72 | 0
73 | ENDBLK
74 | 5
75 | 17
76 | 330
77 | 15
78 | 100
79 | AcDbEntity
80 | 8
81 | 0
82 | 100
83 | AcDbBlockEnd
84 | 0
85 | ENDSEC"
86 | `;
87 |
--------------------------------------------------------------------------------
/__tests__/blocks/__snapshots__/endblk.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`EndBlk class > should create an endblk instance 1`] = `
4 | "0
5 | ENDBLK
6 | 5
7 | 1
8 | 330
9 | 0
10 | 100
11 | AcDbEntity
12 | 8
13 | 0
14 | 100
15 | AcDbBlockEnd"
16 | `;
17 |
--------------------------------------------------------------------------------
/__tests__/blocks/block.test.ts:
--------------------------------------------------------------------------------
1 | import { Block, BlockRecordEntry, Seeder, TagsManager } from "@/index";
2 |
3 | describe("Block class", () => {
4 | const seeder = new Seeder();
5 | const options = { name: "*Model_Space", seeder };
6 | it("should create an empty block", () => {
7 | const block = new Block({
8 | ...options,
9 | blockRecord: new BlockRecordEntry(options),
10 | });
11 | block.addAppDefined("ACAD_REACTORS");
12 | const mg = new TagsManager();
13 | block.tagify(mg);
14 | expect(mg.stringify()).toMatchSnapshot();
15 | mg.clear();
16 | block.tagify(mg);
17 | expect(mg.stringify()).toMatchSnapshot();
18 | });
19 |
20 | it("should create defined application", () => {
21 | const block = new Block({
22 | ...options,
23 | blockRecord: new BlockRecordEntry(options),
24 | });
25 | const reactors = block.addAppDefined("ACAD_REACTORS");
26 | expect(reactors.name).toBe("ACAD_REACTORS");
27 | const test = block.addAppDefined("ACAD_REACTORS");
28 | expect(test).toBe(reactors);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/__tests__/blocks/blocks.test.ts:
--------------------------------------------------------------------------------
1 | import { Blocks, Seeder, Tables, TagsManager } from "@/index";
2 |
3 | describe("Blocks class", () => {
4 | const seeder = new Seeder();
5 | it("should create a blocks section", () => {
6 | const options = { seeder };
7 | const blocks = new Blocks({
8 | ...options,
9 | tables: new Tables(options),
10 | });
11 | const mg = new TagsManager();
12 | blocks.tagify(mg);
13 | expect(mg.stringify()).toMatchSnapshot();
14 | expect(blocks.paperSpace.isPaperSpace).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/__tests__/blocks/endblk.test.ts:
--------------------------------------------------------------------------------
1 | import { EndBlk, Seeder, TagsManager } from "@/index";
2 |
3 | describe("EndBlk class", () => {
4 | it("should create an endblk instance", () => {
5 | const block = new EndBlk({ seeder: new Seeder() });
6 | block.addAppDefined("ACAD_REACTORS");
7 | const mg = new TagsManager();
8 | block.tagify(mg);
9 | expect(mg.stringify()).toMatchSnapshot();
10 | });
11 |
12 | it("should create defined application", () => {
13 | const block = new EndBlk({ seeder: new Seeder() });
14 | const reactors = block.addAppDefined("ACAD_REACTORS");
15 | expect(reactors.name).toBe("ACAD_REACTORS");
16 | const test = block.addAppDefined("ACAD_REACTORS");
17 | expect(test).toBe(reactors);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/__tests__/document.test.ts:
--------------------------------------------------------------------------------
1 | import { Document, Units } from "@/index";
2 |
3 | describe("Document class", () => {
4 | it("should create dxf document", () => {
5 | const document = new Document();
6 | expect(document.stringify()).toMatchSnapshot();
7 | });
8 |
9 | it("should set units", () => {
10 | const document = new Document();
11 | document.setUnits(Units.Millimeters);
12 | expect(document.units).toBe(4);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/__tests__/entities/__snapshots__/entity.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Entity class > should have the default values 1`] = `
4 | "0
5 | LINE
6 | 5
7 | 1
8 | 330
9 | 0
10 | 100
11 | AcDbEntity
12 | 8
13 | 0
14 | 92
15 | 4
16 | 310
17 | test
18 | 1001
19 | XDATA_TEST
20 | 1002
21 | {
22 | 1003
23 | 0
24 | 1002
25 | }"
26 | `;
27 |
--------------------------------------------------------------------------------
/__tests__/entities/__snapshots__/face.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Face class > should cover the face entity 1`] = `
4 | "0
5 | 3DFACE
6 | 5
7 | 1
8 | 330
9 | 0
10 | 100
11 | AcDbEntity
12 | 8
13 | 0
14 | 100
15 | AcDbFace
16 | 10
17 | 0
18 | 20
19 | 0
20 | 30
21 | 0
22 | 11
23 | 10
24 | 21
25 | 0
26 | 31
27 | 0
28 | 12
29 | 10
30 | 22
31 | 10
32 | 32
33 | 0
34 | 13
35 | 0
36 | 23
37 | 10
38 | 33
39 | 0
40 | 70
41 | 0
42 | 0
43 | 3DFACE
44 | 5
45 | 2
46 | 330
47 | 0
48 | 100
49 | AcDbEntity
50 | 8
51 | 0
52 | 100
53 | AcDbFace
54 | 10
55 | 0
56 | 20
57 | 0
58 | 30
59 | 10
60 | 11
61 | 10
62 | 21
63 | 0
64 | 31
65 | 10
66 | 12
67 | 10
68 | 22
69 | 10
70 | 32
71 | 10
72 | 13
73 | 10
74 | 23
75 | 10
76 | 33
77 | 10
78 | 70
79 | 0
80 | 0
81 | 3DFACE
82 | 5
83 | 3
84 | 330
85 | 0
86 | 100
87 | AcDbEntity
88 | 8
89 | 0
90 | 100
91 | AcDbFace
92 | 10
93 | 0
94 | 20
95 | 0
96 | 30
97 | 5
98 | 11
99 | 10
100 | 21
101 | 0
102 | 31
103 | 5
104 | 12
105 | 10
106 | 22
107 | 10
108 | 32
109 | 5
110 | 13
111 | 0
112 | 23
113 | 10
114 | 33
115 | 5
116 | 70
117 | 4"
118 | `;
119 |
--------------------------------------------------------------------------------
/__tests__/entities/__snapshots__/line.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Line class > should create a line entity 1`] = `
4 | "0
5 | LINE
6 | 5
7 | 1
8 | 330
9 | 0
10 | 100
11 | AcDbEntity
12 | 8
13 | 0
14 | 100
15 | AcDbLine
16 | 39
17 | 0
18 | 10
19 | 0
20 | 20
21 | 0
22 | 30
23 | 0
24 | 11
25 | 100
26 | 21
27 | 100
28 | 31
29 | 0
30 | 210
31 | 0
32 | 220
33 | 0
34 | 230
35 | 1"
36 | `;
37 |
--------------------------------------------------------------------------------
/__tests__/entities/__snapshots__line.test.ts.snap:
--------------------------------------------------------------------------------
1 | 0
2 | LINE
3 | 5
4 | 4
5 | 330
6 | 1
7 | 100
8 | AcDbEntity
9 | 8
10 | 0
11 | 100
12 | AcDbLine
13 | 39
14 | 0
15 | 10
16 | 0
17 | 20
18 | 0
19 | 30
20 | 0
21 | 11
22 | 100
23 | 21
24 | 100
25 | 31
26 | 0
27 | 210
28 | 0
29 | 220
30 | 0
31 | 230
32 | 1
--------------------------------------------------------------------------------
/__tests__/entities/dimension/__snapshots__/arc.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`ArcDimension class > should create a line entity 1`] = `
4 | "0
5 | ARC_DIMENSION
6 | 5
7 | 1
8 | 330
9 | 0
10 | 100
11 | AcDbEntity
12 | 8
13 | 0
14 | 100
15 | AcDbDimension
16 | 70
17 | 0
18 | 71
19 | 5
20 | 210
21 | 0
22 | 220
23 | 0
24 | 230
25 | 1
26 | 100
27 | AcDbArcDimension
28 | 13
29 | 10
30 | 23
31 | 0
32 | 33
33 | 0
34 | 14
35 | 0
36 | 24
37 | 10
38 | 34
39 | 0
40 | 15
41 | 0
42 | 25
43 | 0
44 | 35
45 | 0
46 | 40
47 | 0
48 | 41
49 | 0
50 | 70
51 | 0
52 | 71
53 | 0"
54 | `;
55 |
--------------------------------------------------------------------------------
/__tests__/entities/dimension/arc.test.ts:
--------------------------------------------------------------------------------
1 | import { ArcDimension, ArcDimensionOptions } from "@/entities";
2 | import { Seeder, TagsManager, point } from "@/utils";
3 |
4 | describe("ArcDimension class", () => {
5 | it("should create a line entity", () => {
6 | const options: ArcDimensionOptions = {
7 | center: point(),
8 | startPoint: point(10),
9 | endPoint: point(0, 10),
10 | seeder: new Seeder(),
11 | };
12 | const arc = new ArcDimension(options);
13 | const mg = new TagsManager();
14 | arc.tagify(mg);
15 | expect(mg.stringify()).toMatchSnapshot();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/__tests__/entities/entities.test.ts:
--------------------------------------------------------------------------------
1 | import { Blocks, Entities, Seeder, Tables, TagsManager } from "@/index";
2 |
3 | describe("Entities class", () => {
4 | const seeder = new Seeder();
5 | it("should create an empty entities section", () => {
6 | const options = { seeder };
7 | const blocks = new Blocks({
8 | ...options,
9 | tables: new Tables(options),
10 | });
11 | const entities = new Entities({ ...options, blocks });
12 | const mg = new TagsManager();
13 | entities.tagify(mg);
14 | expect(mg.stringify()).toBe("0\nSECTION\n2\nENTITIES\n0\nENDSEC");
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/__tests__/entities/entity.test.ts:
--------------------------------------------------------------------------------
1 | import { Entity, Seeder, TagsManager } from "@/index";
2 |
3 | class DummyEntity extends Entity {
4 | override get subClassMarker(): string | undefined {
5 | return;
6 | }
7 |
8 | constructor() {
9 | super({ seeder: new Seeder() });
10 | this._type = "LINE";
11 | }
12 |
13 | protected override tagifyChild(): void {}
14 | }
15 |
16 | describe("Entity class", () => {
17 | it("should have the default values", () => {
18 | const dummy = new DummyEntity();
19 | const xdata = dummy.addXData("XDATA_TEST");
20 | xdata.layerName("0");
21 | const mg = new TagsManager();
22 | dummy.tagify(mg);
23 | dummy.proxyEntityGraphics = "test";
24 | mg.clear();
25 | dummy.tagify(mg);
26 | expect(mg.stringify()).toMatchSnapshot();
27 | });
28 |
29 | it("should return the visibility", () => {
30 | const dummy = new DummyEntity();
31 | expect(dummy.visibility).toBeUndefined();
32 | dummy.visible = false;
33 | expect(dummy.visibility).toBe(1);
34 | dummy.visible = true;
35 | expect(dummy.visibility).toBe(0);
36 | });
37 |
38 | it("should return existing defined application", () => {
39 | const dummy = new DummyEntity();
40 | const reactors = dummy.addAppDefined("ACAD_REACTORS");
41 | const xdictionary = dummy.addAppDefined("ACAD_XDICTIONARY");
42 | expect(reactors.name).toBe("ACAD_REACTORS");
43 | expect(xdictionary.name).toBe("ACAD_XDICTIONARY");
44 | expect(reactors).toBe(dummy.reactors);
45 | expect(xdictionary).toBe(dummy.xdictionary);
46 | });
47 |
48 | it("should create new defined application", () => {
49 | const dummy = new DummyEntity();
50 | const test = dummy.addAppDefined("ACAD_TEST");
51 | expect(test.name).toBe("ACAD_TEST");
52 | });
53 |
54 | it("should create new XData", () => {
55 | const dummy = new DummyEntity();
56 | const xdata = dummy.addXData("XDATA_TEST");
57 | expect(xdata.name).toBe("XDATA_TEST");
58 | const test = dummy.addXData("XDATA_TEST");
59 | expect(test).toBe(xdata);
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/__tests__/entities/face.test.ts:
--------------------------------------------------------------------------------
1 | import { Face, InvisibleEdge } from "@/entities";
2 | import { Seeder, TagsManager, point } from "@/utils";
3 |
4 | describe("Face class", () => {
5 | it("should cover the face entity", () => {
6 | const mg = new TagsManager();
7 | const seeder = new Seeder();
8 | const face1 = new Face({
9 | first: point(),
10 | second: point(10),
11 | third: point(10, 10),
12 | fourth: point(0, 10),
13 | seeder,
14 | });
15 | face1.tagify(mg);
16 | const face2 = new Face({
17 | first: point(0, 0, 10),
18 | second: point(10, 0, 10),
19 | third: point(10, 10, 10),
20 | seeder,
21 | });
22 | face2.tagify(mg);
23 |
24 | const face3 = new Face({
25 | first: point(0, 0, 5),
26 | second: point(10, 0, 5),
27 | third: point(10, 10, 5),
28 | fourth: point(0, 10, 5),
29 | flags: InvisibleEdge.Third,
30 | seeder,
31 | });
32 | face3.tagify(mg);
33 | expect(mg.stringify()).toMatchSnapshot();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/__tests__/entities/line.test.ts:
--------------------------------------------------------------------------------
1 | import { Line, Seeder, TagsManager, point } from "@/index";
2 |
3 | describe("Line class", () => {
4 | it("should create a line entity", () => {
5 | const line = new Line({
6 | start: point(),
7 | end: point(100, 100),
8 | seeder: new Seeder(),
9 | });
10 | const mg = new TagsManager();
11 | line.tagify(mg);
12 | expect(mg.stringify()).toMatchSnapshot();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/__tests__/entities/manager.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BlockRecordEntry,
3 | EntitiesManager,
4 | Seeder,
5 | TagsManager,
6 | point,
7 | } from "@/index";
8 |
9 | describe("EntitiesManager class", () => {
10 | const seeder = new Seeder();
11 | const blockRecord = new BlockRecordEntry({ name: "*Model_Space", seeder });
12 | it("should create an empty varaible", () => {
13 | const mg = new EntitiesManager({ blockRecord, seeder });
14 | const m = new TagsManager();
15 | mg.tagify(m);
16 | expect(m.stringify()).toBe("");
17 | });
18 |
19 | it("should be able to add a line entity", () => {
20 | const mg = new EntitiesManager({ blockRecord, seeder });
21 | mg.addLine({
22 | start: point(),
23 | end: point(100, 100),
24 | });
25 | const m = new TagsManager();
26 | mg.tagify(m);
27 | expect(m.stringify()).toMatchFileSnapshot("__snapshots__line.test.ts.snap");
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/__tests__/entities/polyline.test.ts:
--------------------------------------------------------------------------------
1 | import { Polyline, PolylineFlags, VertexFlags } from "@/entities";
2 | import { Seeder, TagsManager } from "@/utils";
3 |
4 | const polyfaceVertices = [
5 | [0, 0, 0],
6 | [1, 0, 0],
7 | [1, 1, 0],
8 | [0, 1, 0],
9 | [0, 0, 1],
10 | [1, 0, 1],
11 | [1, 1, 1],
12 | [0, 1, 1],
13 | ];
14 |
15 | const polyfaceFaces = [
16 | [0, 3, 2, 1],
17 | [1, 2, 6, 5],
18 | [3, 7, 6, 2],
19 | [0, 4, 7, 3],
20 | [0, 1, 5, 4],
21 | [4, 5, 6, 7],
22 | ];
23 |
24 | const polyline2DVertices = [
25 | [0, 0, 0],
26 | [2, 0, 0],
27 | [2, 2, 0],
28 | [0, 2, 0],
29 | ];
30 |
31 | const polyline3DVertices = [
32 | [0, 0, 1],
33 | [2, 0, 1],
34 | [2, 2, 1],
35 | [0, 2, 1],
36 | ];
37 |
38 | describe("Polyline class", () => {
39 | it("should cover the polyline entity", () => {
40 | const mg = new TagsManager();
41 | const seeder = new Seeder();
42 | const polyface = new Polyline({
43 | flags: PolylineFlags.PolyfaceMesh,
44 | seeder,
45 | });
46 |
47 | const vertexFlags =
48 | VertexFlags.PolyfaceMeshVertex | VertexFlags.Polyline3DMesh;
49 |
50 | polyfaceVertices.forEach((vertex) => {
51 | const [x, y, z] = vertex;
52 | polyface.add({ x, y, z, flags: vertexFlags });
53 | });
54 |
55 | polyfaceFaces.forEach((indices) => {
56 | polyface.add({
57 | indices: indices.map((i) => i + 1),
58 | flags: VertexFlags.PolyfaceMeshVertex,
59 | faceRecord: true,
60 | });
61 | });
62 |
63 | polyface.tagify(mg);
64 |
65 | const polyline2D = new Polyline({ seeder });
66 | polyline2DVertices.forEach((vertex) => {
67 | const [x, y, z] = vertex;
68 | polyline2D.add({ x, y, z });
69 | });
70 |
71 | polyline2D.tagify(mg);
72 |
73 | const polyline3D = new Polyline({
74 | flags: PolylineFlags.Polyline3D,
75 | seeder,
76 | });
77 | polyline3DVertices.forEach((vertex) => {
78 | const [x, y, z] = vertex;
79 | polyline3D.add({ x, y, z, flags: VertexFlags.Polyline3DVertex });
80 | });
81 |
82 | polyline3D.tagify(mg);
83 | expect(mg.stringify()).toMatchSnapshot();
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/__tests__/header/__snapshots__/header.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Header class > should be able to add a variable 1`] = `
4 | "0
5 | SECTION
6 | 2
7 | HEADER
8 | 9
9 | $ACADVER
10 | 1
11 | AC1021
12 | 9
13 | $HANDSEED
14 | 5
15 | 1
16 | 9
17 | $ANGDIR
18 | 70
19 | 0
20 | 0
21 | ENDSEC"
22 | `;
23 |
24 | exports[`Header class > should create a header section with defaults 1`] = `
25 | "0
26 | SECTION
27 | 2
28 | HEADER
29 | 9
30 | $ACADVER
31 | 1
32 | AC1021
33 | 9
34 | $HANDSEED
35 | 5
36 | 1
37 | 0
38 | ENDSEC"
39 | `;
40 |
--------------------------------------------------------------------------------
/__tests__/header/header.test.ts:
--------------------------------------------------------------------------------
1 | import { Header, Seeder, TagsManager } from "@/index";
2 |
3 | describe("Header class", () => {
4 | it("should create a header section with defaults", () => {
5 | const header = new Header({ seeder: new Seeder() });
6 | const mg = new TagsManager();
7 | header.tagify(mg);
8 | expect(mg.stringify()).toMatchSnapshot();
9 | });
10 |
11 | it("should be able to add a variable", () => {
12 | const header = new Header({ seeder: new Seeder() });
13 | expect(header.exists("$ANGDIR")).toBeFalsy();
14 | const angleDirection = header.add("$ANGDIR");
15 | angleDirection.add(70, 0);
16 | expect(header.exists("$ANGDIR")).toBeTruthy();
17 | const test = header.add("$ANGDIR");
18 | expect(test).toBe(angleDirection);
19 | const mg = new TagsManager();
20 | header.tagify(mg);
21 | expect(mg.stringify()).toMatchSnapshot();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/__tests__/header/variable.test.ts:
--------------------------------------------------------------------------------
1 | import { TagsManager, Variable } from "@/index";
2 |
3 | describe("Variable class", () => {
4 | it("should create an empty variable", () => {
5 | const version = new Variable("$ACADVER");
6 | const mg = new TagsManager();
7 | version.tagify(mg);
8 | expect(mg.stringify()).toBe("");
9 | });
10 |
11 | it("should be able to add a value", () => {
12 | const version = new Variable("$ACADVER");
13 | version.add(1, "AC1021");
14 | const mg = new TagsManager();
15 | version.tagify(mg);
16 | expect(mg.stringify()).toBe("9\n$ACADVER\n1\nAC1021");
17 | });
18 |
19 | it("should be able to clear the values", () => {
20 | const version = new Variable("$ACADVER");
21 | version.add(1, "AC1021");
22 | version.clear();
23 | const mg = new TagsManager();
24 | version.tagify(mg);
25 | expect(mg.stringify()).toBe("");
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/__tests__/helpers/angles.test.ts:
--------------------------------------------------------------------------------
1 | import { HALF_PI, PI, TOW_PI, angleBetween, calculateAngle } from "@/helpers";
2 | import { point2d } from "@/utils";
3 |
4 | describe("between function", () => {
5 | it("should return the correct value", () => {
6 | expect(angleBetween(45, 0, 90)).toBeTruthy();
7 | expect(angleBetween(45, 90, 0)).toBeFalsy();
8 | expect(angleBetween(45, 350, 90)).toBeTruthy();
9 | expect(angleBetween(-10, -90, 90)).toBeTruthy();
10 | expect(angleBetween(-10, 90, -30)).toBeFalsy();
11 |
12 | expect(angleBetween(HALF_PI, 0, TOW_PI, true)).toBeTruthy();
13 | expect(angleBetween(HALF_PI + TOW_PI, 0, TOW_PI, true)).toBeTruthy();
14 | });
15 | });
16 |
17 | describe("calculateAngle function", () => {
18 | it("should calculate correct value", () => {
19 | expect(calculateAngle(point2d(), point2d(-100, 0))).toBe(PI);
20 | expect(calculateAngle(point2d(), point2d(100, 0))).toBe(0);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/__tests__/helpers/primitives/arc.test.ts:
--------------------------------------------------------------------------------
1 | import { ArcPrimitive } from "@/helpers";
2 | import { Writer } from "@/writer";
3 | import { point2d } from "@/utils";
4 |
5 | describe("ArcPrimitive class", () => {
6 | it("should create an arc from 3 points", () => {
7 | const result = new ArcPrimitive({
8 | center: point2d(),
9 | radius: 100,
10 | startAngle: 0,
11 | endAngle: 180,
12 | });
13 |
14 | const from3Points = ArcPrimitive.from3Points;
15 |
16 | const arc1 = from3Points(point2d(100), point2d(0, 100), point2d(-100));
17 | const arc2 = from3Points(point2d(-100), point2d(0, 100), point2d(100));
18 | const arc3 = from3Points(point2d(100), point2d(), point2d(-100));
19 |
20 | expect(arc1).toEqual(result.cw.ccw.ccw);
21 | expect(arc2).toEqual(result.ccw.cw.cw);
22 | expect(arc3).toBeNull();
23 |
24 | const writer1 = new Writer();
25 | arc1?.write(writer1.document.modelSpace);
26 |
27 | const writer2 = new Writer();
28 | arc2?.write(writer2.document.modelSpace);
29 |
30 | expect(writer1.stringify()).toBe(writer2.stringify());
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/__tests__/helpers/primitives/line.test.ts:
--------------------------------------------------------------------------------
1 | import { LinePrimitive } from "@/helpers";
2 | import { Writer } from "@/writer";
3 | import { point } from "@/utils";
4 |
5 | describe("LinePrimitive class", () => {
6 | it("should cover all code", () => {
7 | const line = new LinePrimitive(point(), point(100));
8 |
9 | const writer1 = new Writer();
10 | line.write(writer1.document.modelSpace);
11 |
12 | const writer2 = new Writer();
13 | writer2.document.modelSpace.addLine({
14 | start: point(),
15 | end: point(100),
16 | });
17 |
18 | expect(writer1.stringify()).toBe(writer2.stringify());
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/__tests__/objects/__snapshots__/objects.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Objects class > should create an objects section 1`] = `
4 | "0
5 | SECTION
6 | 2
7 | OBJECTS
8 | 0
9 | DICTIONARY
10 | 5
11 | 1
12 | 330
13 | 0
14 | 100
15 | AcDbDictionary
16 | 281
17 | 1
18 | 3
19 | ACAD_GROUP
20 | 350
21 | 2
22 | 0
23 | DICTIONARY
24 | 5
25 | 2
26 | 330
27 | 1
28 | 100
29 | AcDbDictionary
30 | 281
31 | 0
32 | 0
33 | ENDSEC"
34 | `;
35 |
--------------------------------------------------------------------------------
/__tests__/objects/object.test.ts:
--------------------------------------------------------------------------------
1 | import { Seeder, XObject } from "@/index";
2 |
3 | class DummyObject extends XObject {
4 | constructor() {
5 | super({ seeder: new Seeder(), type: "DICTIONARY" });
6 | }
7 | }
8 |
9 | describe("XObject class", () => {
10 | it("should return existing application defined", () => {
11 | const dummy = new DummyObject();
12 | const reactors = dummy.addAppDefined("ACAD_REACTORS");
13 | expect(reactors).toBe(dummy.reactors);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/__tests__/objects/objects.test.ts:
--------------------------------------------------------------------------------
1 | import { Objects, Seeder, TagsManager } from "@/index";
2 |
3 | describe("Objects class", () => {
4 | it("should create an objects section", () => {
5 | const objects = new Objects({ seeder: new Seeder() });
6 | const mg = new TagsManager();
7 | objects.tagify(mg);
8 | expect(mg.stringify()).toMatchSnapshot();
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/__tests__/tables/entry.test.ts:
--------------------------------------------------------------------------------
1 | import { Entry, Seeder } from "@/index";
2 |
3 | class DummyEntry extends Entry {
4 | constructor() {
5 | super({
6 | seeder: new Seeder(),
7 | type: "DUMMY",
8 | });
9 | }
10 | }
11 |
12 | describe("Entry class", () => {
13 | it("should have reactors and xdictionary", () => {
14 | const dummy = new DummyEntry();
15 | const reactors = dummy.reactors;
16 | const xdictionary = dummy.xdictionary;
17 |
18 | const foundReactors = dummy.addAppDefined("ACAD_REACTORS");
19 | const foundXDictionary = dummy.addAppDefined("ACAD_XDICTIONARY");
20 |
21 | expect(reactors).toBe(foundReactors);
22 | expect(xdictionary).toBe(foundXDictionary);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/__tests__/tables/tables.test.ts:
--------------------------------------------------------------------------------
1 | import { Seeder, Tables, TagsManager } from "@/index";
2 |
3 | describe("Tables class", () => {
4 | it("should create a tables section", () => {
5 | const tables = new Tables({ seeder: new Seeder() });
6 | tables.addLType({
7 | name: "DASHDOT",
8 | descriptive: "__ . ",
9 | elements: [1, 1, -1, 0, -1],
10 | });
11 | const mg = new TagsManager();
12 | tables.tagify(mg);
13 | expect(mg.stringify()).toMatchSnapshot();
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/__tests__/utils/application.test.ts:
--------------------------------------------------------------------------------
1 | import { AppDefined, TagsManager } from "@/index";
2 |
3 | describe("AppDefined class", () => {
4 | it("should create an empty application", () => {
5 | const application = new AppDefined("ACAD_REACTORS");
6 | const mg = new TagsManager();
7 | application.tagify(mg);
8 | expect(mg.stringify()).toBe("");
9 | });
10 |
11 | it("should be able to add a tag value", () => {
12 | const application = new AppDefined("ACAD_REACTORS");
13 | application.add(330, "A");
14 | const mg = new TagsManager();
15 | application.tagify(mg);
16 | expect(mg.stringify()).toBe("102\n{ACAD_REACTORS\n330\nA\n102\n}");
17 | });
18 |
19 | it("should be able to clear the tag values", () => {
20 | const application = new AppDefined("ACAD_REACTORS");
21 | application.add(330, "A");
22 | application.clear();
23 | const mg = new TagsManager();
24 | application.tagify(mg);
25 | expect(mg.stringify()).toBe("");
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/__tests__/utils/bbox.test.ts:
--------------------------------------------------------------------------------
1 | import { BBox, bbox, point } from "@/index";
2 |
3 | describe("BBox class", () => {
4 | it("should bbox of a line", () => {
5 | const _bbox = bbox(point(), point(10, 10, 10));
6 | expect(BBox.line(point(), point(10, 10, 10))).toEqual(_bbox);
7 | expect(BBox.line(point(10, 10, 10), point())).toEqual(_bbox);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/__tests__/utils/color.test.ts:
--------------------------------------------------------------------------------
1 | import { TrueColor } from "@/utils";
2 |
3 | describe("TrueColor class", () => {
4 | it("should return the correct true color value", () => {
5 | expect(TrueColor.fromRGB(200, 100, 50)).toBe(13132850);
6 | expect(TrueColor.fromRGB(1, 100, 50)).toBe(91186);
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/__tests__/utils/seeder.test.ts:
--------------------------------------------------------------------------------
1 | import { Seeder } from "@/utils";
2 |
3 | describe("Seeder class", () => {
4 | const seeder = new Seeder();
5 | it("should return the next handle", () => {
6 | expect(seeder.next()).toBe("1");
7 | expect(seeder.next()).toBe("2");
8 | expect(seeder.next()).toBe("3");
9 | });
10 |
11 | it("should return the next handle without increment", () => {
12 | expect(seeder.peek()).toBe("4");
13 | expect(seeder.peek()).toBe("4");
14 | expect(seeder.peek()).toBe("4");
15 | });
16 |
17 | it("should return the correct hex value", () => {
18 | const seeder = new Seeder();
19 | for (let i = 0; i < 42; i++) seeder.next();
20 | expect(seeder.next()).toBe("2B");
21 | expect(seeder.next()).toBe("2C");
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/__tests__/utils/tags.test.ts:
--------------------------------------------------------------------------------
1 | import { TagsManager, point, point2d } from "@/index";
2 |
3 | describe("TagsManager class", () => {
4 | it("should create an empty mg", () => {
5 | const mg = new TagsManager();
6 | expect(mg.stringify()).toBe("");
7 | });
8 |
9 | it("should clear tag values", () => {
10 | const mg = new TagsManager();
11 | mg.add(0, "SECTION");
12 | mg.add(0);
13 | mg.clear();
14 | expect(mg.stringify()).toBe("");
15 | });
16 |
17 | it("should push tag values", () => {
18 | const mg = new TagsManager();
19 | mg.add(0, "SECTION");
20 | mg.add(0);
21 | expect(mg.stringify()).toBe("0\nSECTION");
22 | });
23 |
24 | it("should start a section", () => {
25 | const mg = new TagsManager();
26 | mg.sectionStart("HEADER");
27 | expect(mg.stringify()).toBe("0\nSECTION\n2\nHEADER");
28 | });
29 |
30 | it("should end a section", () => {
31 | const mg = new TagsManager();
32 | mg.sectionStart("HEADER");
33 | mg.sectionEnd();
34 | expect(mg.stringify()).toBe("0\nSECTION\n2\nHEADER\n0\nENDSEC");
35 | });
36 |
37 | it("should add 2d point values", () => {
38 | const mg = new TagsManager();
39 | mg.point2d(point2d());
40 | expect(mg.stringify()).toBe("10\n0\n20\n0");
41 | mg.clear();
42 | mg.point2d(point2d(), 1);
43 | expect(mg.stringify()).toBe("11\n0\n21\n0");
44 | mg.clear();
45 | mg.point2d();
46 | expect(mg.stringify()).toBe("");
47 | });
48 |
49 | it("should add 3d point values", () => {
50 | const mg = new TagsManager();
51 | mg.point(point());
52 | expect(mg.stringify()).toBe("10\n0\n20\n0\n30\n0");
53 | mg.clear();
54 | mg.point(point(), 1);
55 | expect(mg.stringify()).toBe("11\n0\n21\n0\n31\n0");
56 | mg.clear();
57 | mg.point();
58 | expect(mg.stringify()).toBe("");
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/__tests__/utils/text.test.ts:
--------------------------------------------------------------------------------
1 | import { Colors, StyledText, TextBuilder } from "@/index";
2 |
3 | describe("StyledText class", () => {
4 | it("should create a styled text", () => {
5 | const styledText = new StyledText();
6 | styledText.add({
7 | value: "Hello World!",
8 | fontFamily: "Arial",
9 | italic: true,
10 | colorNumber: Colors.Red,
11 | underline: true,
12 | center: true,
13 | bold: true,
14 | });
15 | expect(styledText.value).toBe("{\\L\\C1;\\fArial|b1|i1|c1;Hello World!}");
16 | styledText.paragraph = true;
17 | expect(styledText.value).toBe(
18 | "{\\L\\C1;\\fArial|b1|i1|c1;Hello World!}\\P"
19 | );
20 | });
21 |
22 | it("should return an empty string", () => {
23 | const styledText = new StyledText();
24 | styledText.add({
25 | value: "",
26 | fontFamily: "Arial",
27 | italic: true,
28 | colorNumber: Colors.Red,
29 | underline: true,
30 | center: true,
31 | bold: true,
32 | });
33 | expect(styledText.value).toBe("");
34 | });
35 | });
36 |
37 | describe("XTextBuilder class", () => {
38 | it("should create a text builder", () => {
39 | const builder = new TextBuilder();
40 | const p1 = builder.add(
41 | {
42 | value: "Hello",
43 | fontFamily: "Arial",
44 | italic: true,
45 | },
46 | true
47 | );
48 | p1.add({
49 | value: " Hello",
50 | colorNumber: Colors.Red,
51 | });
52 | const p2 = builder.add({
53 | value: "Hello",
54 | italic: true,
55 | colorNumber: Colors.Green,
56 | });
57 | p2.add({
58 | value: " Hello",
59 | colorNumber: Colors.Yellow,
60 | });
61 | expect(builder.value).toBe(
62 | "{\\fArial|i1;Hello\\C1; Hello}\\P{\\C3;|i1;Hello\\C2; Hello}"
63 | );
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitepress";
2 | import { version } from "../../package.json";
3 |
4 | export default defineConfig({
5 | title: "@tarikjabiri/dxf",
6 | description: "A JavaScript dxf generator written in TypeScript.",
7 | lastUpdated: true,
8 | head: [
9 | ["meta", { name: "theme-color", content: "#4caf50" }],
10 | ["link", { rel: "icon", href: "/logo.svg", type: "image/svg+xml" }],
11 | ["script", { defer: "", src: "/_vercel/insights/script.js" }],
12 | ],
13 | markdown: {
14 | theme: {
15 | light: "github-light",
16 | dark: "github-dark",
17 | },
18 | },
19 | themeConfig: {
20 | logo: "/logo.svg",
21 | nav: [
22 | {
23 | text: `v${version}`,
24 | items: [
25 | {
26 | text: "Releases ",
27 | link: "https://github.com/tarikjabiri/dxf/releases",
28 | },
29 | {
30 | text: "npm ",
31 | link: "https://www.npmjs.com/package/@tarikjabiri/dxf",
32 | },
33 | ],
34 | },
35 | ],
36 | socialLinks: [
37 | { icon: "github", link: "https://github.com/dxfjs/writer" },
38 | { icon: "slack", link: "https://dxfjs.slack.com" },
39 | ],
40 | algolia: {
41 | appId: "VYHQL6H1FK",
42 | apiKey: "d12995c0f160c81f0fb1bf6138648503",
43 | indexName: "dxf",
44 | },
45 | sidebar: {
46 | "/": sidebar(),
47 | "/v2/": v2SideBar(),
48 | },
49 | footer: {
50 | message: "Released under the MIT License.",
51 | copyright: "Copyright © 2021-present Tarik EL JABIRI",
52 | },
53 | editLink: {
54 | pattern: "https://github.com/dxfjs/writer/edit/main/docs/:path",
55 | text: "Edit this page on GitHub",
56 | },
57 | },
58 | });
59 |
60 | function sidebar() {
61 | return [
62 | {
63 | text: "Introduction",
64 | collapsed: false,
65 | items: [{ text: "Get started", link: "/start" }],
66 | },
67 | {
68 | text: "Sections",
69 | collapsed: false,
70 | items: [
71 | { text: "Header", link: "/sections/header" },
72 | { text: "Entities", link: "/sections/entities" },
73 | ],
74 | },
75 | {
76 | text: "Guides",
77 | collapsed: false,
78 | items: [
79 | { text: "Lineweights", link: "/guides/lineweights" },
80 | { text: "MText", link: "/guides/mtext" },
81 | ],
82 | },
83 | ];
84 | }
85 |
86 | function v2SideBar() {
87 | return [
88 | {
89 | text: "Guide",
90 | items: [
91 | { text: "Introduction", link: "/v2/guide/" },
92 | { text: "Header", link: "/v2/guide/header" },
93 | { text: "Tables", link: "/v2/guide/tables" },
94 | { text: "Blocks", link: "/v2/guide/blocks" },
95 | { text: "Entities", link: "/v2/guide/entities" },
96 | { text: "Objects", link: "/v2/guide/objects" },
97 | ],
98 | },
99 | {
100 | text: "Tutoriels",
101 | items: [{ text: "About Extended Data", link: "/v2/tutoriels/xdata" }],
102 | },
103 | ];
104 | }
105 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | import DefaultTheme from 'vitepress/theme';
2 | import './main.css';
3 | export default DefaultTheme;
4 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/main.css:
--------------------------------------------------------------------------------
1 | iframe {
2 | border-radius: 8px;
3 | }
4 |
5 | .content img {
6 | border-radius: 10px;
7 | margin: auto;
8 | }
9 |
--------------------------------------------------------------------------------
/docs/guides/lineweights.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Lineweights
6 |
7 | The `lineweight` property represents the lineweight as integer value in `millimeters * 100`, e.g. 0.25mm = 25, independently from the unit system used in the DXF document.
8 |
9 | Only certain values are valid: `0`, `5`, `9`, `13`, `15`, `18`, `20`, `25`, `30`, `35`, `40`, `50`, `53`, `60`, `70`, `80`, `90`, `100`, `106`, `120`, `140`, `158`, `200`, `211`.
10 |
11 | Values < 0 have a special meaning:
12 |
13 | | Value | Meaning |
14 | | ------ | :----------------- |
15 | | -1 | LINEWEIGHT_BYLAYER |
16 | | -2 | LINEWEIGHT_BYBLOCK |
17 | | -3 | LINEWEIGHT_DEFAULT |
18 |
19 | ## Example
20 | ```ts
21 | import { Writer, point } from "@tarikjabiri/dxf";
22 |
23 | const writer = new Writer();
24 | const modelSpace = writer.document.modelSpace;
25 |
26 | modelSpace.addLine({
27 | start: point(),
28 | end: point(100, 100),
29 | lineWeight: 100,
30 | });
31 | ```
32 |
33 | :::tip
34 | By default the lineweight is not displayed, to display it use:
35 |
36 | ```ts
37 | writer.document.header.add("$LWDISPLAY").add(290, 1);
38 | ```
39 | :::
40 |
--------------------------------------------------------------------------------
/docs/guides/mtext.md:
--------------------------------------------------------------------------------
1 | # MText guide
2 |
3 | ## Adding a `MTEXT` entity
4 |
5 | The `MTEXT` entity can be added to any layout (modelspace, paperspace or block) by the `addMText()` method.
6 |
7 | ```ts
8 | modelSpace.addMText({
9 | height: 10,
10 | insertionPoint: point(15, 11),
11 | value: "Hello World!",
12 | });
13 | ```
14 |
15 | ## XTextBuilder class
16 |
17 | The `XTextBuilder` provide an esay interface to build `MText content`.
18 |
19 | ```ts
20 | const builder = new TextBuilder();
21 | const txt = builder.add({
22 | value: "Hello World!",
23 | fontFamily: "Arial",
24 | italic: true,
25 | colorNumber: Colors.Green,
26 | });
27 |
28 | const mtext = modelSpace.addMText({
29 | height: 10,
30 | insertionPoint: point(15, 11),
31 | });
32 |
33 | mtext.value = builder.value;
34 | ```
35 |
36 | To create multiple lines you can mark the text as paragraph.
37 |
38 | ```ts
39 | builder.add(
40 | {
41 | value: "Hello World!",
42 | fontFamily: "Arial",
43 | italic: true,
44 | colorNumber: Colors.Green,
45 | },
46 | true // This will make sure to add a line break.
47 | );
48 |
49 | builder.add({
50 | value: "Hello World!",
51 | fontFamily: "Arial",
52 | italic: true,
53 | colorNumber: Colors.Green,
54 | });
55 | ```
56 |
57 | The result will be.
58 |
59 | ```txt
60 | {\C3;\fArial|i1;Hello World!}\P{\C3;\fArial|i1;Hello World!}
61 | ```
62 |
63 | Or you can access the property `paragraph`.
64 |
65 | ```ts
66 | txt1.paragraph = true;
67 | ```
68 |
69 | You can create and style the text manually.
70 |
71 | ::: tip
72 |
73 | You can find here ([ezdxf](https://ezdxf.readthedocs.io/en/stable/tutorials/mtext.htm)) more informations about `MText`:
74 |
75 | - [MText Inline Codes](https://ezdxf.readthedocs.io/en/stable/dxfentities/mtext.html#mtext-inline-codes).
76 | - [MText formatting](https://ezdxf.readthedocs.io/en/stable/tutorials/mtext.html#mtext-formatting).
77 |
78 | :::
79 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 |
4 | title: "@tarikjabiri/dxf"
5 | titleTemplate: A DXF writer written in TypeScript..
6 |
7 | hero:
8 | name: "@tarikjabiri/dxf"
9 | text: A DXF writer written in TypeScript.
10 | image:
11 | src: /logo.svg
12 | alt: "@tarikjabiri/dxf"
13 | actions:
14 | - theme: brand
15 | text: Get Started
16 | link: /start
17 | - theme: alt
18 | text: View on GitHub
19 | link: https://github.com/dxfjs/writer
20 | ---
21 |
22 |
48 |
49 | Maintainers, Sponsors & Contributors
50 |
51 |
52 |
53 |
54 |
55 |
56 |
63 |
64 |
65 |
72 |
--------------------------------------------------------------------------------
/docs/public/logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/sections/entities.md:
--------------------------------------------------------------------------------
1 | # Entities
2 |
3 | All entities described here can be added to the model space, paper spaces or blocks.
4 |
5 | ```ts
6 | const modelSpace = writer.document.modelSpace;
7 | const paperSpace = writer.document.paperSpace;
8 | const myBlock = writer.document.blocks.addBlock({
9 | name: "myBlock",
10 | });
11 | ```
12 |
13 | ## Line
14 |
15 | ```ts
16 | const line = modelSpace.addLine({
17 | start: point(),
18 | end: point(100, 100),
19 | });
20 | ```
21 |
22 | ## Point
23 |
24 | ```ts
25 | const point = modelSpace.addPoint({
26 | x: 5,
27 | y: 5,
28 | });
29 | ```
30 |
31 | ## Ray
32 |
33 | ```ts
34 | const line = modelSpace.addRay({
35 | start: point(),
36 | unitDirectionVector: point(10, 10),
37 | });
38 | ```
39 |
40 | ## Spline
41 |
42 | ```ts
43 | const spline = modelSpace.addSpline({
44 | controls: [
45 | point(),
46 | point(10, 10),
47 | point(20, 10),
48 | point(30, 20),
49 | point(100, 100),
50 | ],
51 | });
52 | ```
53 |
54 | ## Text
55 |
56 | ```ts
57 | const text = modelSpace.addText({
58 | value: "Hello World!",
59 | firstAlignmentPoint: point(),
60 | height: 10,
61 | });
62 | ```
63 |
--------------------------------------------------------------------------------
/docs/sections/header.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # HEADER Section
6 |
--------------------------------------------------------------------------------
/docs/sections/objects.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Objects
6 |
--------------------------------------------------------------------------------
/docs/sections/tables.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Tables
6 |
--------------------------------------------------------------------------------
/docs/start.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Installation
4 |
5 | ::: code-group
6 |
7 | ```sh [npm]
8 | $ npm install @tarikjabiri/dxf
9 | ```
10 |
11 | ```sh [pnpm]
12 | $ pnpm add @tarikjabiri/dxf
13 | ```
14 |
15 | ```sh [yarn]
16 | $ yarn add @tarikjabiri/dxf
17 | ```
18 |
19 | :::
20 |
21 | ## Quick start
22 |
23 | ```ts
24 | import { Writer, point } from "@tarikjabiri/dxf";
25 |
26 | const writer = new Writer();
27 | const modelSpace = writer.document.modelSpace;
28 |
29 | // Add entites to the model space
30 | modelSpace.addLine({
31 | start: point(),
32 | end: point(100, 100),
33 | // Other options...
34 | });
35 |
36 | // Get the dxf content
37 | const content = writer.stringify();
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/v2/guide/_media/ellipse-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dxfjs/writer/8b895cb23a6197f010a30ef32ddf752b77e0f30e/docs/v2/guide/_media/ellipse-demo.png
--------------------------------------------------------------------------------
/docs/v2/guide/_media/linetype-axes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dxfjs/writer/8b895cb23a6197f010a30ef32ddf752b77e0f30e/docs/v2/guide/_media/linetype-axes.png
--------------------------------------------------------------------------------
/docs/v2/guide/blocks.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Blocks
6 |
7 | Blocks are reusable definitions used by the `INSERT` entity, defined in the `BLOCKS` section.
8 |
9 | By default two blocks are created: `*Model_Space` and `*Paper_Space`.
10 |
11 | :::danger
12 | `INSERT` entity cannot use these tow blocks.
13 | :::
14 |
15 | ## Adding blocks
16 |
17 | To add a block use the convenient function `addBlock()`, it is a factory function that create a block, store it and return a reference to it:
18 |
19 | ```js
20 | import { DxfWriter } from "@tarikjabiri/dxf";
21 | const dxf = new DxfWriter();
22 | const myBlock = dxf.addBlock("myBlock");
23 | ```
24 |
25 | :::warning
26 | Blocks names cannot include the following characters: < > / \ " : ; ? * | = `
27 |
28 | By default these characters are removed from the given name if exists
29 | :::
30 |
31 | ## Adding entities to the block
32 |
33 | To add entities to a block just call the convenient methods to do so:
34 |
35 | ```js
36 | import { DxfWriter, point3d } from "@tarikjabiri/dxf";
37 | const dxf = new DxfWriter();
38 | const myBlock = dxf.addBlock("myBlock");
39 | myBlock.addCircle(point3d(0, 0, 0), 20);
40 | myBlock.addLine(point3d(0, 0, 0), point3d(0, 20, 0));
41 | // and so on ...
42 | ```
43 |
44 | ## Inserting blocks
45 |
46 | To insert a block in the drawing use the convenient method `addInsert()`:
47 |
48 | ```js
49 | import { DxfWriter, point3d } from "@tarikjabiri/dxf";
50 | const dxf = new DxfWriter();
51 | const myBlock = dxf.addBlock("myBlock");
52 | myBlock.addCircle(point3d(0, 0, 0), 20);
53 | myBlock.addLine(point3d(0, 0, 0), point3d(0, 20, 0));
54 |
55 | // Inserting the block
56 | dxf.addInsert(myBlock.name, point3d(0, 0, 0));
57 | ```
58 |
--------------------------------------------------------------------------------
/docs/v2/guide/header.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Header
6 |
7 | ## Variables
8 |
9 | Variables contains settings associated with the drawing, defined in the `HEADER` section.
10 | > All possible [variables](https://help.autodesk.com/view/OARX/2023/ENU/?guid=GUID-A85E8E67-27CD-4C59-BE61-4DC9FADBE74A)
11 |
12 | :::info
13 |
14 | By default these variables are set automatically:
15 |
16 | - `$ACADVER` The AutoCAD drawing database version number: AC1021 = AutoCAD 2007.
17 | - `$HANDSEED` Next available handle.
18 | - `$INSUNITS` Default drawing units for AutoCAD DesignCenter blocks: 0 = Unitless.
19 | - `$VIEWCTR` XY center of current view on screen.
20 | - `$CLAYER` Save the current layer name.
21 | - `$LASTSAVEDBY` Sets to the package name.
22 |
23 | :::
24 |
25 | ## Adding variables
26 |
27 | Each variable is specified by a 9 group code giving the variable's name, followed by groups that supply the variable's value:
28 |
29 | ```txt
30 | 9
31 | $NAME // The name of the variable.
32 | 10 // First group code
33 | 20 // The value assocoated
34 | 20 // Second group code
35 | 20 // The value assocoated
36 | // And so on...
37 |
38 | // These values are random.
39 | ```
40 |
41 | Javascript code :
42 |
43 | ```js
44 | import { DxfWriter } from "@tarikjabiri/dxf";
45 | const dxf = new DxfWriter();
46 | dxf.setVariable("$ATTMODE", { 70: 2 });
47 | // $ATTMODE is the name of the variable
48 | // 70 is the group code
49 | // 2 is the value to be set.
50 | dxf.setVariable("$PLIMMAX", {
51 | 10: 20,
52 | 20: 30,
53 | }); // This variable accept tow group codes and tow values
54 | ```
55 |
56 | :::info
57 |
58 | - The object passed as values is key value paired, the key is the group code and value associated with it.
59 | - If you try to add a variable already added, its values will be updated.
60 |
61 | :::
62 |
63 | ## Setting Units
64 |
65 | To set units use the convenient method `setUnits()`:
66 |
67 | ```js
68 | import { DxfWriter, Units } from '@tarikjabiri/dxf';
69 |
70 | const dxf = new DxfWriter();
71 | dxf.setUnits(Units.Meters);
72 |
73 | ```
--------------------------------------------------------------------------------
/docs/v2/guide/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Getting started
6 |
7 | ## Installation
8 |
9 | :::code-group
10 | ```sh [pnpm]
11 | pnpm add @tarikjabiri/dxf
12 | ```
13 | ```sh [npm]
14 | npm i @tarikjabiri/dxf
15 | ```
16 | ```sh [yarn]
17 | yarn add @tarikjabiri/dxf
18 | ```
19 | :::
20 |
21 | ## Quick start
22 |
23 | ```js
24 | import { DxfWriter, point3d } from "@tarikjabiri/dxf";
25 | const dxf = new DxfWriter();
26 | dxf.addLine(point3d(0, 0), point3d(100, 100));
27 | // To get the dxf string just call the stringify() method
28 | const dxfString = dxf.stringify();
29 | ```
30 | ## Sponsor
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/docs/v2/guide/objects.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Objects
6 |
7 | Objects are the nongraphical objects in the dxf.
8 |
9 | Supported objects are:
10 | [[toc]]
11 |
12 |
13 | ## `DICTIONARY`
14 |
15 | Objects section have a root dictionary, you can get a reference to it like this:
16 | ```js
17 | import { DxfWriter } from "@tarikjabiri/dxf";
18 | const dxf = new DxfWriter();
19 | const root = dxf.objects.root
20 | ```
21 |
22 | The root dictionary is the first object appearing in the section, it can own objects appearing after, also can have multiple entries.
23 |
24 |
25 | ```js
26 | import { DxfWriter } from "@tarikjabiri/dxf";
27 | const dxf = new DxfWriter();
28 | dxf.objects.addEntryToRoot("ACAD_IMAGE_DICT", "1A");
29 | ```
30 |
31 | To add a dictionary use the convenient function `addDictionary()`:
32 |
33 | ```js
34 | import { DxfWriter } from "@tarikjabiri/dxf";
35 | const dxf = new DxfWriter();
36 | const dic = dxf.objects.addDictionary();
37 | // Add an entry to it
38 | dic.addEntry("example", "1B");
39 | ```
40 |
41 | ## `IMAGEDEF`
42 |
43 | This object can store a reference to an external image file, which can be placed by the `IMAGE` entity.
44 |
45 | ```js
46 | import { DxfWriter, ImageDefResolutionUnits } from "@tarikjabiri/dxf";
47 | const dxf = new DxfWriter();
48 | const imgDef = dxf.addImageDef("path/to/image");
49 | // You can customize it with these properties:
50 | imgDef.acadImageDictHandle = ""; // Soft-pointer ID/handle to the ACAD_IMAGE_DICT dictionary.
51 | imgDef.addImageDefReactorHandle("handle"); // Soft-pointer ID/handle to IMAGEDEF_REACTOR object (multiple entries; one for each instance).
52 | imgDef.width = 1; // Image width in pixels.
53 | imgDef.height = 1; // Image height in pixels.
54 | imgDef.widthPixelSize = 1; // Default width of one pixel in AutoCAD units.
55 | imgDef.heightPixelSize = 1; // Default height of one pixel in AutoCAD units.
56 | imgDef.loaded = true; // Image is loaded.
57 | imgDef.resolutionUnits = ImageDefResolutionUnits.NoUnits; // Resolution units.
58 | ```
59 |
60 | The possible values of `ImageDefResolutionUnits`:
61 |
62 | - `ImageDefResolutionUnits.NoUnits` = 0;
63 | - `ImageDefResolutionUnits.Centimeters` = 2;
64 | - `ImageDefResolutionUnits.Inch` = 5;
65 |
66 | :::tip
67 |
68 | For this to work properly in `AutoCAD` you need to add a [`DICTIONARY`](#dictionary-object) object with an entry to image name and the ID/Handle to [`IMAGEDEF`](#imagedef-object) object you added. Also when you place a `IMAGE` entity add a [`IMAGEDEF_REACTOR`](#imagedef_reactor-object) object.
69 |
70 | > But if you use the `addImage()` function all of this will be added automatically. No need to do it manually.
71 |
72 | :::
73 |
74 | ## `IMAGEDEF_REACTOR`
75 |
76 | ```js
77 | import { DxfWriter } from "@tarikjabiri/dxf";
78 | const dxf = new DxfWriter();
79 | const imgDefReactor = dxf.addImageDefReactor("2F"); // Object ID for associated image entity.
80 | ```
81 |
--------------------------------------------------------------------------------
/docs/v2/tutoriels/xdata.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: doc
3 | ---
4 |
5 | # Extended data (XDATA)
6 |
7 | Extended data allows for the inclusion of additional information that is not explicitly defined by the standard DXF entity data, such as custom application-specific data or metadata. It is stored as a list of name-value pairs associated with the entity.
8 |
9 | ## Register application id
10 |
11 | To use XDATA in a DXF file, you need to first register an [APPID](/v2/guide/tables.html#appid) in the tables section of the file:
12 |
13 | ```ts
14 | const appIdTest = dxf.tables.addAppId("TEST_APPID")
15 | ```
16 |
17 | ## Create an entity
18 |
19 | Next create an entity that needs XDATA to be associated with it:
20 |
21 | ```ts
22 | const line = dxf.addLine(point3d(), point3d(100, 100))
23 | ```
24 |
25 | ## Add xdata object
26 |
27 | ```ts
28 | const xdataTest = line.addXData(appIdTest.name)
29 | ```
30 |
31 | ## Add a string
32 | ```ts
33 | xdataTest.string('Test string')
34 | ```
35 |
36 | ## Add a layer name
37 | ```ts
38 | xdataTest.layerName('layer_name')
39 | ```
40 |
41 | :::danger
42 | The layer name should be defined in order to make DXF file valid.
43 |
44 | ```ts
45 | const layerExist = dxf.layer('layer_name')
46 | if (layerExist) {
47 | xdataTest.layerName(layerExist.name)
48 | }
49 | ```
50 | :::
51 |
52 | ## Add a binary data
53 | ```ts
54 | xdataTest.binaryData('.....')
55 | ```
56 |
57 | ## Add a database handle
58 | ```ts
59 | xdataTest.databaseHandle('1A16235')
60 | ```
61 |
62 | ## Add a 3 reals
63 | ```ts
64 | xdataTest.point(point3d(100.2, 100.3, 100))
65 | ```
66 |
67 | ## Add a world space position
68 | ```ts
69 | xdataTest.position(point3d(100.2, 100.3, 100))
70 | ```
71 |
72 | ## Add a world space displacement
73 | ```ts
74 | xdataTest.displacement(point3d(100.2, 100.3, 100))
75 | ```
76 |
77 | ## Add a world direction
78 | ```ts
79 | xdataTest.displacement(point3d(100.2, 100.3, 100))
80 | ```
81 |
82 | ## Add a real
83 | ```ts
84 | xdataTest.real(100.34)
85 | ```
86 |
87 | ## Add a distance
88 | ```ts
89 | xdataTest.distance(1002.56)
90 | ```
91 |
92 | ## Add a scale factor
93 | ```ts
94 | xdataTest.scale(2)
95 | ```
96 |
97 | ## Add a integer
98 | ```ts
99 | xdataTest.integer(209)
100 | ```
101 |
102 | ## Add a long
103 | ```ts
104 | xdataTest.long(27353653)
105 | ```
106 |
--------------------------------------------------------------------------------
/examples/blocks.ts:
--------------------------------------------------------------------------------
1 | import { Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const document = writer.document;
6 | const modelSpace = writer.document.modelSpace;
7 |
8 | const hr5 = document.tables.addStyle({
9 | name: "HR5",
10 | primaryfontFileName: "HR5.SHX",
11 | });
12 |
13 | const blk = document.addBlock({ name: "blk" });
14 | blk.addCircle({ center: point(), radius: 5 });
15 | blk.addLine({ start: point(), end: point(10, 10) });
16 | const attdef = blk.addAttdef({
17 | firstAlignmentPoint: point(12, 10),
18 | height: 2,
19 | tag: "TAG",
20 | value: "",
21 | styleName: hr5.name,
22 | });
23 |
24 | const iblk = modelSpace.addInsert({
25 | blockName: blk.name,
26 | followAttributes: true,
27 | });
28 |
29 | modelSpace.addAttrib({
30 | ...attdef,
31 | insert: iblk,
32 | startPoint: attdef.firstAlignmentPoint,
33 | value: "B 16A",
34 | });
35 |
36 | modelSpace.push(iblk.seqend);
37 |
38 | save(writer.stringify(), fileURLToPath(import.meta.url));
39 |
--------------------------------------------------------------------------------
/examples/color.ts:
--------------------------------------------------------------------------------
1 | import { Colors, TrueColor, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const green = writer.document.tables.addLayer({
8 | name: "Green",
9 | colorNumber: Colors.Green,
10 | trueColor: TrueColor.fromRGB(1, 100, 50),
11 | });
12 |
13 | modelSpace.currentLayerName = green.name;
14 |
15 | modelSpace.addLine({ start: point(), end: point(100, 100) });
16 |
17 | modelSpace.addLine({
18 | start: point(20),
19 | end: point(120, 100),
20 | trueColor: TrueColor.fromRGB(200, 100, 50),
21 | });
22 |
23 | save(writer.stringify(), fileURLToPath(import.meta.url));
24 |
--------------------------------------------------------------------------------
/examples/dimension.ts:
--------------------------------------------------------------------------------
1 | import { Colors, Writer, dline, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | writer.document.header.add("$DIMTXT").add(40, 2.5);
8 |
9 | const green = writer.document.tables.addLayer({
10 | name: "Green",
11 | colorNumber: Colors.Green,
12 | });
13 |
14 | modelSpace.currentLayerName = green.name;
15 |
16 | const aligned = modelSpace.addAlignedDim({
17 | start: point(),
18 | end: point(100, 100),
19 | offset: 10,
20 | dimStyleName: writer.document.tables.dimStyleStandard.options.name
21 | });
22 |
23 | writer.document.renderer.aligned(aligned);
24 |
25 | modelSpace.addLinearDim({
26 | start: point(),
27 | end: point(100, 100),
28 | offset: 10,
29 | angle: 0,
30 | dimStyleName: writer.document.tables.dimStyleStandard.options.name
31 | });
32 |
33 | writer.document.tables.dimStyleStandard.options.DIMTXT = 2.5;
34 |
35 | const dim = modelSpace.addAngularLinesDim({
36 | firstLine: dline(point(0, 2.5), point(10, 2.5)),
37 | secondLine: dline(point(0, 5), point(5, 10)),
38 | positionArc: point(6.988716, 5.042493),
39 | middle: point(6.575676, 6.259268),
40 | measurement: 0.785398,
41 | dimStyleName: writer.document.tables.dimStyleStandard.options.name,
42 | });
43 | writer.document.renderer.angularLines(dim);
44 | writer.document.tables.dimStyleStandard.reactors.add(330, dim.handle);
45 |
46 | modelSpace.addAngularPointsDim({
47 | center: point(),
48 | first: point(20, 10),
49 | second: point(10, 20),
50 | definition: point(14, 14),
51 | middle: point(15, 15),
52 | measurement: 0.6435011087932843,
53 | dimStyleName: writer.document.tables.dimStyleStandard.options.name
54 | });
55 |
56 | modelSpace.addRadialDim({
57 | first: point(170.7107, 70.7107),
58 | leaderLength: 10,
59 | definition: point(100, 0),
60 | });
61 |
62 | modelSpace.addDiameterDim({
63 | first: point(200, 0),
64 | leaderLength: 10,
65 | definition: point(),
66 | });
67 |
68 | save(writer.stringify(), fileURLToPath(import.meta.url));
69 |
--------------------------------------------------------------------------------
/examples/hatch.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Colors,
3 | HatchGradient,
4 | HatchGradientType,
5 | HatchPattern,
6 | HatchPolyline,
7 | Writer,
8 | point,
9 | point2d,
10 | } from "@/index";
11 | import { fileURLToPath, save } from "./utils";
12 |
13 | const writer = new Writer();
14 | const modelSpace = writer.document.modelSpace;
15 |
16 | const cyan = writer.document.tables.addLayer({
17 | name: "Cyan",
18 | colorNumber: Colors.Cyan,
19 | });
20 |
21 | modelSpace.currentLayerName = cyan.name;
22 |
23 | const gradient = new HatchGradient({
24 | first: Colors.Green,
25 | second: Colors.Red,
26 | type: HatchGradientType.Cylinder,
27 | angle: 20,
28 | });
29 |
30 | const pattern = new HatchPattern({
31 | name: "TEST",
32 | });
33 |
34 | pattern.add({
35 | angle: 0,
36 | base: point2d(),
37 | offset: point2d(0, 5),
38 | dashLengths: [2, -3],
39 | });
40 |
41 | pattern.add({
42 | angle: 45,
43 | base: point2d(),
44 | offset: point2d(5, 0),
45 | dashLengths: [4, -1],
46 | });
47 |
48 | const hatch1 = modelSpace.addHatch({
49 | fill: gradient,
50 | });
51 |
52 | const boundary1 = hatch1.add();
53 | boundary1.line({ start: point2d(), end: point(10, 0) });
54 | boundary1.arc({
55 | center: point2d(10, 5),
56 | radius: 5,
57 | start: -90,
58 | end: 90,
59 | });
60 | boundary1.ellipse({
61 | center: point2d(5, 10),
62 | endpoint: point2d(5),
63 | ratio: 0.5,
64 | start: 0,
65 | end: 180,
66 | });
67 | boundary1.line({ start: point(0, 10), end: point2d() });
68 |
69 | const hatch2 = modelSpace.addHatch({
70 | fill: pattern,
71 | });
72 |
73 | const hatchPolyline = new HatchPolyline({ isClosed: true });
74 | hatchPolyline.add(point(20));
75 | hatchPolyline.add(point(40));
76 | hatchPolyline.add(point(40, 20));
77 | hatchPolyline.add(point(20, 20));
78 | hatch2.add().polyline(hatchPolyline);
79 |
80 | save(writer.stringify(), fileURLToPath(import.meta.url));
81 |
--------------------------------------------------------------------------------
/examples/index.ts:
--------------------------------------------------------------------------------
1 | import "./blocks";
2 | import "./color";
3 | import "./dimension";
4 | import "./hatch";
5 | import "./leader";
6 | import "./lwpolyline";
7 | import "./mleader";
8 | import "./mesh";
9 | import "./mtext";
10 | import "./paper-space";
11 | import "./polyline";
12 | import "./quick-start";
13 | import "./rectangle";
14 | import "./svg";
15 | import "./table";
16 | import "./text";
17 |
--------------------------------------------------------------------------------
/examples/leader.ts:
--------------------------------------------------------------------------------
1 | import { Colors, PathType, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const cyan = writer.document.tables.addLayer({
8 | name: "Cyan",
9 | colorNumber: Colors.Cyan,
10 | });
11 |
12 | modelSpace.addLeader({
13 | vertices: [point(0, 0), point(1, 1), point(2, 1)],
14 | layerName: cyan.name,
15 | });
16 |
17 | modelSpace.addLeader({
18 | vertices: [point(2, 0), point(3, 1), point(4, 1)],
19 | layerName: cyan.name,
20 | pathType: PathType.Spline,
21 | colorNumber: Colors.Red,
22 | });
23 |
24 | save(writer.stringify(), fileURLToPath(import.meta.url));
25 |
--------------------------------------------------------------------------------
/examples/lwpolyline.ts:
--------------------------------------------------------------------------------
1 | import { Colors, LWPolylineFlags, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const green = writer.document.tables.addLayer({
8 | name: "Green",
9 | colorNumber: Colors.Green,
10 | });
11 |
12 | modelSpace.currentLayerName = green.name;
13 |
14 | const polyline = modelSpace.addLWPolyline({
15 | flags: LWPolylineFlags.Closed,
16 | });
17 |
18 | polyline.add({ ...point() });
19 | polyline.add({ ...point(100) });
20 | polyline.add({ ...point(100, 100) });
21 | polyline.add({ ...point(0, 100) });
22 |
23 | save(writer.stringify(), fileURLToPath(import.meta.url));
24 |
--------------------------------------------------------------------------------
/examples/mesh.ts:
--------------------------------------------------------------------------------
1 | import { Colors, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const green = writer.document.tables.addLayer({
8 | name: "Green",
9 | colorNumber: Colors.Green,
10 | });
11 |
12 | modelSpace.currentLayerName = green.name;
13 |
14 | const vertices = [
15 | point(0, 0, 0),
16 | point(1, 0, 0),
17 | point(1, 1, 0),
18 | point(0, 1, 0),
19 | point(0, 0, 1),
20 | point(1, 0, 1),
21 | point(1, 1, 1),
22 | point(0, 1, 1),
23 | ];
24 |
25 | const faces = [
26 | [0, 1, 2, 3],
27 | [4, 5, 6, 7],
28 | [0, 1, 5, 4],
29 | [1, 2, 6, 5],
30 | [3, 2, 6, 7],
31 | [0, 3, 7, 4],
32 | ];
33 |
34 | const mesh = modelSpace.addMesh({});
35 | mesh.vertices = vertices;
36 | mesh.faces = faces;
37 | mesh.size = 3;
38 |
39 | save(writer.stringify(), fileURLToPath(import.meta.url));
40 |
--------------------------------------------------------------------------------
/examples/mleader.ts:
--------------------------------------------------------------------------------
1 | import { Colors, TextBuilder, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const magenta = writer.document.tables.addLayer({
8 | name: "Magenta",
9 | colorNumber: Colors.Magenta,
10 | });
11 |
12 | const builder = new TextBuilder();
13 | const p1 = builder.add({
14 | value: "Hello World!",
15 | fontFamily: "JetBrainsMono Nerd Font",
16 | italic: true,
17 | }, true);
18 | p1.add({
19 | value: " Hello World!",
20 | fontFamily: "Arial",
21 | colorNumber: Colors.Red,
22 | });
23 | const p2 = builder.add({
24 | value: "Hello World!",
25 | fontFamily: "JetBrainsMono Nerd Font Mono",
26 | italic: true,
27 | colorNumber: Colors.Green,
28 | });
29 | p2.add({
30 | value: " Hello World!",
31 | colorNumber: Colors.Yellow,
32 | });
33 |
34 |
35 | modelSpace.addMLeader({
36 | textPosition: point(15, 11),
37 | lastPosition: point(10, 10),
38 | arrowheadSize: 2,
39 | value: builder.value,
40 | layerName: magenta.name,
41 | textHeight: 2,
42 | vertices: [point(), point(5)],
43 | doglegLength: 4,
44 | });
45 |
46 | save(writer.stringify(), fileURLToPath(import.meta.url));
47 |
--------------------------------------------------------------------------------
/examples/mtext.ts:
--------------------------------------------------------------------------------
1 | import { Colors, TextBuilder, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const yellow = writer.document.tables.addLayer({
8 | name: "Yellow",
9 | colorNumber: Colors.Yellow,
10 | });
11 |
12 | const builder = new TextBuilder();
13 | const p1 = builder.add(
14 | {
15 | value: "Hello World!",
16 | fontFamily: "Arial",
17 | italic: true,
18 | },
19 | true
20 | );
21 | p1.add({
22 | value: " Hello World!",
23 | fontFamily: "Arial",
24 | colorNumber: Colors.Red,
25 | });
26 | const p2 = builder.add({
27 | value: "Hello World!",
28 | fontFamily: "OpenSans",
29 | italic: true,
30 | colorNumber: Colors.Green,
31 | });
32 | p2.add({
33 | value: " Hello World!",
34 | colorNumber: Colors.Yellow,
35 | });
36 |
37 | modelSpace.addMText({
38 | height: 10,
39 | insertionPoint: point(15, 11),
40 | value: builder.value,
41 | layerName: yellow.name,
42 | });
43 |
44 | const builder2 = new TextBuilder();
45 |
46 | builder2.add(
47 | {
48 | value: "Hello World!",
49 | fontFamily: "Arial",
50 | italic: true,
51 | colorNumber: Colors.Green,
52 | },
53 | true
54 | );
55 |
56 | builder2.add(
57 | {
58 | value: "Hello World!",
59 | fontFamily: "Arial",
60 | italic: true,
61 | colorNumber: Colors.Green,
62 | }
63 | );
64 |
65 | save(writer.stringify(), fileURLToPath(import.meta.url));
66 |
--------------------------------------------------------------------------------
/examples/paper-space.ts:
--------------------------------------------------------------------------------
1 | import { Colors, OmitSeeder, TextOptions, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const paperSpace = writer.document.paperSpace;
6 | const paperSpace0 = writer.document.blocks.addPaperSpace();
7 | const paperSpace1 = writer.document.blocks.addPaperSpace();
8 | writer.document.blocks.addPaperSpace(); // paperSpace2
9 |
10 | const green = writer.document.tables.addLayer({
11 | name: "Green",
12 | colorNumber: Colors.Green,
13 | });
14 |
15 | const style = writer.document.tables.addStyle({
16 | name: "style",
17 | fontFamily: "Arial",
18 | italic: true,
19 | bold: true,
20 | });
21 |
22 | const textOptions: OmitSeeder = {
23 | firstAlignmentPoint: point(),
24 | value: "Hello World!",
25 | height: 10,
26 | styleName: style.name,
27 | layerName: green.name,
28 | };
29 |
30 | paperSpace.addText(textOptions);
31 | paperSpace0.addText(textOptions);
32 | paperSpace1.addText(textOptions);
33 |
34 |
35 | save(writer.stringify(), fileURLToPath(import.meta.url));
36 |
--------------------------------------------------------------------------------
/examples/polyline.ts:
--------------------------------------------------------------------------------
1 | import { Colors, PolylineFlags, VertexFlags, Writer } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const cyan = writer.document.tables.addLayer({
8 | name: "Cyan",
9 | colorNumber: Colors.Cyan,
10 | });
11 |
12 | modelSpace.currentLayerName = cyan.name;
13 |
14 | const polyfaceVertices = [
15 | [0, 0, 0],
16 | [1, 0, 0],
17 | [1, 1, 0],
18 | [0, 1, 0],
19 | [0, 0, 1],
20 | [1, 0, 1],
21 | [1, 1, 1],
22 | [0, 1, 1],
23 | ];
24 |
25 | const polyfaceFaces = [
26 | [0, 3, 2, 1],
27 | [1, 2, 6, 5],
28 | [3, 7, 6, 2],
29 | [0, 4, 7, 3],
30 | [0, 1, 5, 4],
31 | [4, 5, 6, 7],
32 | ];
33 |
34 | const polyline2DVertices = [
35 | [0, 0, 0],
36 | [2, 0, 0],
37 | [2, 2, 0],
38 | [0, 2, 0],
39 | ];
40 |
41 | const polyline3DVertices = [
42 | [0, 0, 1],
43 | [2, 0, 1],
44 | [2, 2, 1],
45 | [0, 2, 1],
46 | ];
47 |
48 | const polyface = modelSpace.addPolyline({ flags: PolylineFlags.PolyfaceMesh });
49 |
50 | const vertexFlags = VertexFlags.PolyfaceMeshVertex | VertexFlags.Polyline3DMesh;
51 |
52 | polyfaceVertices.forEach((vertex) => {
53 | const [x, y, z] = vertex;
54 | polyface.add({ x, y, z, flags: vertexFlags });
55 | });
56 |
57 | polyfaceFaces.forEach((indices) => {
58 | polyface.add({
59 | indices: indices.map((i) => i + 1),
60 | flags: VertexFlags.PolyfaceMeshVertex,
61 | faceRecord: true,
62 | });
63 | });
64 |
65 | const polyline2D = modelSpace.addPolyline({
66 | flags: PolylineFlags.Closed,
67 | });
68 | polyline2DVertices.forEach((vertex) => {
69 | const [x, y, z] = vertex;
70 | polyline2D.add({ x, y, z });
71 | });
72 |
73 | const polyline3D = modelSpace.addPolyline({
74 | flags: PolylineFlags.Polyline3D | PolylineFlags.Closed,
75 | });
76 |
77 | polyline3DVertices.forEach((vertex) => {
78 | const [x, y, z] = vertex;
79 | polyline3D.add({ x, y, z, flags: VertexFlags.Polyline3DVertex });
80 | });
81 |
82 | save(writer.stringify(), fileURLToPath(import.meta.url));
83 |
--------------------------------------------------------------------------------
/examples/quick-start.ts:
--------------------------------------------------------------------------------
1 | import { Colors, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const green = writer.document.tables.addLayer({
8 | name: "Green",
9 | colorNumber: Colors.Green,
10 | });
11 |
12 | const dashed = writer.document.tables.addLType({
13 | name: "DASHED",
14 | descriptive: "Dashed Line",
15 | elements: [1, -1, 1, -1],
16 | });
17 |
18 | const line = modelSpace.addLine({
19 | start: point(),
20 | end: point(100, 100),
21 | layerName: green.name,
22 | lineTypeName: dashed.name,
23 | });
24 |
25 | line.lineTypeScale = 5;
26 |
27 | save(writer.stringify(), fileURLToPath(import.meta.url));
28 |
--------------------------------------------------------------------------------
/examples/rectangle.ts:
--------------------------------------------------------------------------------
1 | import { Colors, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const green = writer.document.tables.addLayer({
8 | name: "Green",
9 | colorNumber: Colors.Green,
10 | });
11 |
12 | modelSpace.currentLayerName = green.name;
13 |
14 | modelSpace.addRectangle({
15 | origin: point(),
16 | width: 100,
17 | });
18 |
19 | modelSpace.addRectangle({
20 | origin: point(110),
21 | width: 100,
22 | corner: 20,
23 | colorNumber: Colors.Red,
24 | });
25 |
26 | modelSpace.addRectangle({
27 | origin: point(220),
28 | width: 100,
29 | corner: point(20, 20),
30 | colorNumber: Colors.Blue,
31 | });
32 |
33 | modelSpace.addRectangle({
34 | origin: point(330),
35 | width: 100,
36 | height: 200,
37 | corner: point(20, 10),
38 | colorNumber: Colors.Yellow,
39 | });
40 |
41 | save(writer.stringify(), fileURLToPath(import.meta.url));
42 |
--------------------------------------------------------------------------------
/examples/svg.ts:
--------------------------------------------------------------------------------
1 | import { Colors, Writer, dline, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 | import { svg } from "@/svg";
4 |
5 | const writer = new Writer();
6 | const modelSpace = writer.document.modelSpace;
7 |
8 | const green = writer.document.tables.addLayer({
9 | name: "Green",
10 | colorNumber: Colors.Green,
11 | });
12 |
13 | const blue = writer.document.tables.addLayer({
14 | name: "Blue",
15 | colorNumber: Colors.Blue,
16 | });
17 |
18 | modelSpace.currentLayerName = green.name;
19 |
20 | modelSpace.addLine({ start: point(), end: point(20, 20) });
21 | modelSpace.addLine({ start: point(0, 2.5), end: point(20, 2.5) });
22 | modelSpace.addLine({ start: point(0, 5), end: point(5, 10) });
23 |
24 | modelSpace.currentLayerName = blue.name;
25 |
26 | const aligned = modelSpace.addAlignedDim({
27 | start: point(),
28 | end: point(20, 20),
29 | offset: 5,
30 | });
31 |
32 | writer.document.renderer.aligned(aligned);
33 |
34 |
35 | const angular = modelSpace.addAngularLinesDim({
36 | firstLine: dline(point(0, 2.5), point(20, 2.5)),
37 | secondLine: dline(point(0, 5), point(5, 10)),
38 | positionArc: point(15, 10),
39 | middle: point(6.575676, 6.259268),
40 | measurement: 0.785398,
41 | });
42 |
43 | writer.document.renderer.angularLines(angular);
44 |
45 | save(svg(writer.document), fileURLToPath(import.meta.url), ".svg");
46 |
--------------------------------------------------------------------------------
/examples/table.ts:
--------------------------------------------------------------------------------
1 | import { Colors, TextBuilder, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const green = writer.document.tables.addLayer({
8 | name: "Green",
9 | colorNumber: Colors.Green,
10 | });
11 |
12 | const cyan = writer.document.tables.addLayer({
13 | name: "Cyan",
14 | colorNumber: Colors.Cyan,
15 | });
16 |
17 | modelSpace.currentLayerName = green.name;
18 |
19 | modelSpace.addTable({
20 | cells: [],
21 | columnsCount: 3,
22 | columnsHeight: [],
23 | insertionPoint: point(0, 18),
24 | rowsCount: 2,
25 | rowsHeight: [],
26 | });
27 |
28 | const table = modelSpace.addTable({
29 | cells: [],
30 | columnsCount: 10,
31 | columnsHeight: [6],
32 | insertionPoint: point(),
33 | rowsCount: 3,
34 | rowsHeight: [4],
35 | });
36 |
37 | const textHeight = 1;
38 |
39 | for (let i = 0; i < 30; i++) {
40 | table.add({ text: `${i + 1}`, textHeight });
41 | }
42 |
43 | const table2 = modelSpace.addTable({
44 | cells: [],
45 | columnsCount: 10,
46 | columnsHeight: [6],
47 | insertionPoint: point(0, 14),
48 | rowsCount: 3,
49 | rowsHeight: [4],
50 | layerName: cyan.name,
51 | });
52 |
53 | for (let i = 0; i < 30; i++) {
54 | if (i > 9) table2.add({ text: `${i + 1}`, textHeight });
55 | else table2.add({});
56 | }
57 |
58 | const builder = new TextBuilder();
59 | const txt = builder.add({
60 | value: "Hello World!",
61 | fontFamily: "Arial",
62 | bold: true,
63 | });
64 |
65 | table2.cells[0].text = txt.value;
66 | table2.cells[0].textHeight = 2;
67 |
68 | save(writer.stringify(), fileURLToPath(import.meta.url));
69 |
--------------------------------------------------------------------------------
/examples/text.ts:
--------------------------------------------------------------------------------
1 | import { Colors, Writer, point } from "@/index";
2 | import { fileURLToPath, save } from "./utils";
3 |
4 | const writer = new Writer();
5 | const modelSpace = writer.document.modelSpace;
6 |
7 | const cyan = writer.document.tables.addLayer({
8 | name: "Cyan",
9 | colorNumber: Colors.Cyan,
10 | });
11 |
12 | const style = writer.document.tables.addStyle({
13 | name: "style",
14 | fontFamily: "JetBrainsMono Nerd Font Mono",
15 | italic: true,
16 | });
17 |
18 | modelSpace.addText({
19 | firstAlignmentPoint: point(),
20 | value: "Hello World!",
21 | height: 10,
22 | styleName: style.name,
23 | layerName: cyan.name,
24 | });
25 |
26 | save(writer.stringify(), fileURLToPath(import.meta.url));
27 |
--------------------------------------------------------------------------------
/examples/utils.ts:
--------------------------------------------------------------------------------
1 | import { mkdirSync, writeFileSync } from "fs";
2 | import { basename } from "path";
3 |
4 | export { fileURLToPath } from "url";
5 |
6 | export function save(content: string, filename: string, ext = ".dxf") {
7 | mkdirSync("output", { recursive: true });
8 | const _name = `output/${basename(filename).replace(".ts", ext)}`;
9 | writeFileSync(_name, content);
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tarikjabiri/dxf",
3 | "version": "3.0.0-alpha.13",
4 | "type": "module",
5 | "description": "A DXF writer written in TypeScript.",
6 | "main": "./lib/index.cjs",
7 | "module": "./lib/index.js",
8 | "types": "./lib/index.d.ts",
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/dxfjs/writer.git"
12 | },
13 | "author": "Tarik EL JABIRI",
14 | "license": "MIT",
15 | "homepage": "https://dxf.vercel.app",
16 | "bugs": {
17 | "url": "https://github.com/dxfjs/writer/issues",
18 | "email": "dxfjss@gmail.com"
19 | },
20 | "scripts": {
21 | "test": "vitest",
22 | "coverage": "vitest run --coverage",
23 | "build": "tsc --noEmit && tsup",
24 | "examples": "tsx ./examples",
25 | "docs:dev": "vitepress dev docs",
26 | "docs:build": "vitepress build docs",
27 | "docs:serve": "vitepress serve docs",
28 | "eslint": "eslint . --fix",
29 | "commit": "cz"
30 | },
31 | "files": [
32 | "lib/**/*"
33 | ],
34 | "devDependencies": {
35 | "@types/node": "^20.5.4",
36 | "@types/opentype.js": "^1.3.4",
37 | "@typescript-eslint/eslint-plugin": "^6.4.1",
38 | "@typescript-eslint/parser": "^6.4.1",
39 | "@vitest/coverage-v8": "^0.34.2",
40 | "commitizen": "^4.3.0",
41 | "cz-conventional-changelog": "^3.3.0",
42 | "eslint": "^8.47.0",
43 | "tsup": "^7.2.0",
44 | "tsx": "^3.12.7",
45 | "typescript": "5.1.*",
46 | "vitepress": "1.0.0-rc.4",
47 | "vitest": "^0.34.2",
48 | "vue": "^3.3.4"
49 | },
50 | "funding": "https://github.com/sponsors/dxfjs",
51 | "keywords": [
52 | "js-dxf",
53 | "ts-dxf",
54 | "dxf",
55 | "writer",
56 | "js",
57 | "ts"
58 | ],
59 | "pnpm": {
60 | "peerDependencyRules": {
61 | "ignoreMissing": [
62 | "@algolia/client-search"
63 | ]
64 | }
65 | },
66 | "engines": {
67 | "node": ">=16",
68 | "pnpm": ">=8"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/blocks/block.ts:
--------------------------------------------------------------------------------
1 | import { AppDefined, TagsManager, point } from "@/utils";
2 | import { Point3D, Taggable, WithBlockRecord, WithSeeder } from "@/types";
3 | import { EndBlk } from "./endblk";
4 | import { EntitiesManager } from "@/entities";
5 |
6 | export const BlockFlags = {
7 | None: 0,
8 | Anonymous: 1,
9 | NoAttribute: 2,
10 | External: 4,
11 | XRef: 8,
12 | ExternallyDependent: 16,
13 | ResolvedXRef: 32,
14 | ReferencedXRef: 64,
15 | } as const;
16 |
17 | export interface BlockOptions extends WithSeeder, WithBlockRecord {
18 | name: string;
19 | layerName?: string;
20 | flags?: number;
21 | basePoint?: Point3D;
22 | secondName?: string;
23 | xrefPathName?: string;
24 | description?: string;
25 | }
26 |
27 | export class Block extends EntitiesManager implements Taggable {
28 | readonly applications: AppDefined[];
29 |
30 | ownerObjectHandle: string;
31 | layerName: string;
32 | name: string;
33 | flags: number;
34 | basePoint: Point3D;
35 | secondName: string;
36 | xrefPathName: string;
37 | description?: string;
38 |
39 | readonly endblk: EndBlk;
40 |
41 | get isModelSpace() {
42 | return this.name.startsWith("*Model_Space");
43 | }
44 |
45 | get isPaperSpace() {
46 | return this.name.startsWith("*Paper_Space");
47 | }
48 |
49 | constructor(options: BlockOptions) {
50 | super(options);
51 | this.applications = [];
52 |
53 | this.ownerObjectHandle = "0";
54 | this.layerName = options.layerName || "0";
55 | this.name = options.name;
56 | this.flags = options.flags ?? BlockFlags.None;
57 | this.basePoint = options.basePoint || point();
58 | this.secondName = options.secondName || this.name;
59 | this.xrefPathName = options.xrefPathName || "";
60 | this.description = options.description;
61 |
62 | this.endblk = new EndBlk(this);
63 | }
64 |
65 | addAppDefined(name: string) {
66 | const f = this.applications.find((a) => a.name === name);
67 | if (f) return f;
68 |
69 | const a = new AppDefined(name);
70 | this.applications.push(a);
71 | return a;
72 | }
73 |
74 | override tagify(mg: TagsManager): void {
75 | mg.add(0, "BLOCK");
76 | mg.add(5, this.handle);
77 | this.applications.forEach((a) => a.tagify(mg));
78 | mg.add(330, this.ownerObjectHandle);
79 | mg.add(100, "AcDbEntity");
80 | mg.add(8, this.layerName);
81 | mg.add(100, "AcDbBlockBegin");
82 | mg.add(2, this.name);
83 | mg.add(70, this.flags);
84 | mg.point(this.basePoint);
85 | mg.add(3, this.secondName);
86 | mg.add(1, this.xrefPathName);
87 | mg.add(4, this.description);
88 | if (!this.isModelSpace && this.name !== "*Paper_Space") super.tagify(mg);
89 | this.endblk.tagify(mg);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/blocks/blocks.ts:
--------------------------------------------------------------------------------
1 | import { Block, BlockOptions } from "./block";
2 | import { OmitBlockRecord, OmitSeeder, Taggable, WithSeeder } from "@/types";
3 | import { Seeder, TagsManager } from "@/utils";
4 | import { Tables } from "@/tables";
5 |
6 | export interface BlocksOptions {
7 | tables: Tables;
8 | seeder: Seeder;
9 | }
10 |
11 | export class Blocks implements Taggable, WithSeeder {
12 | readonly tables: Tables;
13 | readonly seeder: Seeder;
14 | readonly blocks: Block[];
15 | readonly modelSpace: Block;
16 | readonly paperSpace: Block;
17 |
18 | private paperSpaceSeed = 0;
19 |
20 | constructor({ tables, seeder }: BlocksOptions) {
21 | this.tables = tables;
22 | this.seeder = seeder;
23 | this.blocks = [];
24 | this.modelSpace = this.addBlock({ name: "*Model_Space" });
25 | this.paperSpace = this.addBlock({ name: "*Paper_Space" });
26 | }
27 |
28 | addBlock(options: OmitBlockRecord>) {
29 | const blockRecord = this.tables.addBlockRecord(options);
30 | const b = new Block({ ...options, ...this, blockRecord });
31 | b.ownerObjectHandle = blockRecord.handle;
32 | b.endblk.ownerObjectHandle = blockRecord.handle;
33 | this.blocks.push(b);
34 | return b;
35 | }
36 |
37 | get(name?: string) {
38 | if (name == null) return;
39 | return this.blocks.find(b => b.name === name);
40 | }
41 |
42 | addPaperSpace() {
43 | const name = `*Paper_Space${this.paperSpaceSeed++}`;
44 | return this.addBlock({ name });
45 | }
46 |
47 | tagify(mg: TagsManager): void {
48 | mg.sectionStart("BLOCKS");
49 | this.blocks.forEach((b) => b.tagify(mg));
50 | mg.sectionEnd();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/blocks/endblk.ts:
--------------------------------------------------------------------------------
1 | import { AppDefined, TagsManager } from "@/utils";
2 | import { Taggable, WithSeeder } from "@/types";
3 |
4 | export interface EndBlkOptions extends WithSeeder {}
5 |
6 | export class EndBlk implements Taggable {
7 | readonly handle: string;
8 |
9 | readonly applications: AppDefined[];
10 |
11 | ownerObjectHandle: string;
12 | layerName: string;
13 |
14 | constructor({ seeder }: EndBlkOptions) {
15 | this.handle = seeder.next();
16 |
17 | this.applications = [];
18 |
19 | this.ownerObjectHandle = "0";
20 | this.layerName = "0";
21 | }
22 |
23 | addAppDefined(name: string) {
24 | const f = this.applications.find((a) => a.name === name);
25 | if (f) return f;
26 |
27 | const a = new AppDefined(name);
28 | this.applications.push(a);
29 | return a;
30 | }
31 |
32 | tagify(mg: TagsManager): void {
33 | mg.add(0, "ENDBLK");
34 | mg.add(5, this.handle);
35 | this.applications.forEach((a) => a.tagify(mg));
36 | mg.add(330, this.ownerObjectHandle);
37 | mg.add(100, "AcDbEntity");
38 | mg.add(8, this.layerName);
39 | mg.add(100, "AcDbBlockEnd");
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/blocks/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./block";
2 | export * from "./blocks";
3 | export * from "./endblk";
4 |
--------------------------------------------------------------------------------
/src/classes/classes.ts:
--------------------------------------------------------------------------------
1 | import { Taggable } from "@/types";
2 | import { TagsManager } from "@/utils";
3 |
4 | export class Classes implements Taggable {
5 | tagify(mg: TagsManager): void {
6 | mg.sectionStart("CLASSES");
7 | mg.sectionEnd();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/classes/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./classes";
2 |
--------------------------------------------------------------------------------
/src/document.ts:
--------------------------------------------------------------------------------
1 | import { BBox, Seeder, TagsManager, Units, point2d } from "./utils";
2 | import { BlockOptions, Blocks } from "./blocks";
3 | import {
4 | OmitBlockRecord,
5 | OmitSeeder,
6 | Stringifiable,
7 | WithSeeder,
8 | } from "./types";
9 | import { Classes } from "./classes";
10 | import { DimensionRenderer } from "./entities";
11 | import { Entities } from "./entities";
12 | import { Header } from "./header";
13 | import { Objects } from "./objects";
14 | import { Tables } from "./tables";
15 |
16 | export class Document implements Stringifiable, WithSeeder {
17 | readonly seeder: Seeder;
18 | readonly header: Header;
19 | readonly classes: Classes;
20 | readonly blocks: Blocks;
21 | readonly entities: Entities;
22 | readonly tables: Tables;
23 | readonly objects: Objects;
24 | readonly renderer: DimensionRenderer;
25 |
26 | units: number;
27 |
28 | get modelSpace() {
29 | return this.blocks.modelSpace;
30 | }
31 |
32 | get paperSpace() {
33 | return this.blocks.paperSpace;
34 | }
35 |
36 | constructor() {
37 | this.seeder = new Seeder();
38 | this.header = new Header(this);
39 | this.classes = new Classes();
40 | this.tables = new Tables(this);
41 | this.blocks = new Blocks(this);
42 | this.entities = new Entities(this);
43 | this.objects = new Objects(this);
44 | this.renderer = new DimensionRenderer(this);
45 |
46 | this.units = Units.Unitless;
47 | }
48 |
49 | setUnits(units: number) {
50 | this.units = units;
51 | }
52 |
53 | setCurrentLayerName(name: string) {
54 | this.modelSpace.currentLayerName = name;
55 | }
56 |
57 | addBlock(options: OmitBlockRecord>) {
58 | return this.blocks.addBlock(options);
59 | }
60 |
61 | addPaperSpace() {
62 | return this.blocks.addPaperSpace();
63 | }
64 |
65 | addVariable(name: string) {
66 | return this.header.add(name);
67 | }
68 |
69 | stringify(): string {
70 | const mg = new TagsManager();
71 | this.fitIn();
72 | this.header.tagify(mg);
73 | this.classes.tagify(mg);
74 | this.tables.tagify(mg);
75 | this.blocks.tagify(mg);
76 | this.entities.tagify(mg);
77 | this.objects.tagify(mg);
78 | mg.add(0, "EOF");
79 | return mg.stringify();
80 | }
81 |
82 | private fitIn() {
83 | const bbox = this.modelSpace.bbox();
84 | const center = BBox.center(bbox);
85 | const height = BBox.height(bbox);
86 | this.tables.vportActive.lowerLeft = point2d(bbox.minX, bbox.minY);
87 | this.tables.vportActive.upperRight = point2d(bbox.maxX, bbox.maxY);
88 | this.tables.vportActive.center = center;
89 | this.tables.vportActive.height = height;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/entities/arc.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, extrusion, TagsManager } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface ArcOptions extends EntityOptions {
6 | thickness?: number;
7 | center: Point3D;
8 | radius: number;
9 | startAngle: number;
10 | endAngle: number;
11 | extrusion?: Point3D;
12 | }
13 |
14 | export class Arc extends Entity {
15 | thickness?: number;
16 | center: Point3D;
17 | radius: number;
18 | startAngle: number;
19 | endAngle: number;
20 | extrusion: Point3D;
21 |
22 | override get subClassMarker(): string {
23 | return "AcDbCircle";
24 | }
25 |
26 | constructor(options: ArcOptions) {
27 | super(options);
28 | this._type = "ARC";
29 | this.thickness = options.thickness;
30 | this.center = options.center;
31 | this.radius = options.radius;
32 | this.startAngle = options.startAngle;
33 | this.endAngle = options.endAngle;
34 | this.extrusion = options.extrusion || extrusion();
35 | }
36 |
37 | override bbox(): BoundingBox {
38 | return BBox.point(this.center);
39 | }
40 |
41 | protected override tagifyChild(mg: TagsManager): void {
42 | mg.add(39, this.thickness);
43 | mg.point(this.center);
44 | mg.add(40, this.radius);
45 | mg.point(this.extrusion, 200);
46 | mg.add(100, "AcDbArc");
47 | mg.add(50, this.startAngle);
48 | mg.add(51, this.endAngle);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/entities/attdef.ts:
--------------------------------------------------------------------------------
1 | import { Text, TextOptions } from "./text";
2 | import { TagsManager } from "@/utils";
3 |
4 | export interface AttdefOptions extends TextOptions {
5 | prompt?: string;
6 | tag: string;
7 | flags?: number;
8 | }
9 |
10 | export class Attdef extends Text {
11 | prompt: string;
12 | tag: string;
13 | flags: number;
14 |
15 | protected override get subClassMarker2(): string {
16 | return "AcDbAttributeDefinition";
17 | }
18 |
19 | constructor(options: AttdefOptions) {
20 | super(options);
21 | this._type = "ATTDEF";
22 | this.prompt = options.prompt || "";
23 | this.tag = options.tag;
24 | this.flags = options.flags || 0;
25 | }
26 |
27 | protected override tagifyChild(mg: TagsManager): void {
28 | mg.add(39, this.thickness);
29 | mg.point(this.firstAlignmentPoint);
30 | mg.add(40, this.height);
31 | mg.add(1, this.value);
32 | mg.add(50, this.rotation);
33 | mg.add(41, this.relativeXScaleFactor);
34 | mg.add(51, this.obliqueAngle);
35 | mg.add(7, this.styleName);
36 | mg.add(71, this.generationFlags);
37 | mg.add(72, this.horizontalJustification);
38 | mg.point(this.secondAlignmentPoint, 1);
39 | mg.add(100, this.subClassMarker2);
40 | mg.add(280, 0);
41 | mg.add(3, this.prompt);
42 | mg.add(2, this.tag);
43 | mg.add(70, this.flags);
44 | mg.add(74, this.verticalJustification);
45 | mg.add(280, 1);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/entities/attrib.ts:
--------------------------------------------------------------------------------
1 | import { Entity, EntityOptions } from "./entity";
2 | import { Point3D, Union } from "@/types";
3 | import { TagsManager, extrusion } from "@/utils";
4 | import { TextHorizontalJustification, TextVerticalJustification } from "./text";
5 | import { Insert } from "./insert";
6 | import { TextGenerationFlags } from "@/tables";
7 |
8 | export interface AttribOptions extends EntityOptions {
9 | thickness?: number;
10 | startPoint: Point3D;
11 | height: number;
12 | value: string;
13 | tag: string;
14 | flags?: number;
15 | rotation?: number;
16 | relativeXScaleFactor?: number;
17 | obliqueAngle?: number;
18 | styleName?: string;
19 | generationFlags?: Union;
20 | horizontalJustification?: Union;
21 | verticalJustification?: Union;
22 | alignmentPoint?: Point3D;
23 | extrusion?: Point3D;
24 | insert: Insert;
25 | }
26 |
27 | export class Attrib extends Entity {
28 | thickness?: number;
29 | startPoint: Point3D;
30 | height: number;
31 | value: string;
32 | tag: string;
33 | flags: number;
34 | rotation?: number;
35 | relativeXScaleFactor?: number;
36 | obliqueAngle?: number;
37 | styleName?: string;
38 | generationFlags?: Union;
39 | horizontalJustification?: Union;
40 | verticalJustification?: Union;
41 | alignmentPoint?: Point3D;
42 | extrusion: Point3D;
43 | insert: Insert;
44 |
45 | override get subClassMarker(): string | undefined {
46 | return "AcDbText";
47 | }
48 |
49 | override get changeOwner(): boolean {
50 | return false;
51 | }
52 |
53 | constructor(options: AttribOptions) {
54 | super(options);
55 | this._type = "ATTRIB";
56 | this.thickness = options.thickness;
57 | this.startPoint = options.startPoint;
58 | this.height = options.height;
59 | this.value = options.value;
60 | this.tag = options.tag;
61 | this.flags = options.flags ?? 0;
62 | this.rotation = options.rotation;
63 | this.relativeXScaleFactor = options.relativeXScaleFactor;
64 | this.obliqueAngle = options.obliqueAngle;
65 | this.styleName = options.styleName;
66 | this.generationFlags = options.generationFlags;
67 | this.horizontalJustification = options.horizontalJustification;
68 | this.verticalJustification = options.verticalJustification;
69 | this.alignmentPoint = options.alignmentPoint;
70 | this.extrusion = options.extrusion || extrusion();
71 | this.insert = options.insert;
72 | this.ownerObjectHandle = options.insert.handle;
73 | }
74 |
75 | protected override tagifyChild(mg: TagsManager): void {
76 | mg.add(39, this.thickness);
77 | mg.point(this.startPoint);
78 | mg.add(40, this.height);
79 | mg.add(1, this.value);
80 | mg.add(50, this.rotation);
81 | mg.add(41, this.relativeXScaleFactor);
82 | mg.add(51, this.obliqueAngle);
83 | mg.add(7, this.styleName);
84 | mg.add(100, "AcDbAttribute");
85 | mg.add(280, 0);
86 | mg.add(2, this.tag);
87 | mg.add(70, this.flags);
88 | mg.add(71, this.generationFlags);
89 | mg.add(72, this.horizontalJustification);
90 | mg.add(74, this.verticalJustification);
91 | mg.point(this.alignmentPoint, 1);
92 | mg.add(280, 1);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/entities/circle.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, extrusion } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface CircleOptions extends EntityOptions {
6 | thickness?: number;
7 | center: Point3D;
8 | radius: number;
9 | extrusion?: Point3D;
10 | }
11 |
12 | export class Circle extends Entity {
13 | thickness?: number;
14 | center: Point3D;
15 | radius: number;
16 | extrusion: Point3D;
17 |
18 | override get subClassMarker(): string {
19 | return "AcDbCircle";
20 | }
21 |
22 | constructor(options: CircleOptions) {
23 | super(options);
24 | this._type = "CIRCLE";
25 | this.thickness = options.thickness;
26 | this.center = options.center;
27 | this.radius = options.radius;
28 | this.extrusion = options.extrusion || extrusion();
29 | }
30 |
31 | override bbox(): BoundingBox {
32 | return BBox.point(this.center);
33 | }
34 |
35 | protected override tagifyChild(mg: TagsManager): void {
36 | mg.add(39, this.thickness);
37 | mg.point(this.center);
38 | mg.add(40, this.radius);
39 | mg.point(this.extrusion, 200);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/entities/dimension/aligned.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions, DimensionType } from "./dimension";
2 | import { TagsManager, angle, point, polar } from "@/utils";
3 | import { Point3D } from "@/types";
4 | import { linep } from "@/helpers";
5 |
6 | export interface AlignedDimensionOptions extends DimensionOptions {
7 | insertion?: Point3D;
8 | start: Point3D;
9 | end: Point3D;
10 | offset?: number;
11 | }
12 |
13 | export class AlignedDimension extends Dimension {
14 | insertion?: Point3D;
15 | start: Point3D;
16 | end: Point3D;
17 |
18 | readonly offset: number;
19 |
20 | constructor(options: AlignedDimensionOptions) {
21 | super(options);
22 | this.dimensionType = DimensionType.Aligned;
23 | this.insertion = options.insertion;
24 | this.start = options.start;
25 | this.end = options.end;
26 | this.offset = options.offset ?? 0;
27 | this._offset();
28 | }
29 |
30 | protected override tagifyChild(mg: TagsManager): void {
31 | super.tagifyChild(mg);
32 | mg.add(100, "AcDbAlignedDimension");
33 | mg.point(this.insertion, 2);
34 | mg.point(this.start, 3);
35 | mg.point(this.end, 4);
36 | }
37 |
38 | private _offset() {
39 | const { offset } = this;
40 | if (offset == null) return;
41 | const sign = Math.sign(this.offset);
42 | const a = angle(this.start, this.end) + 90 * sign;
43 | const start = polar(this.start, a, this.offset);
44 | this.definition = polar(this.end, a, this.offset);
45 | const middle = linep(start, this.definition).middle;
46 | this.middle = point(middle.x, middle.y);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/entities/dimension/angular/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./lines";
2 | export * from "./points";
3 |
--------------------------------------------------------------------------------
/src/entities/dimension/angular/lines.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions, DimensionType } from "../dimension";
2 | import { Point3D } from "@/types";
3 | import { TagsManager } from "@/utils";
4 |
5 | export interface AngularLineDimension {
6 | start: Point3D;
7 | end: Point3D;
8 | }
9 |
10 | export interface AngularLineDimensionOptions extends DimensionOptions {
11 | firstLine: AngularLineDimension;
12 | secondLine: AngularLineDimension;
13 | positionArc: Point3D;
14 | }
15 |
16 | export function dline(start: Point3D, end: Point3D): AngularLineDimension {
17 | return { start, end };
18 | }
19 |
20 | export class AngularLinesDimension extends Dimension {
21 | firstLine: AngularLineDimension;
22 | secondLine: AngularLineDimension;
23 | positionArc: Point3D;
24 |
25 | constructor(options: AngularLineDimensionOptions) {
26 | super(options);
27 | this.dimensionType = DimensionType.Angular;
28 | this.firstLine = options.firstLine;
29 | this.secondLine = options.secondLine;
30 | this.positionArc = options.positionArc;
31 | this.definition = this.secondLine.end;
32 | }
33 |
34 | protected override tagifyChild(mg: TagsManager): void {
35 | super.tagifyChild(mg);
36 | mg.add(100, "AcDb2LineAngularDimension");
37 | mg.point(this.firstLine.start, 3);
38 | mg.point(this.firstLine.end, 4);
39 | mg.point(this.secondLine.start, 5);
40 | mg.point(this.positionArc, 6);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/entities/dimension/angular/points.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions, DimensionType } from "../dimension";
2 | import { Point3D } from "@/types";
3 | import { TagsManager } from "@/utils";
4 |
5 | export interface AngularPointsDimensionOptions extends DimensionOptions {
6 | center: Point3D;
7 | first: Point3D;
8 | second: Point3D;
9 | }
10 |
11 | export class AngularPointsDimension extends Dimension {
12 | center: Point3D;
13 | first: Point3D;
14 | second: Point3D;
15 |
16 | constructor(options: AngularPointsDimensionOptions) {
17 | super(options);
18 | this.dimensionType = DimensionType.Angular3Point;
19 | this.center = options.center;
20 | this.first = options.first;
21 | this.second = options.second;
22 | }
23 |
24 | protected override tagifyChild(mg: TagsManager): void {
25 | super.tagifyChild(mg);
26 | mg.add(100, "AcDb3PointAngularDimension");
27 | mg.point(this.first, 3);
28 | mg.point(this.second, 4);
29 | mg.point(this.center, 5);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/entities/dimension/arc.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions } from "./dimension";
2 | import { Point3D } from "@/types.ts";
3 | import { TagsManager } from "@/utils";
4 |
5 | export interface ArcDimensionOptions extends DimensionOptions {
6 | center: Point3D;
7 | startPoint: Point3D;
8 | endPoint: Point3D;
9 | startAngle?: number;
10 | endAngle?: number;
11 | isPartial?: boolean;
12 | hasLeader?: boolean;
13 | firstLeaderPoint?: Point3D;
14 | secondLeaderPoint?: Point3D;
15 | }
16 |
17 | export class ArcDimension extends Dimension {
18 | center: Point3D;
19 | startPoint: Point3D;
20 | endPoint: Point3D;
21 | startAngle: number;
22 | endAngle: number;
23 | isPartial?: boolean;
24 | hasLeader?: boolean;
25 | firstLeaderPoint?: Point3D;
26 | secondLeaderPoint?: Point3D;
27 |
28 | constructor(options: ArcDimensionOptions) {
29 | super(options);
30 | this._type = "ARC_DIMENSION";
31 | this.center = options.center;
32 | this.startPoint = options.startPoint;
33 | this.endPoint = options.endPoint;
34 | this.startAngle = options.startAngle ?? 0;
35 | this.endAngle = options.endAngle ?? 0;
36 | this.isPartial = options.isPartial;
37 | this.hasLeader = options.hasLeader;
38 | this.firstLeaderPoint = options.firstLeaderPoint;
39 | this.secondLeaderPoint = options.secondLeaderPoint;
40 | }
41 |
42 | protected override tagifyChild(mg: TagsManager): void {
43 | super.tagifyChild(mg);
44 | mg.add(100, "AcDbArcDimension");
45 |
46 | mg.point(this.startPoint, 3);
47 | mg.point(this.endPoint, 4);
48 | mg.point(this.center, 5);
49 |
50 | mg.add(40, this.startAngle);
51 | mg.add(41, this.endAngle);
52 |
53 | mg.add(70, Number(this.isPartial ?? 0));
54 | mg.add(71, Number(this.hasLeader ?? 0));
55 |
56 | mg.point(this.firstLeaderPoint, 6);
57 | mg.point(this.secondLeaderPoint, 7);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/entities/dimension/diameter.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions, DimensionType } from "./dimension";
2 | import { Point3D } from "@/types.ts";
3 | import { TagsManager } from "@/utils";
4 |
5 | export interface DiameterDimensionOptions extends DimensionOptions {
6 | first: Point3D;
7 | leaderLength: number;
8 | }
9 |
10 | export class DiameterDimension extends Dimension {
11 | first: Point3D;
12 | leaderLength: number;
13 | constructor(options: DiameterDimensionOptions) {
14 | super(options);
15 | this.dimensionType = DimensionType.Diameter;
16 | this.first = options.first;
17 | this.leaderLength = options.leaderLength;
18 | }
19 |
20 | protected override tagifyChild(mg: TagsManager) {
21 | super.tagifyChild(mg);
22 | mg.add(100, "AcDbDiametricDimension");
23 | mg.point(this.first, 5);
24 | mg.add(40, this.leaderLength);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/entities/dimension/dimension.ts:
--------------------------------------------------------------------------------
1 | import { Entity, EntityOptions } from "../entity";
2 | import { Point3D, Union } from "@/types";
3 | import { TagsManager, extrusion } from "@/utils";
4 |
5 | export const DimensionType = {
6 | None: 0,
7 | Aligned: 1,
8 | Angular: 2,
9 | Diameter: 3,
10 | Radius: 4,
11 | Angular3Point: 5,
12 | Ordinate: 6,
13 | ReferencedByThis: 32,
14 | OrdinateType: 64,
15 | UserDefined: 128,
16 | } as const;
17 |
18 | export const DimAttachment = {
19 | TopLeft: 1,
20 | TopCenter: 2,
21 | TopRight: 3,
22 | MiddleLeft: 4,
23 | MiddleCenter: 5,
24 | MiddleRight: 6,
25 | BottomLeft: 7,
26 | BottomCenter: 8,
27 | BottomRight: 9,
28 | } as const;
29 |
30 | export const DimTextLineSpacingStyle = {
31 | AtLeast: 1,
32 | Exact: 2,
33 | } as const;
34 |
35 | export interface DimensionOptions extends EntityOptions {
36 | blockName?: string;
37 | definition?: Point3D;
38 | middle?: Point3D;
39 | attachment?: Union;
40 | textLineSpacingStyle?: Union;
41 | textLineSpacingFactor?: number;
42 | measurement?: number;
43 | text?: string;
44 | textRotation?: number;
45 | horizontalDirection?: number;
46 | extrusion?: Point3D;
47 | dimStyleName?: string;
48 | }
49 |
50 | export class Dimension extends Entity {
51 | blockName?: string;
52 | definition?: Point3D;
53 | middle?: Point3D;
54 | dimensionType: Union;
55 | attachment: Union;
56 | textLineSpacingStyle?: Union;
57 | textLineSpacingFactor?: number;
58 | measurement?: number;
59 | text?: string;
60 | textRotation?: number;
61 | horizontalDirection?: number;
62 | extrusion: Point3D;
63 | dimStyleName?: string;
64 |
65 | override get subClassMarker(): string | undefined {
66 | return "AcDbDimension";
67 | }
68 |
69 | constructor(options: DimensionOptions) {
70 | super(options);
71 | this._type = "DIMENSION";
72 | this.blockName = options.blockName;
73 | this.definition = options.definition;
74 | this.middle = options.middle;
75 | this.dimensionType = DimensionType.None;
76 | this.attachment = options.attachment ?? DimAttachment.MiddleCenter;
77 | this.textLineSpacingStyle = options.textLineSpacingStyle;
78 | this.textLineSpacingFactor = options.textLineSpacingFactor;
79 | this.measurement = options.measurement;
80 | this.text = options.text;
81 | this.textRotation = options.textRotation;
82 | this.horizontalDirection = options.horizontalDirection;
83 | this.extrusion = options.extrusion ?? extrusion();
84 | this.dimStyleName = options.dimStyleName;
85 | }
86 |
87 | protected override tagifyChild(mg: TagsManager): void {
88 | mg.add(2, this.blockName);
89 | mg.point(this.definition);
90 | mg.point(this.middle, 1);
91 | mg.add(70, this.dimensionType);
92 | mg.add(71, this.attachment);
93 | mg.add(72, this.textLineSpacingStyle);
94 | mg.add(41, this.textLineSpacingFactor);
95 | mg.add(42, this.measurement);
96 | mg.add(1, this.text);
97 | mg.add(53, this.textRotation);
98 | mg.add(51, this.horizontalDirection);
99 | mg.point(this.extrusion, 200);
100 | mg.add(3, this.dimStyleName);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/entities/dimension/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./aligned";
2 | export * from "./angular";
3 | export * from "./arc";
4 | export * from "./diameter";
5 | export * from "./dimension";
6 | export * from "./linear";
7 | export * from "./radial";
8 | export * from "./render";
9 |
--------------------------------------------------------------------------------
/src/entities/dimension/linear.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions } from "./dimension";
2 | import { TagsManager, polar } from "@/utils";
3 | import { Point3D } from "@/types";
4 |
5 | export interface LinearDimensionOptions extends DimensionOptions {
6 | start: Point3D;
7 | end: Point3D;
8 | offset?: number;
9 | insertion?: Point3D;
10 | angle?: number;
11 | types?: number;
12 | }
13 |
14 | export class LinearDimension extends Dimension {
15 | insertion?: Point3D;
16 | start: Point3D;
17 | end: Point3D;
18 | angle: number;
19 | types?: number;
20 |
21 | constructor(options: LinearDimensionOptions) {
22 | super(options);
23 | this.insertion = options.insertion;
24 | this.start = options.start;
25 | this.end = options.end;
26 | this.angle = options.angle ?? 0;
27 | this.types = options.types;
28 | this.offset(options.offset);
29 | }
30 |
31 | protected override tagifyChild(mg: TagsManager): void {
32 | super.tagifyChild(mg);
33 | mg.add(100, "AcDbAlignedDimension");
34 | mg.point(this.insertion, 2);
35 | mg.point(this.start, 3);
36 | mg.point(this.end, 4);
37 | mg.add(50, this.angle);
38 | mg.add(52, this.types);
39 | mg.add(100, "AcDbRotatedDimension");
40 | }
41 |
42 | private offset(v?: number) {
43 | if (v == null) return;
44 | this.definition = polar(this.start, this.angle - 90, v);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/entities/dimension/radial.ts:
--------------------------------------------------------------------------------
1 | import { Dimension, DimensionOptions, DimensionType } from "./dimension";
2 | import { Point3D } from "@/types.ts";
3 | import { TagsManager } from "@/utils";
4 |
5 | export interface RadialDimensionOptions extends DimensionOptions {
6 | first: Point3D;
7 | leaderLength: number;
8 | }
9 |
10 | export class RadialDimension extends Dimension {
11 | first: Point3D;
12 | leaderLength: number;
13 |
14 | constructor(options: RadialDimensionOptions) {
15 | super(options);
16 | this.dimensionType = DimensionType.Radius;
17 | this.first = options.first;
18 | this.leaderLength = options.leaderLength;
19 | }
20 |
21 | protected override tagifyChild(mg: TagsManager) {
22 | super.tagifyChild(mg);
23 | mg.add(100, "AcDbRadialDimension");
24 | mg.point(this.first, 5);
25 | mg.add(40, this.leaderLength);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/entities/dimension/render/arrow.ts:
--------------------------------------------------------------------------------
1 | import { Point2D, Point3D, WithSeeder } from "@/types";
2 | import { Seeder, point, point2d } from "@/utils";
3 | import { Solid } from "@/entities";
4 | import { transform } from "@/helpers";
5 |
6 | export interface DimensionArrowOptions extends WithSeeder {
7 | size?: number;
8 | rotation?: number;
9 | position?: Point3D;
10 | }
11 |
12 | export function arrow(options: DimensionArrowOptions) {
13 | return new DimensionArrow(options).entity();
14 | }
15 |
16 | export class DimensionArrow {
17 | readonly seeder: Seeder;
18 | size: number;
19 | rotation: number;
20 | position: Point3D;
21 |
22 | constructor(options: DimensionArrowOptions) {
23 | this.seeder = options.seeder;
24 | this.size = options.size ?? 2.5;
25 | this.rotation = options.rotation ?? 0;
26 | this.position = options.position ?? point();
27 | }
28 |
29 | entity() {
30 | const { size: s, seeder } = this;
31 | const h = this.size / 3 / 2;
32 | return new Solid({
33 | seeder,
34 | first: this.position,
35 | second: this._transform(point2d(-s, -h)),
36 | third: this._transform(point2d(-s, h)),
37 | });
38 | }
39 |
40 | private _transform(target: Point2D) {
41 | const result = transform({
42 | target,
43 | center: this.position,
44 | angle: this.rotation,
45 | translation: this.position,
46 | });
47 | return point(result.x, result.y);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/entities/dimension/render/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./arrow";
2 | export * from "./renderer";
3 |
--------------------------------------------------------------------------------
/src/entities/ellipse.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, extrusion } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface EllipseOptions extends EntityOptions {
6 | center: Point3D;
7 | endpoint: Point3D;
8 | extrusion?: Point3D;
9 | ratio?: number;
10 | start?: number;
11 | end?: number;
12 | }
13 |
14 | export class Ellipse extends Entity {
15 | center: Point3D;
16 | endpoint: Point3D;
17 | extrusion: Point3D;
18 | ratio: number;
19 | start: number;
20 | end: number;
21 |
22 | override get subClassMarker(): string | undefined {
23 | return "AcDbEllipse";
24 | }
25 |
26 | constructor(options: EllipseOptions) {
27 | super(options);
28 | this._type = "ELLIPSE";
29 | this.center = options.center;
30 | this.endpoint = options.endpoint;
31 | this.extrusion = options.extrusion || extrusion();
32 | this.ratio = options.ratio ?? 1;
33 | this.start = options.start ?? 0;
34 | this.end = options.end ?? 2 * Math.PI;
35 | }
36 |
37 | override bbox(): BoundingBox {
38 | const { x, y, z } = this.endpoint;
39 | const radius = Math.sqrt(x * x + y * y + z * z);
40 | return BBox.point(this.center, radius);
41 | }
42 |
43 | protected override tagifyChild(mg: TagsManager): void {
44 | mg.point(this.center);
45 | mg.point(this.endpoint, 1);
46 | mg.point(this.extrusion, 200);
47 | mg.add(40, this.ratio);
48 | mg.add(41, this.start);
49 | mg.add(42, this.end);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/entities/entities.ts:
--------------------------------------------------------------------------------
1 | import { Blocks } from "@/blocks";
2 | import { Taggable } from "@/types";
3 | import { TagsManager } from "@/utils";
4 |
5 | export interface EntitiesOptions {
6 | blocks: Blocks;
7 | }
8 |
9 | export class Entities implements Taggable {
10 | readonly blocks: Blocks;
11 |
12 | get modelSpace() {
13 | return this.blocks.modelSpace;
14 | }
15 |
16 | get paperSpace() {
17 | return this.blocks.paperSpace;
18 | }
19 |
20 | constructor(options: EntitiesOptions) {
21 | this.blocks = options.blocks;
22 | }
23 |
24 | tagify(mg: TagsManager): void {
25 | mg.sectionStart("ENTITIES");
26 | this.paperSpace.entities.forEach((e) => e.tagify(mg));
27 | this.modelSpace.entities.forEach((e) => e.tagify(mg));
28 | mg.sectionEnd();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/entities/face.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D, Union } from "@/types";
4 |
5 | export const InvisibleEdge = {
6 | None: 0,
7 | First: 1,
8 | Second: 2,
9 | Third: 4,
10 | Fourth: 8,
11 | } as const;
12 |
13 | export interface FaceOptions extends EntityOptions {
14 | first: Point3D;
15 | second: Point3D;
16 | third: Point3D;
17 | fourth?: Point3D;
18 | flags?: Union;
19 | }
20 |
21 | export class Face extends Entity {
22 | first: Point3D;
23 | second: Point3D;
24 | third: Point3D;
25 | fourth: Point3D;
26 | flags: Union;
27 |
28 | override get subClassMarker() {
29 | return "AcDbFace";
30 | }
31 |
32 | constructor(options: FaceOptions) {
33 | super(options);
34 | this._type = "3DFACE";
35 | this.first = options.first;
36 | this.second = options.second;
37 | this.third = options.third;
38 | this.fourth = options.fourth || this.third;
39 | this.flags = options.flags ?? InvisibleEdge.None;
40 | }
41 |
42 | override bbox(): BoundingBox {
43 | return BBox.points([this.first, this.second, this.third, this.fourth]);
44 | }
45 |
46 | protected override tagifyChild(mg: TagsManager): void {
47 | mg.point(this.first);
48 | mg.point(this.second, 1);
49 | mg.point(this.third, 2);
50 | mg.point(this.fourth, 3);
51 | mg.add(70, this.flags);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/entities/hatch/arc.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, onezero, point } from "@/utils";
2 | import { HatchEdge, HatchEdgeType } from "./edges";
3 | import { Point2D } from "@/types";
4 |
5 | export interface HatchArcOptions {
6 | center: Point2D;
7 | radius: number;
8 | start: number;
9 | end: number;
10 | clockwise?: boolean;
11 | }
12 |
13 | export class HatchArc implements HatchEdge {
14 | readonly type: number;
15 | center: Point2D;
16 | radius: number;
17 | start: number;
18 | end: number;
19 | clockwise: boolean;
20 |
21 | constructor(options: HatchArcOptions) {
22 | this.type = HatchEdgeType.CircularArc;
23 | this.center = options.center;
24 | this.radius = options.radius;
25 | this.start = options.start;
26 | this.end = options.end;
27 | this.clockwise = options.clockwise || true;
28 | }
29 |
30 | bbox(): BoundingBox {
31 | const c = point(this.center.x, this.center.y);
32 | return BBox.point(c, this.radius);
33 | }
34 |
35 | tagify(mg: TagsManager): void {
36 | mg.add(72, this.type);
37 | mg.point2d(this.center);
38 | mg.add(40, this.radius);
39 | mg.add(50, this.start);
40 | mg.add(51, this.end);
41 | mg.add(73, onezero(this.clockwise));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/entities/hatch/boundary.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager } from "@/utils";
2 | import { HatchArc, HatchArcOptions } from "./arc";
3 | import { HatchEllipse, HatchEllipseOptions } from "./ellipse";
4 | import { HatchLine, HatchLineOptions } from "./line";
5 | import { HatchEdges } from "./edges";
6 | import { HatchPolyline } from "./polyline";
7 | import { Taggable } from "@/types";
8 |
9 | export const BoundaryPathFlag = {
10 | Default: 0,
11 | External: 1,
12 | Polyline: 2,
13 | Derived: 4,
14 | Textbox: 8,
15 | Outermost: 16,
16 | } as const;
17 |
18 | export class HatchBoundaryPath implements Taggable {
19 | flag: number;
20 | polylines: HatchPolyline[];
21 | edges: HatchEdges;
22 |
23 | constructor() {
24 | this.flag = BoundaryPathFlag.External | BoundaryPathFlag.Derived;
25 | this.polylines = [];
26 | this.edges = new HatchEdges();
27 | }
28 |
29 | arc(options: HatchArcOptions) {
30 | return this.edges.add(new HatchArc(options));
31 | }
32 |
33 | ellipse(options: HatchEllipseOptions) {
34 | return this.edges.add(new HatchEllipse(options));
35 | }
36 |
37 | line(options: HatchLineOptions) {
38 | return this.edges.add(new HatchLine(options));
39 | }
40 |
41 | polyline(p: HatchPolyline) {
42 | this.flag |= BoundaryPathFlag.Polyline;
43 | this.polylines.push(p);
44 | return p;
45 | }
46 |
47 | bbox(): BoundingBox {
48 | const p = BBox.boxes(this.polylines.map((p) => p.bbox()));
49 | const e = this.edges.bbox();
50 | return BBox.boxes([p, e]);
51 | }
52 |
53 | tagify(mg: TagsManager): void {
54 | mg.add(92, this.flag);
55 | if (this.flag & BoundaryPathFlag.Polyline) {
56 | this.polylines.forEach((p) => p.tagify(mg));
57 | } else this.edges.tagify(mg);
58 | mg.add(97, 0);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/entities/hatch/edges.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager } from "@/utils";
2 | import { Taggable } from "@/types";
3 |
4 | export const HatchEdgeType = {
5 | Line: 1,
6 | CircularArc: 2,
7 | EllipticArc: 3,
8 | Spline: 4,
9 | } as const;
10 |
11 | export interface HatchEdge extends Taggable {
12 | readonly type: number;
13 | bbox(): BoundingBox;
14 | }
15 |
16 | export class HatchEdges implements Taggable {
17 | edges: HatchEdge[];
18 |
19 | constructor() {
20 | this.edges = [];
21 | }
22 |
23 | add(e: TEdge) {
24 | this.edges.push(e);
25 | return e;
26 | }
27 |
28 | bbox(): BoundingBox {
29 | return BBox.boxes(this.edges.map((e) => e.bbox()));
30 | }
31 |
32 | tagify(mg: TagsManager): void {
33 | mg.add(93, this.edges.length);
34 | this.edges.forEach((e) => e.tagify(mg));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/entities/hatch/ellipse.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, onezero, point } from "@/utils";
2 | import { HatchEdge, HatchEdgeType } from "./edges";
3 | import { Point2D } from "@/types";
4 |
5 | export interface HatchEllipseOptions {
6 | center: Point2D;
7 | endpoint: Point2D;
8 | ratio: number;
9 | start: number;
10 | end: number;
11 | clockwise?: boolean;
12 | }
13 |
14 | export class HatchEllipse implements HatchEdge {
15 | type: number;
16 | center: Point2D;
17 | endpoint: Point2D;
18 | ratio: number;
19 | start: number;
20 | end: number;
21 | clockwise: boolean;
22 |
23 | constructor(options: HatchEllipseOptions) {
24 | this.type = HatchEdgeType.EllipticArc;
25 | this.center = options.center;
26 | this.endpoint = options.endpoint;
27 | this.ratio = options.ratio;
28 | this.start = options.start;
29 | this.end = options.end;
30 | this.clockwise = options.clockwise || true;
31 | }
32 |
33 | bbox(): BoundingBox {
34 | const { x, y } = this.endpoint;
35 | const radius = Math.sqrt(x * x + y * y);
36 | const c = point(this.center.x, this.center.y);
37 | return BBox.point(c, radius);
38 | }
39 |
40 | tagify(mg: TagsManager): void {
41 | mg.add(72, this.type);
42 | mg.point2d(this.center);
43 | mg.point2d(this.endpoint, 1);
44 | mg.add(40, this.ratio);
45 | mg.add(50, this.start);
46 | mg.add(51, this.end);
47 | mg.add(73, onezero(this.clockwise));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/entities/hatch/gradient.ts:
--------------------------------------------------------------------------------
1 | import { TagsManager, rad } from "@/utils";
2 | import { Taggable } from "@/types";
3 |
4 | export const HatchGradientType = {
5 | Linear: "LINEAR",
6 | Cylinder: "CYLINDER",
7 | InvCylinder: "INVCYLINDER",
8 | Spherical: "SPHERICAL",
9 | HemiSpherical: "HEMISPHERICAL",
10 | Curved: "CURVED",
11 | InvSpherical: "SPHERICAL",
12 | InvHemiSpherical: "INVHEMISPHERICAL",
13 | InvCurved: "INVCURVED",
14 | } as const;
15 |
16 | export interface HatchGradientOptions {
17 | first: number;
18 | second?: number;
19 | angle?: number;
20 | definition?: number;
21 | tint?: number;
22 | type?: string;
23 | }
24 |
25 | export class HatchGradient implements Taggable {
26 | first: number;
27 | second: number;
28 | angle: number;
29 | definition: number;
30 | tint: number;
31 | type: string;
32 |
33 | constructor(options: HatchGradientOptions) {
34 | this.first = options.first;
35 | this.second = options.second || 7;
36 | this.angle = options.angle ?? 0;
37 | this.definition = options.definition ?? 0;
38 | this.tint = options.tint ?? 0;
39 | this.type = options.type || HatchGradientType.Linear;
40 | }
41 |
42 | tagify(mg: TagsManager): void {
43 | mg.add(450, 1);
44 | mg.add(451, 0);
45 | mg.add(460, rad(this.angle));
46 | mg.add(461, this.definition);
47 | mg.add(452, this.second ? 0 : 1);
48 | mg.add(462, this.tint);
49 | mg.add(453, 2);
50 | mg.add(463, 0);
51 | mg.add(63, this.first);
52 | mg.add(463, 1);
53 | mg.add(63, this.second);
54 | mg.add(470, this.type);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/entities/hatch/hatch.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BBox,
3 | BoundingBox,
4 | TagsManager,
5 | extrusion,
6 | onezero,
7 | point,
8 | } from "@/utils";
9 | import { Entity, EntityOptions } from "../entity";
10 | import { HatchPattern, SOLID } from "./pattern";
11 | import { HatchBoundaryPath } from "./boundary";
12 | import { HatchGradient } from "./gradient";
13 | import { Point3D } from "@/types";
14 |
15 | export const AssociativityFlag = {
16 | NonAssociative: 0,
17 | Associative: 1,
18 | } as const;
19 |
20 | export const HatchStyle = {
21 | OddParity: 0,
22 | Outermost: 1,
23 | Through: 2,
24 | } as const;
25 |
26 | export const PatternType = {
27 | UserDefined: 0,
28 | Predefined: 1,
29 | Custom: 2,
30 | } as const;
31 |
32 | export interface HatchOptions extends EntityOptions {
33 | elevation?: number;
34 | extrusion?: Point3D;
35 | fill: HatchPattern | HatchGradient;
36 | }
37 |
38 | export class Hatch extends Entity {
39 | elevation: number;
40 | extrusion: Point3D;
41 | associativity: number;
42 | boundaries: HatchBoundaryPath[];
43 | style: number;
44 | patternType: number;
45 | fill: HatchPattern | HatchGradient;
46 |
47 | get isSolid() {
48 | return this.patternName === SOLID;
49 | }
50 |
51 | get patternName() {
52 | if ("name" in this.fill) return this.fill.name;
53 | return SOLID;
54 | }
55 |
56 | override get subClassMarker(): string | undefined {
57 | return "AcDbHatch";
58 | }
59 |
60 | constructor(options: HatchOptions) {
61 | super(options);
62 | this._type = "HATCH";
63 | this.elevation = options.elevation ?? 0;
64 | this.extrusion = options.extrusion || extrusion();
65 | this.associativity = AssociativityFlag.NonAssociative;
66 | this.boundaries = [];
67 | this.style = HatchStyle.Outermost;
68 | this.patternType = PatternType.Predefined;
69 | this.fill = options.fill;
70 | }
71 |
72 | add() {
73 | const b = new HatchBoundaryPath();
74 | this.boundaries.push(b);
75 | return b;
76 | }
77 |
78 | override bbox(): BoundingBox {
79 | return BBox.boxes(this.boundaries.map((b) => b.bbox()));
80 | }
81 |
82 | protected override tagifyChild(mg: TagsManager): void {
83 | mg.point(point(0, 0, this.elevation));
84 | mg.point(this.extrusion, 200);
85 | mg.add(2, this.patternName);
86 | mg.add(70, onezero(this.isSolid));
87 | mg.add(71, this.associativity);
88 | mg.add(91, this.boundaries.length);
89 | this.boundaries.forEach((b) => b.tagify(mg));
90 | mg.add(75, this.style);
91 | mg.add(76, this.patternType);
92 | if (!this.isSolid) this.fill.tagify(mg);
93 | mg.add(47, 1);
94 | mg.add(98, 0);
95 | if (this.isSolid) this.fill.tagify(mg);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/entities/hatch/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./arc";
2 | export * from "./boundary";
3 | export * from "./edges";
4 | export * from "./ellipse";
5 | export * from "./gradient";
6 | export * from "./hatch";
7 | export * from "./line";
8 | export * from "./pattern";
9 | export * from "./polyline";
10 |
--------------------------------------------------------------------------------
/src/entities/hatch/line.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, point } from "@/utils";
2 | import { HatchEdge, HatchEdgeType } from "./edges";
3 | import { Point2D } from "@/types";
4 |
5 | export interface HatchLineOptions {
6 | start: Point2D;
7 | end: Point2D;
8 | }
9 |
10 | export class HatchLine implements HatchEdge {
11 | readonly type: number;
12 | start: Point2D;
13 | end: Point2D;
14 |
15 | constructor(options: HatchLineOptions) {
16 | this.type = HatchEdgeType.Line;
17 | this.start = options.start;
18 | this.end = options.end;
19 | }
20 |
21 | bbox(): BoundingBox {
22 | const s = point(this.start.x, this.start.y);
23 | const e = point(this.end.x, this.end.y);
24 | return BBox.points([s, e]);
25 | }
26 |
27 | tagify(mg: TagsManager): void {
28 | mg.add(72, this.type);
29 | mg.point2d(this.start);
30 | mg.point2d(this.end, 1);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/entities/hatch/pattern.ts:
--------------------------------------------------------------------------------
1 | import { Point2D, Taggable } from "@/types";
2 | import { TagsManager, onezero } from "@/utils";
3 |
4 | export const SOLID = "SOLID";
5 |
6 | export interface HatchPatternDataOptions {
7 | angle: number;
8 | base: Point2D;
9 | offset: Point2D;
10 | dashLengths: number[];
11 | }
12 |
13 | export class HatchPatternData implements Taggable {
14 | angle: number;
15 | base: Point2D;
16 | offset: Point2D;
17 | dashLengths: number[];
18 |
19 | constructor(options: HatchPatternDataOptions) {
20 | this.angle = options.angle;
21 | this.base = options.base;
22 | this.offset = options.offset;
23 | this.dashLengths = options.dashLengths;
24 | }
25 |
26 | tagify(mg: TagsManager): void {
27 | mg.add(53, this.angle);
28 | mg.add(43, this.base.x);
29 | mg.add(44, this.base.y);
30 | mg.add(45, this.offset.x);
31 | mg.add(46, this.offset.y);
32 | mg.add(79, this.dashLengths.length);
33 | this.dashLengths.forEach((d) => mg.add(49, d));
34 | }
35 | }
36 |
37 | export interface HatchPatternOptions {
38 | name: string;
39 | data?: HatchPatternData[];
40 | angle?: number;
41 | scale?: number;
42 | double?: boolean;
43 | }
44 |
45 | export class HatchPattern implements Taggable {
46 | name: string;
47 | data: HatchPatternData[];
48 | angle: number;
49 | scale: number;
50 | double: boolean;
51 |
52 | constructor(options: HatchPatternOptions) {
53 | this.name = options.name;
54 | this.data = options.data || [];
55 | this.angle = options.angle ?? 0;
56 | this.scale = options.scale ?? 1;
57 | this.double = options.double || false;
58 | }
59 |
60 | add(options: HatchPatternDataOptions) {
61 | const d = new HatchPatternData(options);
62 | this.data.push(d);
63 | return d;
64 | }
65 |
66 | tagify(mg: TagsManager): void {
67 | if (this.name === SOLID) return;
68 | mg.add(52, this.angle);
69 | mg.add(41, this.scale);
70 | mg.add(77, onezero(this.double));
71 | mg.add(78, this.data.length);
72 | this.data.forEach((d) => d.tagify(mg));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/entities/hatch/polyline.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, onezero, point } from "@/utils";
2 | import { Point2D, Taggable } from "@/types";
3 |
4 | export interface HatchPolylineVertex extends Point2D {
5 | bulge?: number;
6 | }
7 |
8 | export interface HatchPolylineOptions {
9 | vertices?: HatchPolylineVertex[];
10 | isClosed?: boolean;
11 | }
12 |
13 | export class HatchPolyline implements Taggable {
14 | isClosed?: boolean;
15 | vertices: HatchPolylineVertex[];
16 |
17 | constructor(options: HatchPolylineOptions) {
18 | this.isClosed = options.isClosed;
19 | this.vertices = options.vertices || [];
20 | }
21 |
22 | add(vertex: HatchPolylineVertex) {
23 | this.vertices.push(vertex);
24 | }
25 |
26 | bbox(): BoundingBox {
27 | return BBox.points(this.vertices.map((v) => point(v.x, v.y)));
28 | }
29 |
30 | tagify(mg: TagsManager): void {
31 | mg.add(72, onezero(this.hasBulge()));
32 | mg.add(73, onezero(this.isClosed));
33 | mg.add(93, this.vertices.length);
34 | this.vertices.forEach((v) => {
35 | mg.point2d(v);
36 | mg.add(42, v.bulge);
37 | });
38 | }
39 |
40 | private hasBulge(): boolean {
41 | return this.vertices.some((v) => v.bulge != null);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/entities/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./arc";
2 | export * from "./circle";
3 | export * from "./dimension";
4 | export * from "./ellipse";
5 | export * from "./entities";
6 | export * from "./entity";
7 | export * from "./face";
8 | export * from "./hatch";
9 | export * from "./insert";
10 | export * from "./leader";
11 | export * from "./line";
12 | export * from "./lwpolyline";
13 | export * from "./manager";
14 | export * from "./mesh";
15 | export * from "./mleader";
16 | export * from "./mtext";
17 | export * from "./point";
18 | export * from "./polyline";
19 | export * from "./ray";
20 | export * from "./seqend";
21 | export * from "./solid";
22 | export * from "./spline";
23 | export * from "./table";
24 | export * from "./text";
25 | export * from "./vertex";
26 |
--------------------------------------------------------------------------------
/src/entities/insert.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, onezero, point } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 | import { SeqEnd } from "./seqend";
5 |
6 | export interface InsertOptions extends EntityOptions {
7 | followAttributes?: boolean;
8 | blockName: string;
9 | insertionPoint?: Point3D;
10 | scale?: Partial;
11 | rotation?: number;
12 | columnCount?: number;
13 | rowCount?: number;
14 | columnSpacing?: number;
15 | rowSpacing?: number;
16 | extrusion?: Point3D;
17 | }
18 |
19 | export class Insert extends Entity {
20 | followAttributes?: boolean;
21 | blockName: string;
22 | insertionPoint: Point3D;
23 | scale?: Partial;
24 | rotation?: number;
25 | columnCount?: number;
26 | rowCount?: number;
27 | columnSpacing?: number;
28 | rowSpacing?: number;
29 | extrusion?: Point3D;
30 |
31 | readonly seqend?: SeqEnd;
32 |
33 | override get subClassMarker(): string {
34 | return "AcDbBlockReference";
35 | }
36 |
37 | constructor(options: InsertOptions) {
38 | super(options);
39 | this._type = "INSERT";
40 | this.followAttributes = options.followAttributes;
41 | this.blockName = options.blockName;
42 | this.insertionPoint = options.insertionPoint || point();
43 | this.scale = options.scale;
44 | this.rotation = options.rotation;
45 | this.columnCount = options.columnCount;
46 | this.rowCount = options.rowCount;
47 | this.columnSpacing = options.columnSpacing;
48 | this.rowSpacing = options.rowSpacing;
49 | this.extrusion = options.extrusion;
50 | if (this.followAttributes) {
51 | this.seqend = new SeqEnd(options);
52 | this.seqend.ownerObjectHandle = this.handle;
53 | }
54 | }
55 |
56 | override bbox(): BoundingBox {
57 | return BBox.point(this.insertionPoint);
58 | }
59 |
60 | protected override tagifyChild(mg: TagsManager): void {
61 | mg.add(66, onezero(this.followAttributes));
62 | mg.add(2, this.blockName);
63 | mg.point(this.insertionPoint);
64 | mg.add(41, this.scale?.x);
65 | mg.add(42, this.scale?.y);
66 | mg.add(43, this.scale?.z);
67 | mg.add(50, this.rotation);
68 | mg.add(70, this.columnCount);
69 | mg.add(71, this.rowCount);
70 | mg.add(44, this.columnSpacing);
71 | mg.add(45, this.rowSpacing);
72 | mg.point(this.extrusion, 200);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/entities/leader.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, onezero } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D, Union } from "@/types";
4 |
5 | export const PathType = {
6 | Segments: 0,
7 | Spline: 1,
8 | } as const;
9 |
10 | export const CreationFlag = {
11 | Text: 0,
12 | Tolerance: 1,
13 | BlockRef: 2,
14 | Without: 3,
15 | } as const;
16 |
17 | export const HooklineDirectionFlag = {
18 | Opposite: 0,
19 | Same: 1,
20 | } as const;
21 |
22 | export interface LeaderOptions extends EntityOptions {
23 | dimStyleName?: string;
24 | arrowhead?: boolean;
25 | pathType?: Union;
26 | creation?: Union;
27 | hooklineDirection?: Union;
28 | hookline?: boolean;
29 | height?: number;
30 | width?: number;
31 | vertices: Point3D[];
32 | color?: number;
33 | annotationHandle?: string;
34 | normal?: Point3D;
35 | horizontalDirection?: Point3D;
36 | blockOffset?: Point3D;
37 | annotationOffset?: Point3D;
38 | }
39 |
40 | export class Leader extends Entity {
41 | dimStyleName?: string;
42 | arrowhead?: boolean;
43 | pathType?: Union;
44 | creation?: Union;
45 | hooklineDirection?: Union;
46 | hookline?: boolean;
47 | height?: number;
48 | width?: number;
49 | vertices: Point3D[];
50 | color?: number;
51 | annotationHandle?: string;
52 | normal?: Point3D;
53 | horizontalDirection?: Point3D;
54 | blockOffset?: Point3D;
55 | annotationOffset?: Point3D;
56 |
57 | override get subClassMarker(): string | undefined {
58 | return "AcDbLeader";
59 | }
60 |
61 | constructor(options: LeaderOptions) {
62 | super(options);
63 | this._type = "LEADER";
64 | this.dimStyleName = options.dimStyleName;
65 | this.arrowhead = options.arrowhead;
66 | this.pathType = options.pathType;
67 | this.creation = options.creation;
68 | this.hooklineDirection = options.hooklineDirection;
69 | this.hookline = options.hookline;
70 | this.height = options.height;
71 | this.width = options.width;
72 | this.vertices = options.vertices;
73 | this.color = options.color;
74 | this.annotationHandle = options.annotationHandle;
75 | this.normal = options.normal;
76 | this.horizontalDirection = options.horizontalDirection;
77 | this.blockOffset = options.blockOffset;
78 | this.annotationOffset = options.annotationOffset;
79 | }
80 |
81 | override bbox(): BoundingBox {
82 | return BBox.points(this.vertices);
83 | }
84 |
85 | protected override tagifyChild(mg: TagsManager): void {
86 | mg.add(3, this.dimStyleName);
87 | mg.add(71, onezero(this.arrowhead));
88 | mg.add(72, this.pathType);
89 | mg.add(73, this.creation);
90 | mg.add(74, this.hooklineDirection);
91 | mg.add(75, onezero(this.hookline));
92 | mg.add(40, this.height);
93 | mg.add(41, this.width);
94 | mg.add(76, this.vertices.length);
95 | this.vertices.forEach((v) => mg.point(v));
96 | mg.add(77, this.color);
97 | mg.add(340, this.annotationHandle);
98 | mg.point(this.normal, 200);
99 | mg.point(this.horizontalDirection, 201);
100 | mg.point(this.blockOffset, 202);
101 | mg.point(this.annotationOffset, 203);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/entities/line.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, extrusion } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface LineOptions extends EntityOptions {
6 | thickness?: number;
7 | start: Point3D;
8 | end: Point3D;
9 | extrusion?: Point3D;
10 | }
11 |
12 | export class Line extends Entity {
13 | thickness: number;
14 | start: Point3D;
15 | end: Point3D;
16 | extrusion: Point3D;
17 |
18 | override get subClassMarker(): string {
19 | return "AcDbLine";
20 | }
21 |
22 | constructor(options: LineOptions) {
23 | super(options);
24 | this._type = "LINE";
25 | this.thickness = options.thickness ?? 0;
26 | this.start = options.start;
27 | this.end = options.end;
28 | this.extrusion = options.extrusion || extrusion();
29 | }
30 |
31 | override bbox(): BoundingBox {
32 | return BBox.line(this.start, this.end);
33 | }
34 |
35 | protected override tagifyChild(mg: TagsManager): void {
36 | mg.add(39, this.thickness);
37 | mg.point(this.start);
38 | mg.point(this.end, 1);
39 | mg.point(this.extrusion, 200);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/entities/lwpolyline.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, extrusion, point } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point2D, Point3D } from "@/types";
4 |
5 | export interface LWPolylineVertex extends Point2D {
6 | startingWidth?: number;
7 | endWidth?: number;
8 | bulge?: number;
9 | }
10 |
11 | export interface LWPolylineOptions extends EntityOptions {
12 | vertices?: LWPolylineVertex[];
13 | flags?: number;
14 | constantWidth?: number;
15 | elevation?: number;
16 | thickness?: number;
17 | extrusion?: Point3D;
18 | }
19 |
20 | export const LWPolylineFlags = {
21 | None: 0,
22 | Closed: 1,
23 | Plinegen: 128,
24 | } as const;
25 |
26 | export class LWPolyline extends Entity {
27 | vertices: LWPolylineVertex[];
28 | flags: number;
29 | constantWidth: number;
30 | elevation: number;
31 | thickness: number;
32 | extrusion: Point3D;
33 |
34 | override get subClassMarker(): string {
35 | return "AcDbPolyline";
36 | }
37 |
38 | constructor(options: LWPolylineOptions) {
39 | super(options);
40 | this._type = "LWPOLYLINE";
41 | this.vertices = options.vertices || [];
42 | this.flags = options.flags ?? LWPolylineFlags.None;
43 | this.constantWidth = options.constantWidth ?? 0;
44 | this.elevation = options.elevation ?? 0;
45 | this.thickness = options.thickness ?? 0;
46 | this.extrusion = options.extrusion || extrusion();
47 | }
48 |
49 | add(v: LWPolylineVertex) {
50 | this.vertices.push(v);
51 | }
52 |
53 | override bbox(): BoundingBox {
54 | return BBox.points(this.vertices.map((v) => point(v.x, v.y)));
55 | }
56 |
57 | protected override tagifyChild(mg: TagsManager): void {
58 | mg.add(90, this.vertices.length);
59 | mg.add(70, this.flags);
60 | mg.add(43, this.constantWidth);
61 | mg.add(38, this.elevation);
62 | mg.add(39, this.thickness);
63 | this.vertices.forEach((v) => {
64 | mg.point2d(v);
65 | mg.add(40, v.startingWidth);
66 | mg.add(41, v.endWidth);
67 | mg.add(42, v.bulge);
68 | });
69 | mg.point(this.extrusion, 200);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/entities/mesh.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface MeshOptions extends EntityOptions {
6 | vertices?: Point3D[];
7 | size?: number;
8 | faces?: number[][];
9 | }
10 |
11 | export class Mesh extends Entity {
12 | vertices: Point3D[];
13 | size: number;
14 | faces: number[][];
15 | edges?: [firstIndex: number, secondIndex: number][];
16 |
17 | override get subClassMarker(): string | undefined {
18 | return "AcDbSubDMesh";
19 | }
20 |
21 | constructor(options: MeshOptions) {
22 | super(options);
23 | this._type = "MESH";
24 | this.vertices = options.vertices || [];
25 | this.size = options.size ?? 3;
26 | this.faces = options.faces || [];
27 | }
28 |
29 | override bbox(): BoundingBox {
30 | return BBox.points(this.vertices);
31 | }
32 |
33 | protected override tagifyChild(mg: TagsManager): void {
34 | mg.add(71, 2);
35 | mg.add(72, 0);
36 | mg.add(91, 0);
37 | mg.add(92, this.vertices.length);
38 | this.vertices.forEach((v) => mg.point(v));
39 | mg.add(93, this.faces.length * (this.size + 1));
40 | this.faces.forEach((f) => {
41 | mg.add(90, f.length);
42 | f.forEach((i) => mg.add(90, i));
43 | });
44 | mg.add(94, 0);
45 | mg.add(95, 0);
46 | mg.add(90, 0);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/entities/point.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, point } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface PointOptions extends EntityOptions, Partial {
6 | thickness?: number;
7 | extrusion?: Point3D;
8 | angleXAxis?: number;
9 | }
10 |
11 | export class Point extends Entity {
12 | x: number;
13 | y: number;
14 | z: number;
15 | thickness?: number;
16 | extrusion?: Point3D;
17 | angleXAxis?: number;
18 |
19 | override get subClassMarker(): string {
20 | return "AcDbPoint";
21 | }
22 |
23 | get location() {
24 | return point(this.x, this.y, this.z);
25 | }
26 |
27 | constructor(options: PointOptions) {
28 | super(options);
29 | this._type = "POINT";
30 | this.x = options.x ?? 0;
31 | this.y = options.y ?? 0;
32 | this.z = options.z ?? 0;
33 | this.thickness = options.thickness;
34 | this.extrusion = options.extrusion;
35 | this.angleXAxis = options.angleXAxis;
36 | }
37 |
38 | override bbox(): BoundingBox {
39 | return BBox.point(this.location);
40 | }
41 |
42 | protected override tagifyChild(mg: TagsManager): void {
43 | mg.point(this.location);
44 | mg.add(39, this.thickness);
45 | mg.point(this.extrusion, 200);
46 | mg.add(50, this.angleXAxis);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/entities/polyline.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, extrusion, point } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { OmitSeeder, Point3D } from "@/types";
4 | import { Vertex, VertexOptions } from "./vertex";
5 | import { SeqEnd } from "./seqend";
6 |
7 | export const PolylineFlags = {
8 | None: 0,
9 | Closed: 1,
10 | CurveFitVertices: 2,
11 | SplineFitVertices: 4,
12 | Polyline3D: 8,
13 | Polygon3DMesh: 16,
14 | ClosedNurbs: 32,
15 | PolyfaceMesh: 64,
16 | ContinuousPattern: 128,
17 | } as const;
18 |
19 | export interface PolylineOptions extends EntityOptions {
20 | elevation?: number;
21 | thickness?: number;
22 | flags?: number;
23 | startWidth?: number;
24 | endWidth?: number;
25 | extrusion?: Point3D;
26 | vertices?: Vertex[];
27 | faces?: Vertex[];
28 | }
29 |
30 | export class Polyline extends Entity {
31 | elevation?: number;
32 | thickness?: number;
33 | flags: number;
34 | startWidth?: number;
35 | endWidth?: number;
36 | extrusion: Point3D;
37 | vertices: Vertex[];
38 | faces: Vertex[];
39 |
40 | readonly seqend: SeqEnd;
41 |
42 | override get subClassMarker(): string {
43 | if (this.flags & PolylineFlags.Polyline3D) return "AcDb3dPolyline";
44 | if (this.flags & PolylineFlags.PolyfaceMesh) return "AcDbPolyFaceMesh";
45 | else return "AcDb2dPolyline";
46 | }
47 |
48 | constructor(options: PolylineOptions) {
49 | super(options);
50 | this._type = "POLYLINE";
51 | this.elevation = options.elevation;
52 | this.thickness = options.thickness;
53 | this.flags = options.flags || PolylineFlags.None;
54 | this.startWidth = options.startWidth;
55 | this.endWidth = options.endWidth;
56 | this.extrusion = options.extrusion || extrusion();
57 | this.vertices = options.vertices || [];
58 | this.faces = options.faces || [];
59 |
60 | this.seqend = new SeqEnd(options);
61 | this.seqend.ownerObjectHandle = this.handle;
62 | }
63 |
64 | add(options: OmitSeeder) {
65 | const v = new Vertex({ ...options, seeder: this.seeder });
66 | v.ownerObjectHandle = this.ownerObjectHandle;
67 | v.layerName = this.layerName;
68 | if (v.faceRecord) this.faces.push(v);
69 | else this.vertices.push(v);
70 | return v;
71 | }
72 |
73 | override bbox(): BoundingBox {
74 | return BBox.points(this.vertices);
75 | }
76 |
77 | protected override tagifyChild(mg: TagsManager): void {
78 | mg.point(point(0, 0, this.elevation));
79 | mg.add(39, this.thickness);
80 | mg.add(70, this.flags);
81 | mg.add(71, this.vertices.length);
82 | mg.add(72, this.faces.length);
83 | mg.add(40, this.startWidth);
84 | mg.add(41, this.endWidth);
85 | mg.point(this.extrusion, 200);
86 | this.vertices.forEach((v) => v.tagify(mg));
87 | this.faces.forEach((f) => f.tagify(mg));
88 | this.seqend.tagify(mg);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/entities/ray.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface RayOptions extends EntityOptions {
6 | start: Point3D;
7 | unitDirectionVector: Point3D;
8 | }
9 |
10 | export class Ray extends Entity {
11 | start: Point3D;
12 | unitDirectionVector: Point3D;
13 |
14 | override get subClassMarker(): string {
15 | return "AcDbRay";
16 | }
17 |
18 | constructor(options: RayOptions) {
19 | super(options);
20 | this._type = "RAY";
21 | this.start = options.start;
22 | this.unitDirectionVector = options.unitDirectionVector;
23 | }
24 |
25 | override bbox(): BoundingBox {
26 | return BBox.line(this.start, this.unitDirectionVector);
27 | }
28 |
29 | protected override tagifyChild(mg: TagsManager): void {
30 | mg.point(this.start);
31 | mg.point(this.unitDirectionVector, 1);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/entities/seqend.ts:
--------------------------------------------------------------------------------
1 | import { Entity, EntityOptions } from "./entity";
2 |
3 | export interface SeqEndOptions extends EntityOptions {}
4 |
5 | export class SeqEnd extends Entity {
6 | override get subClassMarker(): string | undefined {
7 | return;
8 | }
9 |
10 | constructor(options: SeqEndOptions) {
11 | super(options);
12 | this._type = "SEQEND";
13 | }
14 |
15 | protected tagifyChild(): void {}
16 | }
17 |
--------------------------------------------------------------------------------
/src/entities/solid.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, extrusion } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D } from "@/types";
4 |
5 | export interface SolidOptions extends EntityOptions {
6 | first: Point3D;
7 | second: Point3D;
8 | third: Point3D;
9 | fourth?: Point3D;
10 | thickness?: number;
11 | extrusion?: Point3D;
12 | }
13 |
14 | export class Solid extends Entity {
15 | first: Point3D;
16 | second: Point3D;
17 | third: Point3D;
18 | fourth: Point3D;
19 | thickness?: number;
20 | extrusion: Point3D;
21 |
22 | override get subClassMarker() {
23 | return "AcDbTrace";
24 | }
25 |
26 | constructor(options: SolidOptions) {
27 | super(options);
28 | this._type = "SOLID";
29 | this.first = options.first;
30 | this.second = options.second;
31 | this.third = options.third;
32 | this.fourth = options.fourth || this.third;
33 | this.thickness = options.thickness;
34 | this.extrusion = options.extrusion || extrusion();
35 | }
36 |
37 | override bbox(): BoundingBox {
38 | const { first, second, third, fourth } = this;
39 | return BBox.points([first, second, third, fourth]);
40 | }
41 |
42 | protected override tagifyChild(mg: TagsManager) {
43 | mg.point(this.first);
44 | mg.point(this.second, 1);
45 | mg.point(this.third, 2);
46 | mg.point(this.fourth, 3);
47 | mg.add(39, this.thickness);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/entities/spline.ts:
--------------------------------------------------------------------------------
1 | import { BBox, BoundingBox, TagsManager, openUniformKnots } from "@/utils";
2 | import { Entity, EntityOptions } from "./entity";
3 | import { Point3D, Union } from "@/types";
4 |
5 | export const SplineFlags = {
6 | None: 0,
7 | Closed: 1,
8 | Periodic: 2,
9 | Rational: 4,
10 | Planar: 8,
11 | Linear: 16,
12 | } as const;
13 |
14 | export interface SplineOptions extends EntityOptions {
15 | normal?: Point3D;
16 | flags?: Union;
17 | degree?: number;
18 | startTangent?: Point3D;
19 | endTangent?: Point3D;
20 | knots?: number[];
21 | controls: Point3D[];
22 | weights?: number[];
23 | fits?: Point3D[];
24 | }
25 |
26 | export class Spline extends Entity {
27 | normal?: Point3D;
28 | flags: Union;
29 | degree: number;
30 | startTangent?: Point3D;
31 | endTangent?: Point3D;
32 | knots: number[];
33 | controls: Point3D[];
34 | weights: number[];
35 | fits: Point3D[];
36 |
37 | override get subClassMarker(): string {
38 | return "AcDbSpline";
39 | }
40 |
41 | get clen() {
42 | return this.controls.length;
43 | }
44 |
45 | get flen() {
46 | return this.fits.length;
47 | }
48 |
49 | get klen() {
50 | return this.knots.length;
51 | }
52 |
53 | constructor(options: SplineOptions) {
54 | super(options);
55 | this._type = "SPLINE";
56 | this.normal = options.normal;
57 | this.flags = options.flags ?? SplineFlags.None;
58 | this.degree = options.degree ?? 3;
59 | this.startTangent = options.startTangent;
60 | this.endTangent = options.endTangent;
61 | this.knots = options.knots || [];
62 | this.controls = options.controls;
63 | this.weights = options.weights = [];
64 | this.fits = options.fits || [];
65 |
66 | if (this.klen === 0) this.knots = openUniformKnots(this.clen, this.degree);
67 | }
68 |
69 | override bbox(): BoundingBox {
70 | return BBox.boxes([BBox.points(this.controls), BBox.points(this.fits)]);
71 | }
72 |
73 | protected override tagifyChild(mg: TagsManager): void {
74 | mg.point(this.normal, 200);
75 | mg.add(70, this.flags);
76 | mg.add(71, this.degree);
77 | mg.add(72, this.klen);
78 | mg.add(73, this.clen);
79 | mg.add(74, this.flen);
80 | mg.add(42, 0.0000001);
81 | mg.add(43, 0.0000001);
82 | mg.add(44, 0.0000000001);
83 | mg.point(this.startTangent, 2);
84 | mg.point(this.endTangent, 3);
85 | this.knots.forEach((k) => mg.add(40, k));
86 | this.weights.forEach((w) => mg.add(41, w));
87 | this.controls.forEach((c) => mg.point(c));
88 | this.fits.forEach((f) => mg.point(f, 1));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/entities/text.ts:
--------------------------------------------------------------------------------
1 | import { Entity, EntityOptions } from "./entity";
2 | import { Point3D, Union } from "@/types";
3 | import { TagsManager, extrusion } from "@/utils";
4 | import { TextGenerationFlags } from "@/tables";
5 |
6 | export const TextHorizontalJustification = {
7 | Left: 0,
8 | Center: 1,
9 | Right: 2,
10 | Aligned: 3,
11 | Middle: 4,
12 | Fit: 5,
13 | } as const;
14 |
15 | export const TextVerticalJustification = {
16 | BaseLine: 0,
17 | Bottom: 1,
18 | Middle: 2,
19 | Top: 3,
20 | } as const;
21 |
22 | export interface TextOptions extends EntityOptions {
23 | thickness?: number;
24 | firstAlignmentPoint: Point3D;
25 | height: number;
26 | value: string;
27 | rotation?: number;
28 | relativeXScaleFactor?: number;
29 | obliqueAngle?: number;
30 | styleName?: string;
31 | generationFlags?: Union;
32 | horizontalJustification?: Union;
33 | secondAlignmentPoint?: Point3D;
34 | extrusion?: Point3D;
35 | verticalJustification?: Union;
36 | }
37 |
38 | export class Text extends Entity {
39 | thickness?: number;
40 | firstAlignmentPoint: Point3D;
41 | height: number;
42 | value: string;
43 | rotation?: number;
44 | relativeXScaleFactor?: number;
45 | obliqueAngle?: number;
46 | styleName?: string;
47 | generationFlags?: Union;
48 | horizontalJustification?: Union;
49 | secondAlignmentPoint?: Point3D;
50 | extrusion: Point3D;
51 | verticalJustification?: Union;
52 |
53 | override get subClassMarker(): string {
54 | return "AcDbText";
55 | }
56 |
57 | protected get subClassMarker2(): string {
58 | return this.subClassMarker;
59 | }
60 |
61 | constructor(options: TextOptions) {
62 | super(options);
63 | this._type = "TEXT";
64 | this.thickness = options.thickness;
65 | this.firstAlignmentPoint = options.firstAlignmentPoint;
66 | this.height = options.height;
67 | this.value = options.value;
68 | this.rotation = options.rotation;
69 | this.relativeXScaleFactor = options.relativeXScaleFactor;
70 | this.obliqueAngle = options.obliqueAngle;
71 | this.styleName = options.styleName;
72 | this.generationFlags = options.generationFlags;
73 | this.horizontalJustification = options.horizontalJustification;
74 | this.secondAlignmentPoint = options.secondAlignmentPoint;
75 | this.extrusion = options.extrusion || extrusion();
76 | this.verticalJustification = options.verticalJustification;
77 | }
78 |
79 | protected override tagifyChild(mg: TagsManager): void {
80 | mg.add(39, this.thickness);
81 | mg.point(this.firstAlignmentPoint);
82 | mg.add(40, this.height);
83 | mg.add(1, this.value);
84 | mg.add(50, this.rotation);
85 | mg.add(41, this.relativeXScaleFactor);
86 | mg.add(51, this.obliqueAngle);
87 | mg.add(7, this.styleName);
88 | mg.add(71, this.generationFlags);
89 | mg.add(72, this.horizontalJustification);
90 | mg.point(this.secondAlignmentPoint, 1);
91 | mg.point(this.extrusion, 200);
92 | mg.add(100, this.subClassMarker2);
93 | mg.add(73, this.verticalJustification);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/entities/vertex.ts:
--------------------------------------------------------------------------------
1 | import { Entity, EntityOptions } from "./entity";
2 | import { Point3D } from "@/types";
3 | import { TagsManager } from "@/utils";
4 |
5 | export const VertexFlags = {
6 | None: 0,
7 | ExtraVertex: 1,
8 | CurveFit: 2,
9 | NotUsed: 4,
10 | SplineVertex: 8,
11 | SplineControlPoint: 16,
12 | Polyline3DVertex: 32,
13 | Polyline3DMesh: 64,
14 | PolyfaceMeshVertex: 128,
15 | } as const;
16 |
17 | export interface VertexOptions extends EntityOptions, Partial {
18 | startingWidth?: number;
19 | endingWidth?: number;
20 | bulge?: number;
21 | flags?: number;
22 | tangentDirection?: number;
23 | indices?: number[];
24 | faceRecord?: boolean;
25 | identifier?: number;
26 | }
27 |
28 | export class Vertex extends Entity implements Point3D {
29 | z: number;
30 | x: number;
31 | y: number;
32 | startingWidth?: number;
33 | endingWidth?: number;
34 | bulge: number;
35 | flags: number;
36 | tangentDirection?: number;
37 | indices?: number[];
38 | faceRecord?: boolean;
39 | identifier?: number;
40 |
41 | override get subClassMarker(): string {
42 | if (this.faceRecord) return "AcDbFaceRecord";
43 | return "AcDbVertex";
44 | }
45 |
46 | constructor(options: VertexOptions) {
47 | super(options);
48 | this._type = "VERTEX";
49 | this.x = options.x ?? 0;
50 | this.y = options.y ?? 0;
51 | this.z = options.z ?? 0;
52 | this.startingWidth = options.startingWidth;
53 | this.endingWidth = options.endingWidth;
54 | this.bulge = options.bulge ?? 0;
55 | this.flags = options.flags ?? VertexFlags.None;
56 | this.tangentDirection = options.tangentDirection;
57 | this.indices = options.indices;
58 | this.faceRecord = options.faceRecord;
59 | this.identifier = options.identifier;
60 | }
61 |
62 | private vertexSubclassMarker() {
63 | if (this.faceRecord) return undefined;
64 | if (this.flags & VertexFlags.Polyline3DVertex) {
65 | return "AcDb3dPolylineVertex";
66 | } else if (this.flags & VertexFlags.PolyfaceMeshVertex) {
67 | return "AcDbPolyFaceMeshVertex";
68 | } else return "AcDb2dVertex";
69 | }
70 |
71 | protected override tagifyChild(mg: TagsManager): void {
72 | mg.add(100, this.vertexSubclassMarker());
73 | mg.point(this);
74 | mg.add(40, this.startingWidth);
75 | mg.add(41, this.endingWidth);
76 | mg.add(42, this.bulge);
77 | mg.add(70, this.flags);
78 | mg.add(50, this.tangentDirection);
79 | if (this.faceRecord && this.indices) {
80 | mg.add(71, this.indices.at(0));
81 | mg.add(72, this.indices.at(1));
82 | mg.add(73, this.indices.at(2));
83 | mg.add(74, this.indices.at(3));
84 | }
85 | mg.add(91, this.identifier);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/header/header.ts:
--------------------------------------------------------------------------------
1 | import { Seeder, TagsManager } from "@/utils";
2 | import { Taggable, WithSeeder } from "@/types";
3 | import { Variable } from "./variable";
4 |
5 | export interface HeaderOptions extends WithSeeder {}
6 |
7 | export class Header implements Taggable, WithSeeder {
8 | readonly seeder: Seeder;
9 | readonly variables: Variable[];
10 |
11 | constructor({ seeder }: HeaderOptions) {
12 | this.seeder = seeder;
13 | this.variables = [];
14 | this.add("$ACADVER").add(1, "AC1021");
15 | this.handseed();
16 | }
17 |
18 | add(name: string) {
19 | const f = this.variables.find((v) => v.name === name);
20 | if (f) return f;
21 |
22 | const v = new Variable(name);
23 | this.variables.push(v);
24 | return v;
25 | }
26 |
27 | exists(name: string) {
28 | return this.variables.find((v) => v.name === name) != null;
29 | }
30 |
31 | tagify(mg: TagsManager): void {
32 | this.handseed();
33 | mg.sectionStart("HEADER");
34 | this.variables.forEach((v) => v.tagify(mg));
35 | mg.sectionEnd();
36 | }
37 |
38 | private handseed() {
39 | const handseed = this.add("$HANDSEED");
40 | handseed.clear();
41 | handseed.add(5, this.seeder.peek());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/header/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./header";
2 | export * from "./variable";
3 |
--------------------------------------------------------------------------------
/src/header/variable.ts:
--------------------------------------------------------------------------------
1 | import { Tag, Taggable } from "@/types";
2 | import { TagsManager } from "@/utils";
3 |
4 | export class Variable implements Taggable {
5 | readonly tags: Tag[];
6 |
7 | constructor(public readonly name: string) {
8 | this.tags = [];
9 | }
10 |
11 | add(code: number, value: string | number) {
12 | this.tags.push({ code, value });
13 | return this;
14 | }
15 |
16 | clear() {
17 | this.tags.length = 0;
18 | }
19 |
20 | tagify(mg: TagsManager) {
21 | if (this.tags.length > 0) {
22 | mg.add(9, this.name);
23 | this.tags.forEach((t) => mg.add(t.code, t.value));
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/helpers/angles.ts:
--------------------------------------------------------------------------------
1 | import { TOW_PI, periodic } from "@/helpers";
2 | import { Point2D } from "@/types";
3 |
4 | export function pdeg(value: number) {
5 | return periodic(value, 0, 360);
6 | }
7 |
8 | export function prad(value: number) {
9 | return periodic(value, 0, TOW_PI);
10 | }
11 |
12 | export function angleBetween(
13 | target: number,
14 | a: number,
15 | b: number,
16 | radians?: boolean
17 | ) {
18 | const p = radians ? prad : pdeg;
19 |
20 | (target = p(target)), (a = p(a)), (b = p(b));
21 |
22 | if (a < b) return a <= target && target <= b;
23 | return a <= target || target <= b;
24 | }
25 |
26 | export function calculateAngle(start: Point2D, end: Point2D) {
27 | let angle = Math.atan2(end.y - start.y, end.x - start.x);
28 | if (angle < 0) angle += TOW_PI;
29 | return angle;
30 | }
31 |
--------------------------------------------------------------------------------
/src/helpers/constants.ts:
--------------------------------------------------------------------------------
1 | export const { PI } = Math;
2 | export const TOW_PI = PI * 2;
3 | export const HALF_PI = PI / 2;
4 | export const QUARTER_PI = PI / 4;
5 |
--------------------------------------------------------------------------------
/src/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./angles";
2 | export * from "./constants";
3 | export * from "./periodic";
4 | export * from "./primitives";
5 | export * from "./transform";
6 | export * from "./types";
7 |
--------------------------------------------------------------------------------
/src/helpers/periodic.ts:
--------------------------------------------------------------------------------
1 | export function periodic(value: number, rmin: number, rmax: number) {
2 | const range = rmax - rmin;
3 | while (value > rmax) value -= range;
4 | while (value < rmin) value += range;
5 | return value;
6 | }
7 |
--------------------------------------------------------------------------------
/src/helpers/primitives/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./arc";
2 | export * from "./line";
3 | export * from "./vector";
4 |
--------------------------------------------------------------------------------
/src/helpers/primitives/line.ts:
--------------------------------------------------------------------------------
1 | import { Vector, Writable, rotate } from "@/helpers";
2 | import { Block } from "@/blocks";
3 | import { Point2D } from "@/types";
4 | import { point } from "@/utils";
5 |
6 | export function linep(start: Point2D, end: Point2D) {
7 | return new LinePrimitive(start, end);
8 | }
9 |
10 | export class LinePrimitive implements Writable {
11 | readonly start: Vector;
12 | readonly end: Vector;
13 |
14 | get vector(): Vector {
15 | return Vector.from(this.start, this.end);
16 | }
17 |
18 | get middle(): Vector {
19 | return this.start.add(this.end).scale(0.5);
20 | }
21 |
22 | get length(): number {
23 | return this.vector.length();
24 | }
25 |
26 | constructor(start: Point2D, end: Point2D) {
27 | this.start = new Vector(start.x, start.y);
28 | this.end = new Vector(end.x, end.y);
29 | }
30 |
31 | rotate(center: Point2D, angle: number) {
32 | return new LinePrimitive(
33 | rotate({ target: this.start, center, angle }),
34 | rotate({ target: this.end, center, angle })
35 | );
36 | }
37 |
38 | intersect(rhs: LinePrimitive): Vector | null {
39 | const fv = this.vector;
40 | const sv = rhs.vector;
41 | const tv = Vector.from(this.start, rhs.start);
42 |
43 | const cross = fv.cross(sv);
44 | if (cross === 0) return null;
45 |
46 | const t = tv.cross(sv) / cross;
47 | return this.start.add(fv.scale(t));
48 | }
49 |
50 | trimStart(offset: number) {
51 | const vector = this.vector.normalize().scale(offset);
52 | return new LinePrimitive(this.start.add(vector), this.end);
53 | }
54 |
55 | trimEnd(offset: number) {
56 | const vector = this.vector.normalize().scale(-offset);
57 | return new LinePrimitive(this.start, this.end.add(vector));
58 | }
59 |
60 | expandStart(offset: number) {
61 | const vector = this.vector.normalize().scale(-offset);
62 | return new LinePrimitive(this.start.add(vector), this.end);
63 | }
64 |
65 | expandEnd(offset: number) {
66 | const vector = this.vector.normalize().scale(offset);
67 | return new LinePrimitive(this.start, this.end.add(vector));
68 | }
69 |
70 | write(block: B) {
71 | const start = point(this.start.x, this.start.y);
72 | const end = point(this.end.x, this.end.y);
73 | return block.addLine({ start, end });
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/helpers/primitives/vector.ts:
--------------------------------------------------------------------------------
1 | import { Point2D } from "@/types";
2 |
3 | export class Vector implements Point2D {
4 | x: number;
5 | y: number;
6 |
7 | static from(p1: Point2D, p2: Point2D) {
8 | return new Vector(p2.x - p1.x, p2.y - p1.y);
9 | }
10 |
11 | constructor(x: number, y: number) {
12 | this.x = x;
13 | this.y = y;
14 | }
15 |
16 | add(rhs: Vector) {
17 | return new Vector(this.x + rhs.x, this.y + rhs.y);
18 | }
19 |
20 | length() {
21 | return Math.hypot(this.x, this.y);
22 | }
23 |
24 | normalize() {
25 | const length = this.length();
26 | return new Vector(this.x / length, this.y / length);
27 | }
28 |
29 | distance(rhs: Vector) {
30 | return Math.hypot(rhs.x - this.x, rhs.y - this.y);
31 | }
32 |
33 | cross(rhs: Vector) {
34 | return this.x * rhs.y - this.y * rhs.x;
35 | }
36 |
37 | scale(scalar: number) {
38 | return new Vector(this.x * scalar, this.y * scalar);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/helpers/transform.ts:
--------------------------------------------------------------------------------
1 | import { Point2D } from "@/types";
2 | import { point2d } from "@/utils";
3 |
4 | export interface RotateOptions {
5 | target: Point2D;
6 | center: Point2D;
7 | angle: number; // in radians
8 | }
9 |
10 | export interface TranslateOptions {
11 | target: Point2D;
12 | translation: Point2D;
13 | }
14 |
15 | export type TransformOptions = TranslateOptions & RotateOptions;
16 |
17 | export function rotate(options: RotateOptions): Point2D {
18 | const { target, center, angle } = options;
19 | const cos = Math.cos(angle);
20 | const sin = Math.sin(angle);
21 | const ox = target.x - center.x;
22 | const oy = target.y - center.y;
23 | return point2d(
24 | center.x + (ox * cos - oy * sin),
25 | center.y + (ox * sin + oy * cos)
26 | );
27 | }
28 |
29 | export function translate(options: TranslateOptions) {
30 | const { target, translation } = options;
31 | return point2d(target.x + translation.x, target.y + translation.y);
32 | }
33 |
34 | export function transform(options: TransformOptions) {
35 | const { target, translation, center, angle } = options;
36 | return rotate({
37 | target: translate({ target, translation }),
38 | center,
39 | angle,
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/src/helpers/types.ts:
--------------------------------------------------------------------------------
1 | import { Block, Entity } from "@/index";
2 |
3 | export interface Writable {
4 | write(block: B): E;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./blocks";
2 | export * from "./classes";
3 | export * from "./document";
4 | export * from "./entities";
5 | export * from "./header";
6 | export * from "./objects";
7 | export * from "./tables";
8 | export * from "./types";
9 | export * from "./utils";
10 | export * from "./writer";
11 |
12 |
--------------------------------------------------------------------------------
/src/objects/dictionary.ts:
--------------------------------------------------------------------------------
1 | import { Seeder, TagsManager } from "@/utils";
2 | import { XObject } from "./object";
3 |
4 | export const DuplicateRecordFlags = {
5 | NotApplicable: 0,
6 | KeepExisting: 1,
7 | UseClone: 2,
8 | XRef$0$Name: 3,
9 | $0$Name: 4,
10 | UnmangleName: 5,
11 | } as const;
12 |
13 | export interface ObjectEntry {
14 | name: string;
15 | handle: string;
16 | }
17 |
18 | export class Dictionary extends XObject {
19 | hardOwnerFlag?: number;
20 | duplicateRecordFlag: number;
21 |
22 | entries: ObjectEntry[];
23 |
24 | constructor(handle: Seeder) {
25 | super({ seeder: handle, type: "DICTIONARY" });
26 | this.duplicateRecordFlag = DuplicateRecordFlags.NotApplicable;
27 | this.entries = [];
28 | }
29 |
30 | add(name: string, handle: string) {
31 | this.entries.push({ name, handle });
32 | }
33 |
34 | override tagify(mg: TagsManager): void {
35 | super.tagify(mg);
36 | mg.add(100, "AcDbDictionary");
37 | mg.add(280, this.hardOwnerFlag);
38 | mg.add(281, this.duplicateRecordFlag);
39 | this.entries.forEach((e) => {
40 | mg.add(3, e.name);
41 | mg.add(350, e.handle);
42 | });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/objects/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./dictionary";
2 | export * from "./objects";
3 | export * from "./object";
4 |
--------------------------------------------------------------------------------
/src/objects/object.ts:
--------------------------------------------------------------------------------
1 | import { AppDefined, TagsManager } from "@/utils";
2 | import { Taggable, WithSeeder } from "@/types";
3 |
4 | export interface ObjectOptions extends WithSeeder {
5 | type: string;
6 | }
7 |
8 | export abstract class XObject implements Taggable {
9 | readonly handleSeed: string;
10 | readonly type: string;
11 |
12 | readonly applications: AppDefined[];
13 | readonly reactors: AppDefined;
14 | readonly xdictionary: AppDefined;
15 |
16 | ownerObjectHandle: string;
17 |
18 | constructor({ seeder, type }: ObjectOptions) {
19 | this.handleSeed = seeder.next();
20 | this.type = type;
21 | this.ownerObjectHandle = "0";
22 |
23 | this.applications = [];
24 | this.reactors = this.addAppDefined("ACAD_REACTORS");
25 | this.xdictionary = this.addAppDefined("ACAD_XDICTIONARY");
26 | }
27 |
28 | addAppDefined(name: string) {
29 | const f = this.applications.find((a) => a.name === name);
30 | if (f) return f;
31 |
32 | const a = new AppDefined(name);
33 | this.applications.push(a);
34 | return a;
35 | }
36 |
37 | tagify(mg: TagsManager): void {
38 | mg.add(0, this.type);
39 | mg.add(5, this.handleSeed);
40 | this.applications.forEach((a) => a.tagify(mg));
41 | mg.add(330, this.ownerObjectHandle);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/objects/objects.ts:
--------------------------------------------------------------------------------
1 | import { Dictionary, DuplicateRecordFlags } from "./dictionary";
2 | import { Seeder, TagsManager } from "@/utils";
3 | import { Taggable, WithSeeder } from "@/types";
4 | import { XObject } from "./object";
5 |
6 | export interface ObjectsOptions extends WithSeeder {}
7 |
8 | export class Objects implements Taggable,WithSeeder {
9 | readonly seeder: Seeder;
10 | readonly objects: XObject[];
11 | readonly root: Dictionary;
12 |
13 | constructor({ seeder }: ObjectsOptions) {
14 | this.seeder = seeder;
15 | this.objects = [];
16 | this.root = new Dictionary(seeder);
17 | this.root.duplicateRecordFlag = DuplicateRecordFlags.KeepExisting;
18 | this.root.add("ACAD_GROUP", this.addDictionary().handleSeed);
19 | }
20 |
21 | add(obj: O) {
22 | this.objects.push(obj);
23 | return obj;
24 | }
25 |
26 | addDictionary() {
27 | const d = new Dictionary(this.seeder);
28 | d.ownerObjectHandle = this.root.handleSeed;
29 | return this.add(d);
30 | }
31 |
32 | tagify(mg: TagsManager): void {
33 | mg.sectionStart("OBJECTS");
34 | this.root.tagify(mg);
35 | this.objects.forEach((o) => o.tagify(mg));
36 | mg.sectionEnd();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/shapes/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./rectangle";
2 |
--------------------------------------------------------------------------------
/src/shapes/rectangle.ts:
--------------------------------------------------------------------------------
1 | import {
2 | LWPolylineFlags,
3 | LWPolylineOptions,
4 | LWPolylineVertex,
5 | } from "@/entities";
6 | import { Point2D } from "@/types";
7 | import { bulge } from "@/utils";
8 |
9 | export interface RectangleOptions extends Omit {
10 | origin: Point2D;
11 | width: number;
12 | height?: number;
13 | corner?: number | Point2D;
14 | }
15 |
16 | export class Rectangle {
17 | options: RectangleOptions;
18 |
19 | constructor(options: RectangleOptions) {
20 | this.options = options;
21 | this.options.flags ??= LWPolylineFlags.None;
22 | this.options.flags |= LWPolylineFlags.Closed;
23 | }
24 |
25 | get lwpolylineOptions(): LWPolylineOptions {
26 | const { options, vertices } = this;
27 | return { ...options, vertices };
28 | }
29 |
30 | get vertices(): LWPolylineVertex[] {
31 | const { corner } = this.options;
32 | if (corner == null) return this._normal();
33 | else if (typeof corner === "number") {
34 | return this._rounded(corner);
35 | } else return this._chamfer(corner);
36 | }
37 |
38 | private _normal(): LWPolylineVertex[] {
39 | const { origin, width: w, height } = this.options;
40 | const h = height ?? w;
41 | return [
42 | { x: origin.x, y: origin.y },
43 | { x: origin.x + w, y: origin.y },
44 | { x: origin.x + w, y: origin.y + h },
45 | { x: origin.x, y: origin.y + h },
46 | ];
47 | }
48 |
49 | private _rounded(c: number): LWPolylineVertex[] {
50 | const { origin, width: w, height } = this.options;
51 | const h = height ?? w;
52 | const b = bulge(Math.PI / 2);
53 | return [
54 | { x: origin.x + c, y: origin.y },
55 | { x: origin.x + w - c, y: origin.y, bulge: b },
56 | { x: origin.x + w, y: origin.y + c },
57 | { x: origin.x + w, y: origin.y + h - c, bulge: b },
58 | { x: origin.x + w - c, y: origin.y + h },
59 | { x: origin.x + c, y: origin.y + h, bulge: b },
60 | { x: origin.x, y: origin.y + h - c },
61 | { x: origin.x, y: origin.y + c, bulge: b },
62 | ];
63 | }
64 |
65 | private _chamfer(c: Point2D) {
66 | const { origin, width: w, height } = this.options;
67 | const h = height ?? w;
68 | return [
69 | { x: origin.x + c.x, y: origin.y },
70 | { x: origin.x + w - c.x, y: origin.y },
71 | { x: origin.x + w, y: origin.y + c.y },
72 | { x: origin.x + w, y: origin.y + h - c.y },
73 | { x: origin.x + w - c.x, y: origin.y + h },
74 | { x: origin.x + c.x, y: origin.y + h },
75 | { x: origin.x, y: origin.y + h - c.y },
76 | { x: origin.x, y: origin.y + c.y },
77 | ];
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/svg/elements.ts:
--------------------------------------------------------------------------------
1 | import { Arc, Line, MText, Solid } from "@/entities";
2 | import { ArcPrimitive } from "@/helpers";
3 | import { LayerEntry } from "@/tables";
4 | import { colors } from "./colors";
5 |
6 | export function lineSvg(line: Line, layer?: LayerEntry) {
7 | const { start, end } = line;
8 | const parts: string[] = [];
9 | const color: string = colors[layer?.colorNumber ?? 0];
10 | parts.push("");
19 | return parts.join(" ");
20 | }
21 |
22 | export function solidSvg(s: Solid, layer?: LayerEntry) {
23 | const { first, second, third, fourth } = s;
24 | const parts: string[] = [];
25 | const color: string = colors[layer?.colorNumber ?? 0];
26 | parts.push("");
45 | return parts.join(" ");
46 | }
47 |
48 | export function textSvg(t: MText, layer?: LayerEntry) {
49 | const { insertionPoint: i } = t;
50 | const parts: string[] = [];
51 | const color: string = colors[layer?.colorNumber ?? 0];
52 | parts.push("${t.value}`);
62 | return parts.join(" ");
63 | }
64 |
65 | export function arcSvg(a: Arc, layer?: LayerEntry) {
66 | const parts: string[] = [];
67 | const color: string = colors[layer?.colorNumber ?? 0];
68 | parts.push("");
90 | return parts.join(" ");
91 | }
92 |
--------------------------------------------------------------------------------
/src/svg/exporter.ts:
--------------------------------------------------------------------------------
1 | import { arcSvg, lineSvg, solidSvg, textSvg } from "./elements";
2 | import { isArc, isDimension, isLine, isMText, isSolid } from "./guards";
3 | import { Block } from "@/blocks";
4 | import { Dimension } from "@/entities";
5 | import { Document } from "@/document";
6 |
7 | const xmlns = "http://www.w3.org/2000/svg";
8 |
9 | export class SVGExporter {
10 | readonly doc: Document;
11 | readonly lines: string[];
12 |
13 | width: number;
14 | height: number;
15 |
16 | private get _svg() {
17 | const parts: string[] = ["");
42 | }
43 |
44 | private _block(b: Block) {
45 | b.entities.forEach((e) => {
46 | const layer = this.layer(e.layerName);
47 | if (isArc(e)) this.lines.push(arcSvg(e, layer));
48 | if (isLine(e)) this.lines.push(lineSvg(e, layer));
49 | if (isSolid(e)) this.lines.push(solidSvg(e, layer));
50 | if (isMText(e)) this.lines.push(textSvg(e, layer));
51 | if (isDimension(e)) this._dim(e);
52 | });
53 | }
54 |
55 | private _dim(dim: Dimension) {
56 | const b = this.doc.blocks.get(dim.blockName);
57 | if (b) this._block(b);
58 | }
59 |
60 | clear() {
61 | this.lines.length = 0;
62 | }
63 |
64 | stringify() {
65 | return this.lines.join("\n");
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/svg/guards.ts:
--------------------------------------------------------------------------------
1 | import { Arc, Dimension, Entity, Line, MText, Solid } from "@/entities";
2 |
3 | export function isLine(entity: Entity): entity is Line {
4 | return entity instanceof Line;
5 | }
6 |
7 | export function isSolid(entity: Entity): entity is Solid {
8 | return entity instanceof Solid;
9 | }
10 |
11 | export function isMText(entity: Entity): entity is MText {
12 | return entity instanceof MText;
13 | }
14 |
15 | export function isArc(entity: Entity): entity is Arc {
16 | return entity instanceof Arc;
17 | }
18 |
19 | export function isDimension(entity: Entity): entity is Dimension {
20 | return entity instanceof Dimension;
21 | }
22 |
--------------------------------------------------------------------------------
/src/svg/index.ts:
--------------------------------------------------------------------------------
1 | import { Document } from "@/document";
2 | import { SVGExporter } from "./exporter";
3 |
4 | export function svg(doc: Document) {
5 | const exporter = new SVGExporter(doc);
6 | exporter.start();
7 | return exporter.stringify();
8 | }
9 |
--------------------------------------------------------------------------------
/src/tables/appid.ts:
--------------------------------------------------------------------------------
1 | import { OmitSeeder, WithSeeder } from "@/types";
2 | import { Entry } from "./entry";
3 | import { TagsManager } from "@/utils";
4 | import { XTable } from "./table";
5 |
6 | export const AppIdFlags = {
7 | None: 0,
8 | XRefDependent: 16,
9 | XRefResolved: 32,
10 | Referenced: 64,
11 | } as const;
12 |
13 | export interface AppIdOptions extends WithSeeder {
14 | name: string;
15 | flags?: number;
16 | }
17 |
18 | export class AppIdEntry extends Entry {
19 | readonly name: string;
20 | flags: number;
21 |
22 | constructor(options: AppIdOptions) {
23 | super({ seeder: options.seeder, type: "APPID" });
24 | this.name = options.name;
25 | this.flags = options.flags ?? AppIdFlags.None;
26 | }
27 |
28 | override tagify(mg: TagsManager): void {
29 | super.tagify(mg);
30 | mg.add(100, "AcDbRegAppTableRecord");
31 | mg.add(2, this.name);
32 | mg.add(70, this.flags);
33 | }
34 | }
35 |
36 | export interface AppIdTableOptions extends WithSeeder {}
37 |
38 | export class AppId extends XTable {
39 | constructor(options: AppIdTableOptions) {
40 | super({ seeder: options.seeder, name: "APPID" });
41 | }
42 |
43 | add(options: OmitSeeder) {
44 | return this.addEntry(AppIdEntry, options);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/tables/block.ts:
--------------------------------------------------------------------------------
1 | import { OmitSeeder, WithSeeder } from "@/types";
2 | import { TagsManager, Units, XData } from "@/utils";
3 | import { Entry } from "./entry";
4 | import { XTable } from "./table";
5 |
6 | export interface BlockRecordOptions extends WithSeeder {
7 | name: string;
8 | layoutObjectHandle?: string;
9 | insertionUnits?: number;
10 | explodability?: number;
11 | scalability?: number;
12 | bitmapPreview?: string;
13 | }
14 |
15 | export class BlockRecordEntry extends Entry {
16 | name: string;
17 | layoutObjectHandle?: string;
18 | insertionUnits: number;
19 | explodability: number;
20 | scalability: number;
21 | bitmapPreview?: string;
22 | acadXData: XData;
23 |
24 | get isPaperSpace() {
25 | return this.name.startsWith("*Paper_Space");
26 | }
27 |
28 | constructor(options: BlockRecordOptions) {
29 | super({ seeder: options.seeder, type: "BLOCK_RECORD" });
30 | this.name = options.name;
31 | this.layoutObjectHandle = options.layoutObjectHandle;
32 | this.insertionUnits = options.insertionUnits ?? Units.Unitless;
33 | this.explodability = options.explodability ?? 1;
34 | this.scalability = options.scalability ?? 0;
35 | this.acadXData = new XData("ACAD");
36 | }
37 |
38 | override tagify(mg: TagsManager): void {
39 | super.tagify(mg);
40 | mg.add(100, "AcDbBlockTableRecord");
41 | mg.add(2, this.name);
42 | mg.add(340, this.layoutObjectHandle);
43 | mg.add(70, this.insertionUnits);
44 | mg.add(280, this.explodability);
45 | mg.add(281, this.scalability);
46 | mg.add(310, this.bitmapPreview);
47 | this.acadXData.tagify(mg);
48 | }
49 | }
50 |
51 | export interface BlockRecordTableOptions extends WithSeeder {}
52 |
53 | export class BlockRecord extends XTable {
54 | constructor(options: BlockRecordTableOptions) {
55 | super({ seeder: options.seeder, name: "BLOCK_RECORD" });
56 | }
57 |
58 | add(options: OmitSeeder) {
59 | return this.addEntry(BlockRecordEntry, options);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/tables/entry.ts:
--------------------------------------------------------------------------------
1 | import { AppDefined, TagsManager } from "@/utils";
2 | import { Taggable, WithSeeder } from "@/types";
3 |
4 | export const EntryCommonFlags = {
5 | None: 0,
6 | XRefDependent: 16,
7 | XRefResolved: 32,
8 | Referenced: 64,
9 | } as const;
10 |
11 | export interface EntryOptions extends WithSeeder {
12 | type: string;
13 | hcode?: number;
14 | }
15 |
16 | export abstract class Entry implements Taggable {
17 | readonly handle: string;
18 | private readonly type: string;
19 | ownerObjectHandle: string;
20 |
21 | protected hcode: number;
22 |
23 | readonly applications: AppDefined[];
24 | readonly reactors: AppDefined;
25 | readonly xdictionary: AppDefined;
26 |
27 | constructor({ seeder, type, hcode }: EntryOptions) {
28 | this.handle = seeder.next();
29 | this.type = type;
30 | this.ownerObjectHandle = "0";
31 |
32 | this.hcode = hcode ?? 5;
33 |
34 | this.applications = [];
35 | this.reactors = this.addAppDefined("ACAD_REACTORS");
36 | this.xdictionary = this.addAppDefined("ACAD_XDICTIONARY");
37 | }
38 |
39 | addAppDefined(name: string) {
40 | const f = this.applications.find((a) => a.name === name);
41 | if (f) return f;
42 |
43 | const a = new AppDefined(name);
44 | this.applications.push(a);
45 | return a;
46 | }
47 |
48 | tagify(mg: TagsManager): void {
49 | mg.add(0, this.type);
50 | mg.add(this.hcode, this.handle);
51 | this.applications.forEach((a) => a.tagify(mg));
52 | mg.add(330, this.ownerObjectHandle);
53 | mg.add(100, "AcDbSymbolTableRecord");
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/tables/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./appid";
2 | export * from "./block";
3 | export * from "./dimstyle";
4 | export * from "./entry";
5 | export * from "./layer";
6 | export * from "./ltype";
7 | export * from "./style";
8 | export * from "./table";
9 | export * from "./tables";
10 | export * from "./ucs";
11 | export * from "./view";
12 | export * from "./vport";
13 |
--------------------------------------------------------------------------------
/src/tables/layer.ts:
--------------------------------------------------------------------------------
1 | import { Colors, LineTypes, TagsManager, onezero } from "@/utils";
2 | import { OmitSeeder, WithSeeder } from "@/types";
3 | import { Entry } from "./entry";
4 | import { XTable } from "./table";
5 |
6 | export const LayerFlags = {
7 | None: 0,
8 | Frozen: 1,
9 | FrozenInNewViewports: 2,
10 | Locked: 4,
11 | XRefDependent: 16,
12 | XRefResolved: 32,
13 | Referenced: 64,
14 | } as const;
15 |
16 | export interface LayerOptions extends WithSeeder {
17 | name: string;
18 | flags?: number;
19 | colorNumber?: number;
20 | lineTypeName?: string;
21 | lineWeight?: number;
22 | materialObjectHandle?: string;
23 | trueColor?: number;
24 | plot?: boolean;
25 | }
26 |
27 | export class LayerEntry extends Entry {
28 | readonly name: string;
29 | flags: number;
30 | colorNumber: number;
31 | lineTypeName: string;
32 | lineWeight?: number;
33 | plotStyleNameObjectHandle: string;
34 | materialObjectHandle?: string;
35 | trueColor?: number;
36 | plot?: boolean;
37 |
38 | static readonly layerZeroName = "0";
39 |
40 | constructor(options: LayerOptions) {
41 | super({ seeder: options.seeder, type: "LAYER" });
42 | this.name = options.name;
43 | this.flags = options.flags ?? LayerFlags.None;
44 | this.colorNumber = options.colorNumber ?? Colors.White;
45 | this.lineTypeName = options.lineTypeName ?? LineTypes.Continuous;
46 | this.lineWeight = options.lineWeight;
47 | this.plotStyleNameObjectHandle = "0";
48 | this.materialObjectHandle = options.materialObjectHandle;
49 | this.trueColor = options.trueColor;
50 | this.plot = options.plot;
51 | if (this.name.toLocaleLowerCase() === "defpoints") this.plot = false;
52 | }
53 |
54 | override tagify(mg: TagsManager): void {
55 | super.tagify(mg);
56 | mg.add(100, "AcDbLayerTableRecord");
57 | mg.add(2, this.name);
58 | mg.add(70, this.flags);
59 | mg.add(62, this.colorNumber);
60 | mg.add(420, this.trueColor);
61 | mg.add(6, this.lineTypeName);
62 | mg.add(290, onezero(this.plot));
63 | mg.add(370, this.lineWeight);
64 | mg.add(390, this.plotStyleNameObjectHandle);
65 | mg.add(347, this.materialObjectHandle);
66 | }
67 | }
68 |
69 | export interface LayerTableOptions extends WithSeeder {}
70 |
71 | export class Layer extends XTable {
72 | constructor(options: LayerTableOptions) {
73 | super({ seeder: options.seeder, name: "LAYER" });
74 | }
75 |
76 | get(name: string) {
77 | return this.find((layer) => layer.name === name);
78 | }
79 |
80 | add(options: OmitSeeder) {
81 | return this.addEntry(LayerEntry, options);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/tables/ltype.ts:
--------------------------------------------------------------------------------
1 | import { Entry, EntryCommonFlags } from "./entry";
2 | import { OmitSeeder, WithSeeder } from "@/types";
3 | import { TagsManager } from "@/utils";
4 | import { XTable } from "./table";
5 |
6 | export interface LTypeOptions extends WithSeeder {
7 | name: string;
8 | flags?: number;
9 | descriptive?: string;
10 | elements?: number[];
11 | }
12 |
13 | export class LTypeEntry extends Entry {
14 | readonly name: string;
15 | flags: number;
16 | descriptive: string;
17 | readonly elements: number[];
18 |
19 | constructor(options: LTypeOptions) {
20 | super({ seeder: options.seeder, type: "LTYPE" });
21 | this.name = options.name;
22 | this.flags = options.flags ?? EntryCommonFlags.None;
23 | this.descriptive = options.descriptive || "";
24 | this.elements = options.elements || [];
25 | }
26 |
27 | private patternLength() {
28 | return this.elements.reduce((prev, curr) => prev + Math.abs(curr), 0);
29 | }
30 |
31 | override tagify(mg: TagsManager): void {
32 | super.tagify(mg);
33 | mg.add(100, "AcDbLinetypeTableRecord");
34 | mg.add(2, this.name);
35 | mg.add(70, this.flags);
36 | mg.add(3, this.descriptive);
37 | mg.add(72, 65);
38 | mg.add(73, this.elements.length);
39 | mg.add(40, this.patternLength());
40 | this.elements.forEach((e) => {
41 | mg.add(49, e);
42 | mg.add(74, 0);
43 | });
44 | }
45 | }
46 |
47 | export interface LTypeTableOptions extends WithSeeder {}
48 |
49 | export class LType extends XTable {
50 | constructor(options: LTypeTableOptions) {
51 | super({ seeder: options.seeder, name: "LTYPE" });
52 | }
53 |
54 | add(options: OmitSeeder) {
55 | return this.addEntry(LTypeEntry, options);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/tables/style.ts:
--------------------------------------------------------------------------------
1 | import { OmitSeeder, WithSeeder } from "@/types";
2 | import { TagsManager, XData } from "@/utils";
3 | import { Entry } from "./entry";
4 | import { XTable } from "./table";
5 |
6 | export const TEXT_ITALIC = 16777250;
7 | export const TEXT_BOLD = 33554466;
8 |
9 | export const StyleFlags = {
10 | None: 0,
11 | DescribesShape: 1,
12 | VerticalText: 4,
13 | XRefDependent: 16,
14 | XRefResolved: 32,
15 | Referenced: 64,
16 | } as const;
17 |
18 | export const TextGenerationFlags = {
19 | None: 0,
20 | Backward: 2,
21 | UpsideDown: 4,
22 | } as const;
23 |
24 | export interface StyleOptions extends WithSeeder {
25 | name: string;
26 | flags?: number;
27 | fixedTextHeight?: number;
28 | widthFactor?: number;
29 | obliqueAngle?: number;
30 | textGenerationFlags?: number;
31 | lastHeightUsed?: number;
32 | primaryfontFileName?: string;
33 | bigFontFileName?: string;
34 | fontFamily?: string;
35 | italic?: boolean;
36 | bold?: boolean;
37 | }
38 |
39 | export class StyleEntry extends Entry {
40 | readonly name: string;
41 | flags: number;
42 | fixedTextHeight: number;
43 | widthFactor: number;
44 | obliqueAngle: number;
45 | textGenerationFlags: number;
46 | lastHeightUsed: number;
47 | primaryfontFileName: string;
48 | bigFontFileName: string;
49 | fontFamily?: string;
50 | italic?: boolean;
51 | bold?: boolean;
52 |
53 | readonly xdata: XData;
54 |
55 | constructor(options: StyleOptions) {
56 | super({ seeder: options.seeder, type: "STYLE" });
57 | this.name = options.name;
58 | this.flags = options.flags ?? StyleFlags.None;
59 | this.fixedTextHeight = options.fixedTextHeight ?? 0;
60 | this.widthFactor = options.widthFactor ?? 1;
61 | this.obliqueAngle = options.obliqueAngle ?? 0;
62 | this.textGenerationFlags =
63 | options.textGenerationFlags ?? TextGenerationFlags.None;
64 | this.lastHeightUsed = options.lastHeightUsed ?? 1;
65 | this.primaryfontFileName = options.primaryfontFileName || "txt";
66 | this.bigFontFileName = options.bigFontFileName || "";
67 | this.fontFamily = options.fontFamily;
68 | this.italic = options.italic;
69 | this.bold = options.bold;
70 | this.xdata = new XData("ACAD");
71 | }
72 |
73 | override tagify(mg: TagsManager): void {
74 | super.tagify(mg);
75 | mg.add(100, "AcDbTextStyleTableRecord");
76 | mg.add(2, this.name);
77 | mg.add(70, this.flags);
78 | mg.add(40, this.fixedTextHeight);
79 | mg.add(41, this.widthFactor);
80 | mg.add(50, this.obliqueAngle);
81 | mg.add(71, this.textGenerationFlags);
82 | mg.add(42, this.lastHeightUsed);
83 | mg.add(3, this.fontFamily == null ? this.primaryfontFileName : "");
84 | mg.add(4, this.bigFontFileName);
85 | this.updateXData(), this.xdata.tagify(mg);
86 | }
87 |
88 | private updateXData() {
89 | this.xdata.clear();
90 | if (this.fontFamily != null) this.xdata.string(this.fontFamily);
91 | let flags = 34;
92 | if (this.italic != null) flags |= TEXT_ITALIC;
93 | if (this.bold != null) flags |= TEXT_BOLD;
94 | this.xdata.long(flags);
95 | }
96 | }
97 |
98 | export interface StyleTableOptions extends WithSeeder {}
99 |
100 | export class Style extends XTable {
101 | constructor(options: StyleTableOptions) {
102 | super({ seeder: options.seeder, name: "STYLE" });
103 | }
104 |
105 | add(options: OmitSeeder) {
106 | return this.addEntry(StyleEntry, options);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/tables/table.ts:
--------------------------------------------------------------------------------
1 | import { AppDefined, Seeder, TagsManager } from "@/utils";
2 | import { Taggable, WithSeeder } from "@/types";
3 | import { Entry } from "./entry";
4 |
5 | export interface XTableOptions extends WithSeeder {
6 | name: string;
7 | }
8 |
9 | export class XTable implements Taggable {
10 | readonly seeder: Seeder;
11 | readonly name: string;
12 | readonly handle: string;
13 | ownerObjectHandle: string;
14 | protected entries: E[];
15 |
16 | readonly xdictionary: AppDefined;
17 |
18 | constructor(options: XTableOptions) {
19 | this.seeder = options.seeder;
20 | this.name = options.name;
21 | this.handle = this.seeder.next();
22 | this.ownerObjectHandle = "0";
23 | this.entries = [];
24 | this.xdictionary = new AppDefined("ACAD_XDICTIONARY");
25 | }
26 |
27 | addEntry(ctor: new (options: WithSeeder) => E, options: O) {
28 | const entry = new ctor({ seeder: this.seeder, ...options });
29 | entry.ownerObjectHandle = this.handle;
30 | this.entries.push(entry);
31 | return entry;
32 | }
33 |
34 | find(predicate: (value: E, index: number, obj: E[]) => unknown) {
35 | return this.entries.find(predicate);
36 | }
37 |
38 | tagify(mg: TagsManager): void {
39 | mg.add(0, "TABLE");
40 | mg.add(2, this.name);
41 | mg.add(5, this.handle);
42 | this.xdictionary.tagify(mg);
43 | mg.add(330, this.ownerObjectHandle);
44 | mg.add(100, "AcDbSymbolTable");
45 | mg.add(70, this.entries.length);
46 | this.entries.forEach((e) => e.tagify(mg));
47 | mg.add(0, "ENDTAB");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/tables/ucs.ts:
--------------------------------------------------------------------------------
1 | import { WithSeeder } from "@/types";
2 | import { XTable } from "./table";
3 |
4 | export interface UcsTableOptions extends WithSeeder {}
5 |
6 | export class Ucs extends XTable {
7 | constructor(options: UcsTableOptions) {
8 | super({ seeder: options.seeder, name: "UCS" });
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/tables/view.ts:
--------------------------------------------------------------------------------
1 | import { WithSeeder } from "@/types";
2 | import { XTable } from "./table";
3 |
4 | export interface ViewTableOptions extends WithSeeder {}
5 |
6 | export class View extends XTable {
7 | constructor(options: ViewTableOptions) {
8 | super({ seeder: options.seeder, name: "VIEW" });
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/tables/vport.ts:
--------------------------------------------------------------------------------
1 | import { OmitSeeder, Point2D, WithSeeder } from "@/types";
2 | import { Entry } from "./entry";
3 | import { TagsManager } from "@/utils";
4 | import { XTable } from "./table";
5 |
6 | export interface VPortOptions extends WithSeeder {
7 | name: string;
8 | lowerLeft?: Point2D;
9 | upperRight?: Point2D;
10 | center?: Point2D;
11 | height?: number;
12 | }
13 |
14 | export class VPortEntry extends Entry {
15 | readonly name: string;
16 | lowerLeft?: Point2D;
17 | upperRight?: Point2D;
18 | center?: Point2D;
19 | height?: number;
20 |
21 | constructor(options: VPortOptions) {
22 | super({ seeder: options.seeder, type: "VPORT" });
23 | this.name = options.name;
24 | this.lowerLeft = options.lowerLeft;
25 | this.upperRight = options.upperRight;
26 | this.center = options.center;
27 | this.height = options.height;
28 | }
29 |
30 | override tagify(mg: TagsManager): void {
31 | super.tagify(mg);
32 | mg.add(100, "AcDbViewportTableRecord");
33 | mg.add(2, this.name);
34 | mg.add(70, 0);
35 | mg.point2d(this.lowerLeft);
36 | mg.point2d(this.upperRight, 1);
37 | mg.point2d(this.center, 2);
38 | mg.add(40, this.height);
39 | mg.add(41, this.aspect());
40 | }
41 |
42 | private aspect() {
43 | if (!this.lowerLeft || !this.upperRight) return 1;
44 | return (
45 | (this.upperRight.x - this.lowerLeft.x) /
46 | (this.upperRight.y - this.lowerLeft.y)
47 | );
48 | }
49 | }
50 |
51 | export interface VPortTableOptions extends WithSeeder {}
52 |
53 | export class VPort extends XTable {
54 | constructor(options: VPortTableOptions) {
55 | super({ seeder: options.seeder, name: "VPORT" });
56 | }
57 |
58 | add(options: OmitSeeder) {
59 | return this.addEntry(VPortEntry, options);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import { BlockRecordEntry, Seeder } from ".";
2 |
3 | export interface Tag {
4 | code: number;
5 | value: string | number;
6 | }
7 |
8 | export interface Point2D {
9 | x: number;
10 | y: number;
11 | }
12 |
13 | export interface Point3D extends Point2D {
14 | z: number;
15 | }
16 |
17 | export interface Stringifiable {
18 | stringify(): string;
19 | }
20 |
21 | export interface Taggable {
22 | tagify(mg: IManager): void;
23 | }
24 |
25 | export type Union = T[keyof T];
26 |
27 | export type WithSeeder = T & { seeder: Seeder };
28 | export type WithBlockRecord = T & { blockRecord: BlockRecordEntry };
29 |
30 | export type OmitSeeder = Omit;
31 | export type OmitType = Omit;
32 | export type OmitBlockRecord = Omit;
33 | export type OmitBlockName = Omit;
34 |
--------------------------------------------------------------------------------
/src/utils/application.ts:
--------------------------------------------------------------------------------
1 | import { Tag, Taggable } from "@/types";
2 | import { TagsManager } from "./tags";
3 |
4 | export class AppDefined implements Taggable {
5 | readonly values: Tag[];
6 |
7 | constructor(public readonly name: string) {
8 | this.values = [];
9 | }
10 |
11 | add(code: number, value: string | number) {
12 | this.values.push({ code, value });
13 | }
14 |
15 | clear() {
16 | this.values.length = 0;
17 | }
18 |
19 | tagify(mg: TagsManager): void {
20 | if (this.values.length > 0) {
21 | mg.add(102, `{${this.name}`);
22 | this.values.forEach((tag) => mg.add(tag.code, tag.value));
23 | mg.add(102, "}");
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/utils/bbox.ts:
--------------------------------------------------------------------------------
1 | import { Point3D } from "@/types";
2 | import { point } from "./functions";
3 |
4 | export interface BoundingBox {
5 | maxX: number;
6 | maxY: number;
7 | maxZ: number;
8 | minX: number;
9 | minY: number;
10 | minZ: number;
11 | }
12 |
13 | export function bbox(min?: Point3D, max?: Point3D): BoundingBox {
14 | min ||= point();
15 | max ||= point(100, 100, 100);
16 | return {
17 | maxX: max.x,
18 | maxY: max.y,
19 | maxZ: max.z,
20 | minX: min.x,
21 | minY: min.y,
22 | minZ: min.z,
23 | };
24 | }
25 |
26 | export class BBox {
27 | static point(p: Point3D, radius?: number) {
28 | radius = radius ?? 100;
29 | return bbox(
30 | point(p.x - radius, p.y - radius, p.z - radius),
31 | point(p.x + radius, p.y + radius, p.z + radius)
32 | );
33 | }
34 |
35 | static line(start: Point3D, end: Point3D) {
36 | const box = bbox();
37 | box.maxX = start.x > end.x ? start.x : end.x;
38 | box.maxY = start.y > end.y ? start.y : end.y;
39 | box.maxZ = start.z > end.z ? start.z : end.z;
40 | box.minX = start.x < end.x ? start.x : end.x;
41 | box.minY = start.y < end.y ? start.y : end.y;
42 | box.minZ = start.z < end.z ? start.z : end.z;
43 | return box;
44 | }
45 |
46 | static points(points: Point3D[]) {
47 | if (points.length === 0) return BBox.point(point());
48 | const box = bbox();
49 | points.forEach((p) => {
50 | if (box.maxX < p.x) box.maxX = p.x;
51 | if (box.maxY < p.y) box.maxY = p.y;
52 | if (box.maxZ < p.z) box.maxZ = p.z;
53 | if (box.minX > p.x) box.minX = p.x;
54 | if (box.minY > p.y) box.minY = p.y;
55 | if (box.minZ > p.z) box.minZ = p.z;
56 | });
57 | return box;
58 | }
59 |
60 | static boxes(boxes: BoundingBox[]) {
61 | if (boxes.length === 0) return BBox.point(point());
62 | const points: Point3D[] = [];
63 | boxes.forEach((box) => {
64 | points.push(point(box.maxX, box.maxY, box.maxZ));
65 | points.push(point(box.minX, box.minY, box.minZ));
66 | });
67 | return BBox.points(points);
68 | }
69 |
70 | static center(box: BoundingBox) {
71 | return point(
72 | box.minX + (box.maxX - box.minX) / 2,
73 | box.minY + (box.maxY - box.minY) / 2,
74 | box.minZ + (box.maxZ - box.minZ) / 2
75 | );
76 | }
77 |
78 | static height(box: BoundingBox) {
79 | return box.maxY - box.minY;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/utils/color.ts:
--------------------------------------------------------------------------------
1 | export class TrueColor {
2 | static fromHex(hex: string) {
3 | return parseInt(hex.replace("#", ""), 16);
4 | }
5 |
6 | static fromRGB(r: number, g: number, b: number) {
7 | const hex = [r, g, b].reduce((acc, c) => {
8 | const h = c.toString(16);
9 | return `${acc}${h.length === 1 ? `0${h}` : h}`;
10 | }, "0x00");
11 | return TrueColor.fromHex(hex);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const Units = {
2 | Unitless: 0,
3 | Inches: 1,
4 | Feet: 2,
5 | Miles: 3,
6 | Millimeters: 4,
7 | Centimeters: 5,
8 | Meters: 6,
9 | Kilometers: 7,
10 | Microinches: 8,
11 | Mils: 9,
12 | Yards: 10,
13 | Angstroms: 11,
14 | Nanometers: 12,
15 | Microns: 13,
16 | Decimeters: 14,
17 | Decameters: 15,
18 | Hectometers: 16,
19 | Gigameters: 17,
20 | AstronomicalUnits: 18,
21 | LightYears: 19,
22 | Parsecs: 20,
23 | USSurveyFeet: 21,
24 | USSurveyInch: 22,
25 | USSurveyYard: 23,
26 | USSurveyMile: 24,
27 | } as const;
28 |
29 | export const Colors = {
30 | Red: 1,
31 | Green: 3,
32 | Cyan: 4,
33 | Blue: 5,
34 | Magenta: 6,
35 | White: 7,
36 | Black: 0,
37 | Yellow: 2,
38 | } as const;
39 |
40 | export const LineTypes = {
41 | Continuous: "Continuous"
42 | } as const;
43 |
--------------------------------------------------------------------------------
/src/utils/functions.ts:
--------------------------------------------------------------------------------
1 | import { Point2D, Point3D, Tag } from "@/types";
2 |
3 | export function point(x?: number, y?: number, z?: number): Point3D {
4 | (x ??= 0), (y ??= 0), (z ??= 0);
5 | return { x, y, z };
6 | }
7 |
8 | export function point2d(x?: number, y?: number): Point2D {
9 | (x ??= 0), (y ??= 0);
10 | return { x, y };
11 | }
12 |
13 | export function tag(code: number, value: string | number): Tag {
14 | return { code, value };
15 | }
16 |
17 | export function stringByteSize(value: string) {
18 | return new Blob([value]).size;
19 | }
20 |
21 | export function stringChunksSplit(value: string, length = 255) {
22 | const chunks: string[] = [];
23 | const tempChunk: string[] = [];
24 | for (let i = 0; i < value.length; i++) {
25 | const char = value[i];
26 | tempChunk.push(char);
27 | if (tempChunk.length === length || i === value.length - 1) {
28 | chunks.push(tempChunk.join(""));
29 | tempChunk.length = 0;
30 | }
31 | }
32 | return chunks;
33 | }
34 |
35 | export function extrusion() {
36 | return point(0, 0, 1);
37 | }
38 |
39 | function nknots(n: number, degree: number) {
40 | return n + degree + 1;
41 | }
42 |
43 | export function uniformKnots(n: number, degree: number) {
44 | const knots: number[] = [];
45 | for (let i = 0; i < nknots(n, degree); i++) knots.push(i);
46 | return knots;
47 | }
48 |
49 | export function openUniformKnots(n: number, degree: number) {
50 | const knots: number[] = [];
51 | let k = 0;
52 | for (let i = 0; i < nknots(n, degree); i++) {
53 | if (i <= degree || i >= n + 1) knots.push(k);
54 | else knots.push(++k);
55 | }
56 | return knots;
57 | }
58 |
59 | export function deg(rad: number) {
60 | return (rad * 180) / Math.PI;
61 | }
62 |
63 | export function rad(deg: number) {
64 | return (deg * Math.PI) / 180;
65 | }
66 |
67 | export function angle(start: Point2D, end: Point2D) {
68 | let dir = Math.atan2(end.y - start.y, end.x - start.x);
69 | if (dir < 0) dir += 2 * Math.PI;
70 | return deg(dir);
71 | }
72 |
73 | export function polar({ x, y }: Point2D, angle: number, distance: number) {
74 | angle = rad(angle);
75 | const { cos, sin } = Math;
76 | return point(x + distance * cos(angle), y + distance * sin(angle));
77 | }
78 |
79 | export function onezero(value?: boolean) {
80 | if (value == null) return;
81 | return value ? 1 : 0;
82 | }
83 |
84 | export function bulge(angle: number) {
85 | return Math.tan(angle / 4);
86 | }
87 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./application";
2 | export * from "./bbox";
3 | export * from "./color";
4 | export * from "./constants";
5 | export * from "./functions";
6 | export * from "./seeder";
7 | export * from "./tags";
8 | export * from "./text";
9 | export * from "./xdata";
10 |
--------------------------------------------------------------------------------
/src/utils/seeder.ts:
--------------------------------------------------------------------------------
1 | export class Seeder {
2 | private seed: number;
3 |
4 | constructor() {
5 | this.seed = 0;
6 | }
7 |
8 | next(): string {
9 | return (++this.seed).toString(16).toUpperCase();
10 | }
11 |
12 | peek(): string {
13 | return (this.seed + 1).toString(16).toUpperCase();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/tags.ts:
--------------------------------------------------------------------------------
1 | import { Point2D, Point3D, Stringifiable } from "@/types";
2 |
3 | export class TagsManager implements Stringifiable {
4 | readonly lines: (string | number)[];
5 |
6 | constructor() {
7 | this.lines = [];
8 | }
9 |
10 | clear() {
11 | this.lines.length = 0;
12 | }
13 |
14 | add(code: number, value?: string | number) {
15 | if (value != null) this.lines.push(code, value);
16 | }
17 |
18 | sectionStart(name: string) {
19 | this.add(0, "SECTION");
20 | this.add(2, name);
21 | }
22 |
23 | sectionEnd() {
24 | this.add(0, "ENDSEC");
25 | }
26 |
27 | point2d(point?: Point2D, digit = 0) {
28 | if (point == null) return;
29 | this.add(10 + digit, point.x);
30 | this.add(20 + digit, point.y);
31 | }
32 |
33 | point(point?: Point3D, digit = 0) {
34 | if (point == null) return;
35 | this.point2d(point, digit);
36 | this.add(30 + digit, point.z);
37 | }
38 |
39 | stringify() {
40 | return this.lines.join("\u000A");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/utils/text.ts:
--------------------------------------------------------------------------------
1 | export interface StyledTextOptions {
2 | value: string;
3 | fontFamily?: string;
4 | bold?: boolean;
5 | italic?: boolean;
6 | colorNumber?: number;
7 | underline?: boolean;
8 | center?: boolean;
9 | }
10 |
11 | export class StyledText {
12 | private text: string;
13 | paragraph: boolean;
14 |
15 | get value(): string {
16 | if (this.text === "") return "";
17 | let value = `{${this.text}}`;
18 | if (this.paragraph) value += "\\P";
19 | return value;
20 | }
21 |
22 | constructor(paragraph?: boolean) {
23 | this.paragraph = paragraph || false;
24 | this.text = "";
25 | }
26 |
27 | add(options?: StyledTextOptions) {
28 | if (!options || options.value === "") return;
29 |
30 | let font = "";
31 | let style = "";
32 |
33 | if (options.fontFamily) font += `\\f${options.fontFamily}`;
34 | if (options.bold) font += "|b1";
35 | if (options.italic) font += "|i1";
36 | if (options.center) font += "|c1";
37 | if (font !== "") font += ";";
38 |
39 | if (options.underline) style += "\\L";
40 | if (options.colorNumber) style += `\\C${options.colorNumber}`;
41 | if (style !== "") style += ";";
42 |
43 | this.text += `${style}${font}${options.value}`;
44 | }
45 | }
46 |
47 | export class TextBuilder {
48 | readonly texts: StyledText[];
49 |
50 | get value(): string {
51 | return this.texts.map((t) => t.value).join("");
52 | }
53 |
54 | constructor() {
55 | this.texts = [];
56 | }
57 |
58 | add(options?: StyledTextOptions, paragraph?: boolean) {
59 | const txt = new StyledText(paragraph);
60 | txt.add(options);
61 | this.texts.push(txt);
62 | return txt;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/utils/xdata.ts:
--------------------------------------------------------------------------------
1 | import { Point3D, Tag, Taggable } from "@/types";
2 | import { stringChunksSplit, tag } from "./functions";
3 | import { TagsManager } from "./tags";
4 |
5 | export class XData implements Taggable {
6 | readonly tags: Tag[];
7 |
8 | constructor(public readonly name: string) {
9 | this.tags = [];
10 | }
11 |
12 | private xpoint(point: Point3D, digit = 0) {
13 | this.tags.push(tag(1010 + digit, point.x));
14 | this.tags.push(tag(1020 + digit, point.y));
15 | this.tags.push(tag(1030 + digit, point.z));
16 | }
17 |
18 | clear() {
19 | this.tags.length = 0;
20 | }
21 |
22 | string(string: string) {
23 | stringChunksSplit(string).forEach((chunk) =>
24 | this.tags.push(tag(1000, chunk))
25 | );
26 | }
27 |
28 | beginList() {
29 | this.tags.push(tag(1002, "{"));
30 | }
31 |
32 | endList() {
33 | this.tags.push(tag(1002, "}"));
34 | }
35 |
36 | layerName(name: string) {
37 | this.tags.push(tag(1003, name));
38 | }
39 |
40 | binaryData(data: string) {
41 | stringChunksSplit(data).forEach((chunk) =>
42 | this.tags.push(tag(1004, chunk))
43 | );
44 | }
45 |
46 | databaseHandle(handleSeed: string) {
47 | this.tags.push(tag(1005, handleSeed));
48 | }
49 |
50 | point(point: Point3D) {
51 | this.xpoint(point);
52 | }
53 |
54 | position(position: Point3D) {
55 | this.xpoint(position, 1);
56 | }
57 |
58 | displacement(displacement: Point3D) {
59 | this.xpoint(displacement, 2);
60 | }
61 |
62 | direction(direction: Point3D) {
63 | this.xpoint(direction, 3);
64 | }
65 |
66 | real(real: number) {
67 | this.tags.push(tag(1040, real));
68 | }
69 |
70 | distance(distance: number) {
71 | this.tags.push(tag(1041, distance));
72 | }
73 |
74 | scale(scale: number) {
75 | this.tags.push(tag(1042, scale));
76 | }
77 |
78 | integer(integer: number) {
79 | this.tags.push(tag(1070, integer));
80 | }
81 |
82 | long(long: number) {
83 | this.tags.push(tag(1071, long));
84 | }
85 |
86 | tagify(mg: TagsManager): void {
87 | mg.add(1001, this.name);
88 | mg.add(1002, "{");
89 | this.tags.forEach((t) => mg.add(t.code, t.value));
90 | mg.add(1002, "}");
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/writer.ts:
--------------------------------------------------------------------------------
1 | import { Document } from "./document";
2 | import { Stringifiable } from "./types";
3 |
4 | export class Writer implements Stringifiable {
5 | readonly document: Document;
6 |
7 | public constructor() {
8 | this.document = new Document();
9 | }
10 |
11 | stringify(): string {
12 | return this.document.stringify();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
5 | "module": "ESNext",
6 | "skipLibCheck": true,
7 |
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 |
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "noImplicitOverride": true,
19 | "noImplicitAny": true,
20 | "noImplicitThis": true,
21 |
22 | "newLine": "lf",
23 | "types": ["vitest/globals"],
24 |
25 | "paths": {
26 | "@/*": ["./src/*"]
27 | }
28 | },
29 | "include": ["./src", "./__tests__"]
30 | }
31 |
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { Options, defineConfig } from "tsup";
2 |
3 | type CustomOptions = Pick;
4 |
5 | function defineOptions({ format, dts, globalName }: CustomOptions): Options {
6 | return {
7 | entry: ["./src/index.ts", "./src/helpers/index.ts", "./src/svg/index.ts"],
8 | outDir: "lib",
9 | format,
10 | dts,
11 | globalName,
12 | sourcemap: true,
13 | clean: true,
14 | splitting: false,
15 | };
16 | }
17 |
18 | const esm: Options = defineOptions({ format: "esm", dts: true });
19 | const cjs: Options = defineOptions({ format: "cjs" });
20 | const iife: Options = defineOptions({
21 | format: "iife",
22 | globalName: "DXFWriter",
23 | });
24 |
25 | export default defineConfig([esm, cjs, iife]);
26 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import { resolve } from "path";
3 |
4 | const _resolve = (path: string) => resolve(__dirname, path);
5 |
6 | export default defineConfig({
7 | test: {
8 | watch: false,
9 | globals: true,
10 | environment: "node",
11 | alias: {
12 | "@": _resolve("./src/"),
13 | },
14 | },
15 | });
16 |
--------------------------------------------------------------------------------