├── .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 [![Verified on Openbase](https://badges.openbase.com/js/verified/@tarikjabiri/dxf.svg?style=openbase&token=C/1uHA0bNDQFUKrzrn23YQaNpCza+ZeDOe948Hvmi+s=)](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 | ![ci](https://github.com/tarikjabiri/dxf/actions/workflows/ci.yml/badge.svg) 6 | ![publish](https://github.com/tarikjabiri/dxf/actions/workflows/cd.yml/badge.svg) 7 | [![codecov](https://codecov.io/gh/dxfjs/writer/branch/next/graph/badge.svg?token=P5QJAUXZTA)](https://codecov.io/gh/dxfjs/writer) 8 | 9 | 10 | ![GitHub](https://img.shields.io/github/license/dxfjs/writer?color=%2334D058&label=License&logo=Open%20Access&logoColor=%23959DA5) 11 | ![npm (scoped)](https://img.shields.io/npm/v/@tarikjabiri/dxf?color=%2334D058&logo=npm) 12 | ![npm](https://img.shields.io/npm/dt/@tarikjabiri/dxf?color=%2334D058&logo=npm) 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 | Archilogic | Interior space for the digital world 53 | 54 | 55 | Slate 56 | 57 | 58 | Slate 59 | 60 | 61 | Mikey 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 | 50 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | ![Sponsors](_media/sponsors.svg) 60 | 61 | 62 |
63 | 64 |
65 |
66 | 67 | 68 | ![Contributors](_media/contributors.svg) 69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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[] = ["`); 22 | return parts.join(" "); 23 | } 24 | 25 | constructor(doc: Document) { 26 | this.doc = doc; 27 | this.lines = []; 28 | this.width = 2160; 29 | this.height = 2160; 30 | } 31 | 32 | layer(name?: string) { 33 | return this.doc.tables.layer.get(name || "0"); 34 | } 35 | 36 | start() { 37 | const { doc, lines } = this; 38 | this.clear(); 39 | this.lines.push(this._svg); 40 | this._block(doc.modelSpace); 41 | lines.push(""); 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 | --------------------------------------------------------------------------------