@chanmeng666/archlang 0.5.0 β 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -169
- package/dist/{chunk-GUNWYUR2.js β chunk-PABYLU6Z.js} +235 -21
- package/dist/chunk-PABYLU6Z.js.map +1 -0
- package/dist/cli.js +52 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +124 -1
- package/dist/index.js +9 -3
- package/package.json +9 -2
- package/dist/chunk-GUNWYUR2.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,169 +1,175 @@
|
|
|
1
|
-
<!-- AGENT-FIRST NOTICE -->
|
|
2
|
-
> [!IMPORTANT]
|
|
3
|
-
> ### π€ Read this with your AI agent β don't read it by hand.
|
|
4
|
-
> This repo is written agent-first. Point Claude Code, GitHub Copilot, Cursor, or any agent at it:
|
|
5
|
-
> *"Read the README and AGENTS.md, then help me run / extend this."*
|
|
6
|
-
> Structure + [`AGENTS.md`](AGENTS.md) are optimized for agent comprehension.
|
|
7
|
-
<!-- /AGENT-FIRST NOTICE -->
|
|
8
|
-
|
|
9
|
-
<div align="center">
|
|
10
|
-
|
|
11
|
-
# ArchLang
|
|
12
|
-
|
|
13
|
-
A small declarative language that compiles to professional SVG floor plans β like Typst/LaTeX, but for architecture.
|
|
14
|
-
|
|
15
|
-
[](LICENSE)
|
|
16
|
-
[](https://github.com/chanmeng666/archlang/issues)
|
|
17
|
-
[](https://github.com/chanmeng666/archlang/stargazers)
|
|
18
|
-
[](https://github.com/sponsors/ChanMeng666)
|
|
19
|
-
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
## π Introduction
|
|
23
|
-
|
|
24
|
-
**ArchLang** is a tiny language for floor plans. You write a `.arch` source file that
|
|
25
|
-
*declares* a plan β walls, rooms, doors, windows, dimensions β and the compiler renders
|
|
26
|
-
it to a clean, professional **SVG**. Think of it as **Typst/LaTeX for architecture**:
|
|
27
|
-
text in, a precise drawing out.
|
|
28
|
-
|
|
29
|
-
It is **explicit and parametric**. Every element has exact coordinates and sizes in
|
|
30
|
-
millimetres, so the output is **deterministic** (the same source always produces the same
|
|
31
|
-
drawing) and **editable** (changing one number changes exactly one thing). That makes it
|
|
32
|
-
ideal both for humans and for AI agents that author or tweak plans and re-render β e.g.
|
|
33
|
-
*"make the bedroom 1 m wider"* becomes a one-number diff, not a re-roll of a raster image.
|
|
34
|
-
|
|
35
|
-
The compiler is **pure TypeScript with zero runtime dependencies** and runs identically in
|
|
36
|
-
**Node and the browser** β so the [playground](playground/index.html) is fully client-side.
|
|
37
|
-
|
|
38
|
-
> ArchLang is the floor-plan engine behind [ArchCanvas](https://github.com/chanmeng666/archcanvas),
|
|
39
|
-
> an AI design agent β but it stands alone and is useful in any app or script.
|
|
40
|
-
|
|
41
|
-
## β¨ Features
|
|
42
|
-
|
|
43
|
-
- **Code β professional drawing.** PochΓ©-hatched walls, door swing arcs, window glazing,
|
|
44
|
-
computed room areas, dimension lines, a north arrow, a scale bar, and a title block.
|
|
45
|
-
- **Explicit + deterministic.** Integer-millimetre coordinates with optional **grid snapping**;
|
|
46
|
-
byte-for-byte stable output, so renders are cacheable and testable.
|
|
47
|
-
- **Zero dependencies, isomorphic.** Hand-written lexer + recursive-descent parser; runs in
|
|
48
|
-
Node and the browser. No native binaries, no fonts to bundle.
|
|
49
|
-
- **Errors as data.** `compile()` *returns* `errors`/`warnings` with line numbers β it never
|
|
50
|
-
throws on bad source β which makes a tight authoring or LLM self-correction loop trivial.
|
|
51
|
-
- **Library + CLI + playground.** Use the `compile()` API, the `arch` CLI, or the live editor.
|
|
52
|
-
|
|
53
|
-
## π Getting Started
|
|
54
|
-
|
|
55
|
-
### Prerequisites
|
|
56
|
-
|
|
57
|
-
- **Node.js β₯ 18** to use the CLI or build from source. The library itself is dependency-free
|
|
58
|
-
and also runs in any modern browser.
|
|
59
|
-
|
|
60
|
-
### Install
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm install @chanmeng666/archlang
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Build from source / develop
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm install # install dev dependencies
|
|
70
|
-
npm run build # build the library + CLI (dist/)
|
|
71
|
-
npm test # run the test suite (vitest)
|
|
72
|
-
npm run cli -- compile examples/studio.arch -o studio.svg # run the CLI from source
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## π Usage
|
|
76
|
-
|
|
77
|
-
**As a library:**
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
import { compile } from "@chanmeng666/archlang";
|
|
81
|
-
|
|
82
|
-
const source = `
|
|
83
|
-
plan "Tiny" {
|
|
84
|
-
units mm
|
|
85
|
-
grid 50
|
|
86
|
-
wall exterior thickness 200 { (0,0) (4000,0) (4000,3000) (0,3000) close }
|
|
87
|
-
room id=r at (0,0) size 4000x3000 label "Studio"
|
|
88
|
-
door at (2000,3000) width 900 wall exterior hinge left swing in
|
|
89
|
-
window at (0,1500) width 1200 wall exterior
|
|
90
|
-
}`;
|
|
91
|
-
|
|
92
|
-
const { svg, errors, warnings } = compile(source);
|
|
93
|
-
if (errors.length) console.error(errors);
|
|
94
|
-
else writeFileSync("tiny.svg", svg); // a finished floor plan
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**As a CLI:**
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
arch compile floorplan.arch -o floorplan.svg # compile once
|
|
101
|
-
arch compile floorplan.arch -w 1000 # set output width (px)
|
|
102
|
-
arch watch floorplan.arch # recompile on save
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**A taste of the language** (see [`examples/`](examples) and the
|
|
106
|
-
[Language Reference](docs/language-reference.md)):
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
plan "Studio 1BR" {
|
|
110
|
-
units mm
|
|
111
|
-
grid 50
|
|
112
|
-
scale 1:50
|
|
113
|
-
north up
|
|
114
|
-
|
|
115
|
-
wall exterior thickness 200 { (0,0) (7000,0) (7000,6000) (0,6000) close }
|
|
116
|
-
wall partition thickness 100 { (4000,0) (4000,4000) }
|
|
117
|
-
|
|
118
|
-
room id=r_living at (0,0) size 4000x6000 label "Living / Kitchen"
|
|
119
|
-
room id=r_bed at (4000,0) size 3000x4000 label "Bedroom"
|
|
120
|
-
|
|
121
|
-
door id=d_main at (1000,6000) width 1000 wall exterior hinge left swing in
|
|
122
|
-
window at (2500,0) width 1800 wall exterior
|
|
123
|
-
|
|
124
|
-
dim (0,6000)->(7000,6000) offset 600 text "7000"
|
|
125
|
-
title { project "Studio Apartment" drawn_by "ArchLang" date "2026" }
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Try it live
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
[
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<a href="
|
|
167
|
-
|
|
168
|
-
</
|
|
169
|
-
|
|
1
|
+
<!-- AGENT-FIRST NOTICE -->
|
|
2
|
+
> [!IMPORTANT]
|
|
3
|
+
> ### π€ Read this with your AI agent β don't read it by hand.
|
|
4
|
+
> This repo is written agent-first. Point Claude Code, GitHub Copilot, Cursor, or any agent at it:
|
|
5
|
+
> *"Read the README and AGENTS.md, then help me run / extend this."*
|
|
6
|
+
> Structure + [`AGENTS.md`](AGENTS.md) are optimized for agent comprehension.
|
|
7
|
+
<!-- /AGENT-FIRST NOTICE -->
|
|
8
|
+
|
|
9
|
+
<div align="center">
|
|
10
|
+
|
|
11
|
+
# ArchLang
|
|
12
|
+
|
|
13
|
+
A small declarative language that compiles to professional SVG floor plans β like Typst/LaTeX, but for architecture.
|
|
14
|
+
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
[](https://github.com/chanmeng666/archlang/issues)
|
|
17
|
+
[](https://github.com/chanmeng666/archlang/stargazers)
|
|
18
|
+
[](https://github.com/sponsors/ChanMeng666)
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
## π Introduction
|
|
23
|
+
|
|
24
|
+
**ArchLang** is a tiny language for floor plans. You write a `.arch` source file that
|
|
25
|
+
*declares* a plan β walls, rooms, doors, windows, dimensions β and the compiler renders
|
|
26
|
+
it to a clean, professional **SVG**. Think of it as **Typst/LaTeX for architecture**:
|
|
27
|
+
text in, a precise drawing out.
|
|
28
|
+
|
|
29
|
+
It is **explicit and parametric**. Every element has exact coordinates and sizes in
|
|
30
|
+
millimetres, so the output is **deterministic** (the same source always produces the same
|
|
31
|
+
drawing) and **editable** (changing one number changes exactly one thing). That makes it
|
|
32
|
+
ideal both for humans and for AI agents that author or tweak plans and re-render β e.g.
|
|
33
|
+
*"make the bedroom 1 m wider"* becomes a one-number diff, not a re-roll of a raster image.
|
|
34
|
+
|
|
35
|
+
The compiler is **pure TypeScript with zero runtime dependencies** and runs identically in
|
|
36
|
+
**Node and the browser** β so the [playground](playground/index.html) is fully client-side.
|
|
37
|
+
|
|
38
|
+
> ArchLang is the floor-plan engine behind [ArchCanvas](https://github.com/chanmeng666/archcanvas),
|
|
39
|
+
> an AI design agent β but it stands alone and is useful in any app or script.
|
|
40
|
+
|
|
41
|
+
## β¨ Features
|
|
42
|
+
|
|
43
|
+
- **Code β professional drawing.** PochΓ©-hatched walls, door swing arcs, window glazing,
|
|
44
|
+
computed room areas, dimension lines, a north arrow, a scale bar, and a title block.
|
|
45
|
+
- **Explicit + deterministic.** Integer-millimetre coordinates with optional **grid snapping**;
|
|
46
|
+
byte-for-byte stable output, so renders are cacheable and testable.
|
|
47
|
+
- **Zero dependencies, isomorphic.** Hand-written lexer + recursive-descent parser; runs in
|
|
48
|
+
Node and the browser. No native binaries, no fonts to bundle.
|
|
49
|
+
- **Errors as data.** `compile()` *returns* `errors`/`warnings` with line numbers β it never
|
|
50
|
+
throws on bad source β which makes a tight authoring or LLM self-correction loop trivial.
|
|
51
|
+
- **Library + CLI + playground.** Use the `compile()` API, the `arch` CLI, or the live editor.
|
|
52
|
+
|
|
53
|
+
## π Getting Started
|
|
54
|
+
|
|
55
|
+
### Prerequisites
|
|
56
|
+
|
|
57
|
+
- **Node.js β₯ 18** to use the CLI or build from source. The library itself is dependency-free
|
|
58
|
+
and also runs in any modern browser.
|
|
59
|
+
|
|
60
|
+
### Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install @chanmeng666/archlang
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Build from source / develop
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install # install dev dependencies
|
|
70
|
+
npm run build # build the library + CLI (dist/)
|
|
71
|
+
npm test # run the test suite (vitest)
|
|
72
|
+
npm run cli -- compile examples/studio.arch -o studio.svg # run the CLI from source
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## π Usage
|
|
76
|
+
|
|
77
|
+
**As a library:**
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { compile } from "@chanmeng666/archlang";
|
|
81
|
+
|
|
82
|
+
const source = `
|
|
83
|
+
plan "Tiny" {
|
|
84
|
+
units mm
|
|
85
|
+
grid 50
|
|
86
|
+
wall exterior thickness 200 { (0,0) (4000,0) (4000,3000) (0,3000) close }
|
|
87
|
+
room id=r at (0,0) size 4000x3000 label "Studio"
|
|
88
|
+
door at (2000,3000) width 900 wall exterior hinge left swing in
|
|
89
|
+
window at (0,1500) width 1200 wall exterior
|
|
90
|
+
}`;
|
|
91
|
+
|
|
92
|
+
const { svg, errors, warnings } = compile(source);
|
|
93
|
+
if (errors.length) console.error(errors);
|
|
94
|
+
else writeFileSync("tiny.svg", svg); // a finished floor plan
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**As a CLI:**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
arch compile floorplan.arch -o floorplan.svg # compile once
|
|
101
|
+
arch compile floorplan.arch -w 1000 # set output width (px)
|
|
102
|
+
arch watch floorplan.arch # recompile on save
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**A taste of the language** (see [`examples/`](examples) and the
|
|
106
|
+
[Language Reference](docs/language-reference.md)):
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
plan "Studio 1BR" {
|
|
110
|
+
units mm
|
|
111
|
+
grid 50
|
|
112
|
+
scale 1:50
|
|
113
|
+
north up
|
|
114
|
+
|
|
115
|
+
wall exterior thickness 200 { (0,0) (7000,0) (7000,6000) (0,6000) close }
|
|
116
|
+
wall partition thickness 100 { (4000,0) (4000,4000) }
|
|
117
|
+
|
|
118
|
+
room id=r_living at (0,0) size 4000x6000 label "Living / Kitchen"
|
|
119
|
+
room id=r_bed at (4000,0) size 3000x4000 label "Bedroom"
|
|
120
|
+
|
|
121
|
+
door id=d_main at (1000,6000) width 1000 wall exterior hinge left swing in
|
|
122
|
+
window at (2500,0) width 1800 wall exterior
|
|
123
|
+
|
|
124
|
+
dim (0,6000)->(7000,6000) offset 600 text "7000"
|
|
125
|
+
title { project "Studio Apartment" drawn_by "ArchLang" date "2026" }
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Try it live
|
|
130
|
+
|
|
131
|
+
The [`playground/`](playground) is a Vite + CodeMirror 6 app β a client-side editor with syntax
|
|
132
|
+
highlighting, inline lint (fed by the compiler's `diagnostics`), live SVG preview, example plans,
|
|
133
|
+
and SVG download. Run it with:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm run build # build the core (the playground consumes dist/)
|
|
137
|
+
npm install --prefix playground
|
|
138
|
+
npm run dev --prefix playground # open the printed localhost URL
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## π Documentation
|
|
142
|
+
|
|
143
|
+
- **[Language Reference](docs/language-reference.md)** β every statement, with syntax and defaults.
|
|
144
|
+
- **[Examples](examples)** β `studio.arch`, `two-bed.arch`.
|
|
145
|
+
- **[AGENTS.md](AGENTS.md)** β orientation for AI agents working in this repo.
|
|
146
|
+
|
|
147
|
+
## π€ Contributing
|
|
148
|
+
|
|
149
|
+
Contributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) and our
|
|
150
|
+
[Code of Conduct](CODE_OF_CONDUCT.md). Use the issue and pull-request templates when you open one.
|
|
151
|
+
|
|
152
|
+
## β€οΈ Support & Sponsor
|
|
153
|
+
|
|
154
|
+
- Questions? Open a [Discussion](https://github.com/chanmeng666/archlang/discussions) or see [SUPPORT.md](SUPPORT.md).
|
|
155
|
+
- Found a security issue? Follow [SECURITY.md](SECURITY.md).
|
|
156
|
+
- If this project helps you, consider [sponsoring](https://github.com/sponsors/ChanMeng666) β.
|
|
157
|
+
|
|
158
|
+
## π License
|
|
159
|
+
|
|
160
|
+
Released under the [MIT](LICENSE) license.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
<!-- CHAN MENG PERSONAL BRAND -->
|
|
165
|
+
<div align="center">
|
|
166
|
+
<a href="https://github.com/ChanMeng666" target="_blank">
|
|
167
|
+
<img src="./.github/brand/chan-meng-logo.svg" alt="Chan Meng" width="160" />
|
|
168
|
+
</a>
|
|
169
|
+
|
|
170
|
+
<p><strong>Chan Meng</strong><br/>Need a custom app like this one? I build them β let's talk.</p>
|
|
171
|
+
|
|
172
|
+
<a href="mailto:chanmeng.dev@gmail.com"><img src="https://img.shields.io/badge/Email-chanmeng.dev@gmail.com-EA4335?style=flat-square&logo=gmail&logoColor=white" alt="Email Chan Meng"/></a>
|
|
173
|
+
<a href="https://github.com/ChanMeng666"><img src="https://img.shields.io/badge/GitHub-ChanMeng666-181717?style=flat-square&logo=github&logoColor=white" alt="Chan Meng on GitHub"/></a>
|
|
174
|
+
</div>
|
|
175
|
+
<!-- /CHAN MENG PERSONAL BRAND -->
|
|
@@ -352,6 +352,15 @@ function mergeTheme(...layers) {
|
|
|
352
352
|
}
|
|
353
353
|
return out;
|
|
354
354
|
}
|
|
355
|
+
function sanitizeTheme(theme) {
|
|
356
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
357
|
+
const out = { ...theme };
|
|
358
|
+
for (const k of Object.keys(out)) {
|
|
359
|
+
const v = out[k];
|
|
360
|
+
if (typeof v === "string") out[k] = esc(v);
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
355
364
|
|
|
356
365
|
// src/geometry.ts
|
|
357
366
|
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
@@ -418,30 +427,23 @@ function segmentsOfWall(w) {
|
|
|
418
427
|
}
|
|
419
428
|
return segs;
|
|
420
429
|
}
|
|
421
|
-
function
|
|
430
|
+
function hostInfoForWalls(walls, at, ref) {
|
|
422
431
|
const candidates = ref ? walls.filter((w) => w.id === ref || w.category === ref) : walls;
|
|
423
|
-
let
|
|
432
|
+
let host = null;
|
|
424
433
|
let bestDist = Infinity;
|
|
434
|
+
let onWall = false;
|
|
425
435
|
for (const w of candidates) {
|
|
436
|
+
const tol = w.thickness / 2 + Math.max(w.thickness, 1);
|
|
426
437
|
for (const s of segmentsOfWall(w)) {
|
|
427
438
|
const dist = distPointToSegment(at, s.a, s.b);
|
|
428
439
|
if (dist < bestDist) {
|
|
429
440
|
bestDist = dist;
|
|
430
|
-
|
|
441
|
+
host = s;
|
|
431
442
|
}
|
|
443
|
+
if (!onWall && dist <= tol) onWall = true;
|
|
432
444
|
}
|
|
433
445
|
}
|
|
434
|
-
return
|
|
435
|
-
}
|
|
436
|
-
function isOnSomeWall(walls, at, ref) {
|
|
437
|
-
const candidates = ref ? walls.filter((w) => w.id === ref || w.category === ref) : walls;
|
|
438
|
-
for (const w of candidates) {
|
|
439
|
-
const tol = w.thickness / 2 + Math.max(w.thickness, 1);
|
|
440
|
-
for (const s of segmentsOfWall(w)) {
|
|
441
|
-
if (distPointToSegment(at, s.a, s.b) <= tol) return true;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return false;
|
|
446
|
+
return { host, onWall };
|
|
445
447
|
}
|
|
446
448
|
|
|
447
449
|
// src/hatches.ts
|
|
@@ -1427,6 +1429,15 @@ function resolve(ast) {
|
|
|
1427
1429
|
let activeEnv = /* @__PURE__ */ new Map();
|
|
1428
1430
|
const evalNum = (e) => evalExpr(e, activeEnv, (d) => diagnostics.push(d));
|
|
1429
1431
|
const evalPt = (p) => ({ x: evalNum(p.x), y: evalNum(p.y) });
|
|
1432
|
+
let hiKey = "";
|
|
1433
|
+
let hiVal = null;
|
|
1434
|
+
const hostInfo = (at, ref) => {
|
|
1435
|
+
const key = `${at.x},${at.y},${ref ?? ""}`;
|
|
1436
|
+
if (key === hiKey && hiVal) return hiVal;
|
|
1437
|
+
hiKey = key;
|
|
1438
|
+
hiVal = hostInfoForWalls(walls, at, ref);
|
|
1439
|
+
return hiVal;
|
|
1440
|
+
};
|
|
1430
1441
|
const ctx = {
|
|
1431
1442
|
grid: g,
|
|
1432
1443
|
snap,
|
|
@@ -1435,8 +1446,8 @@ function resolve(ast) {
|
|
|
1435
1446
|
evalPt,
|
|
1436
1447
|
id: "",
|
|
1437
1448
|
walls,
|
|
1438
|
-
hostSegment: (at, ref) =>
|
|
1439
|
-
isOnWall: (at, ref) =>
|
|
1449
|
+
hostSegment: (at, ref) => hostInfo(at, ref).host,
|
|
1450
|
+
isOnWall: (at, ref) => hostInfo(at, ref).onWall,
|
|
1440
1451
|
diag: (d) => diagnostics.push(d)
|
|
1441
1452
|
};
|
|
1442
1453
|
for (const def of registryOrder) {
|
|
@@ -1668,7 +1679,7 @@ function renderWalls(walls, ctx) {
|
|
|
1668
1679
|
return ops;
|
|
1669
1680
|
}
|
|
1670
1681
|
function render(ir, opts = {}) {
|
|
1671
|
-
const THEME = mergeTheme(DEFAULT_THEME, ir.theme, opts.theme);
|
|
1682
|
+
const THEME = sanitizeTheme(mergeTheme(DEFAULT_THEME, ir.theme, opts.theme));
|
|
1672
1683
|
const lw = THEME.lineWeight;
|
|
1673
1684
|
const b = planBounds(ir);
|
|
1674
1685
|
const drawW = b.maxX - b.minX;
|
|
@@ -1693,7 +1704,7 @@ function render(ir, opts = {}) {
|
|
|
1693
1704
|
const out = [];
|
|
1694
1705
|
const svgAttrs = opts.width ? `width="${fmt(opts.width)}" height="${fmt(opts.width * vbH / vbW)}"` : "";
|
|
1695
1706
|
out.push(
|
|
1696
|
-
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="${
|
|
1707
|
+
`<svg xmlns="http://www.w3.org/2000/svg" ${svgAttrs} viewBox="${fmt(vbX)} ${fmt(vbY)} ${fmt(vbW)} ${fmt(vbH)}" font-family="${THEME.font}">`
|
|
1697
1708
|
);
|
|
1698
1709
|
const hatchCtx = { fmt, gap: hatchGap, thin, base: THEME.pocheBase, line: THEME.pocheHatch };
|
|
1699
1710
|
const patterns = materialsUsed(ir.walls).map((m) => hatchPattern(m, hatchCtx)).join("");
|
|
@@ -1828,8 +1839,8 @@ function lineEnd(source, offset) {
|
|
|
1828
1839
|
}
|
|
1829
1840
|
function formatDiagnostic(source, d) {
|
|
1830
1841
|
const codeTag = d.code ? `[${d.code}]` : "";
|
|
1831
|
-
const
|
|
1832
|
-
const lines = [
|
|
1842
|
+
const header2 = `${d.severity}${codeTag}: ${d.message}`;
|
|
1843
|
+
const lines = [header2];
|
|
1833
1844
|
if (d.span) {
|
|
1834
1845
|
const { line, col } = offsetToLineCol(source, d.span.start);
|
|
1835
1846
|
const ls = lineStart(source, d.span.start);
|
|
@@ -1855,6 +1866,206 @@ function formatDiagnostic(source, d) {
|
|
|
1855
1866
|
return lines.join("\n");
|
|
1856
1867
|
}
|
|
1857
1868
|
|
|
1869
|
+
// src/export/dxf.ts
|
|
1870
|
+
function num(v) {
|
|
1871
|
+
const r = Math.round(v * 1e4) / 1e4;
|
|
1872
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
1873
|
+
}
|
|
1874
|
+
var DxfBuilder = class {
|
|
1875
|
+
out = [];
|
|
1876
|
+
/** group-code / value pair. */
|
|
1877
|
+
pair(code, value) {
|
|
1878
|
+
this.out.push(String(code), String(value));
|
|
1879
|
+
}
|
|
1880
|
+
line(layer, a, b) {
|
|
1881
|
+
this.pair(0, "LINE");
|
|
1882
|
+
this.pair(8, layer);
|
|
1883
|
+
this.pair(10, num(a.x));
|
|
1884
|
+
this.pair(20, num(-a.y));
|
|
1885
|
+
this.pair(11, num(b.x));
|
|
1886
|
+
this.pair(21, num(-b.y));
|
|
1887
|
+
}
|
|
1888
|
+
arc(layer, center, radius, startDeg, endDeg) {
|
|
1889
|
+
this.pair(0, "ARC");
|
|
1890
|
+
this.pair(8, layer);
|
|
1891
|
+
this.pair(10, num(center.x));
|
|
1892
|
+
this.pair(20, num(-center.y));
|
|
1893
|
+
this.pair(40, num(radius));
|
|
1894
|
+
this.pair(50, num(startDeg));
|
|
1895
|
+
this.pair(51, num(endDeg));
|
|
1896
|
+
}
|
|
1897
|
+
text(layer, at, height, value) {
|
|
1898
|
+
this.pair(0, "TEXT");
|
|
1899
|
+
this.pair(8, layer);
|
|
1900
|
+
this.pair(10, num(at.x));
|
|
1901
|
+
this.pair(20, num(-at.y));
|
|
1902
|
+
this.pair(40, num(height));
|
|
1903
|
+
this.pair(1, value.replace(/\n/g, " "));
|
|
1904
|
+
}
|
|
1905
|
+
rect(layer, corners) {
|
|
1906
|
+
for (let i = 0; i < corners.length; i++) {
|
|
1907
|
+
this.line(layer, corners[i], corners[(i + 1) % corners.length]);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
toString() {
|
|
1911
|
+
return this.out.join("\n") + "\n";
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
var LAYERS = ["WALLS", "ROOMS", "DOORS", "WINDOWS", "FURNITURE", "COLUMNS", "DIMS", "LABELS"];
|
|
1915
|
+
function header() {
|
|
1916
|
+
const h = [];
|
|
1917
|
+
const p = (c, v) => h.push(String(c), String(v));
|
|
1918
|
+
p(0, "SECTION");
|
|
1919
|
+
p(2, "HEADER");
|
|
1920
|
+
p(9, "$ACADVER");
|
|
1921
|
+
p(1, "AC1009");
|
|
1922
|
+
p(0, "ENDSEC");
|
|
1923
|
+
p(0, "SECTION");
|
|
1924
|
+
p(2, "TABLES");
|
|
1925
|
+
p(0, "TABLE");
|
|
1926
|
+
p(2, "LAYER");
|
|
1927
|
+
p(70, LAYERS.length);
|
|
1928
|
+
for (const name of LAYERS) {
|
|
1929
|
+
p(0, "LAYER");
|
|
1930
|
+
p(2, name);
|
|
1931
|
+
p(70, 0);
|
|
1932
|
+
p(62, 7);
|
|
1933
|
+
p(6, "CONTINUOUS");
|
|
1934
|
+
}
|
|
1935
|
+
p(0, "ENDTAB");
|
|
1936
|
+
p(0, "ENDSEC");
|
|
1937
|
+
return h.join("\n") + "\n";
|
|
1938
|
+
}
|
|
1939
|
+
function emitDoor(b, dr) {
|
|
1940
|
+
const seg = dr.host;
|
|
1941
|
+
if (!seg) return;
|
|
1942
|
+
const d = unit(sub(seg.b, seg.a));
|
|
1943
|
+
const n = normal(d);
|
|
1944
|
+
const hw = dr.width / 2;
|
|
1945
|
+
const hinge = dr.hinge === "left" ? add(dr.at, mul(d, -hw)) : add(dr.at, mul(d, hw));
|
|
1946
|
+
const farJamb = dr.hinge === "left" ? add(dr.at, mul(d, hw)) : add(dr.at, mul(d, -hw));
|
|
1947
|
+
const leafDir = dr.swing === "in" ? n : mul(n, -1);
|
|
1948
|
+
const leafEnd = add(hinge, mul(leafDir, dr.width));
|
|
1949
|
+
b.line("DOORS", hinge, leafEnd);
|
|
1950
|
+
const deg = (p) => Math.atan2(-(p.y - hinge.y), p.x - hinge.x) * 180 / Math.PI;
|
|
1951
|
+
const a1 = deg(leafEnd);
|
|
1952
|
+
const a2 = deg(farJamb);
|
|
1953
|
+
const ccw = ((a2 - a1) % 360 + 360) % 360;
|
|
1954
|
+
if (ccw <= 180) b.arc("DOORS", hinge, dr.width, a1, a2);
|
|
1955
|
+
else b.arc("DOORS", hinge, dr.width, a2, a1);
|
|
1956
|
+
}
|
|
1957
|
+
function emitWindow(b, wn) {
|
|
1958
|
+
const seg = wn.host;
|
|
1959
|
+
if (!seg) return;
|
|
1960
|
+
const d = unit(sub(seg.b, seg.a));
|
|
1961
|
+
const n = normal(d);
|
|
1962
|
+
const hw = wn.width / 2;
|
|
1963
|
+
const h = seg.thickness / 2;
|
|
1964
|
+
const jA = add(wn.at, mul(d, -hw));
|
|
1965
|
+
const jB = add(wn.at, mul(d, hw));
|
|
1966
|
+
b.line("WINDOWS", add(jA, mul(n, h)), add(jB, mul(n, h)));
|
|
1967
|
+
b.line("WINDOWS", add(jA, mul(n, -h)), add(jB, mul(n, -h)));
|
|
1968
|
+
b.line("WINDOWS", jA, jB);
|
|
1969
|
+
}
|
|
1970
|
+
function emitDim(b, dm) {
|
|
1971
|
+
const dd = unit(sub(dm.to, dm.from));
|
|
1972
|
+
const dn = normal(dd);
|
|
1973
|
+
const p1 = add(dm.from, mul(dn, dm.offset));
|
|
1974
|
+
const p2 = add(dm.to, mul(dn, dm.offset));
|
|
1975
|
+
b.line("DIMS", p1, p2);
|
|
1976
|
+
if (dm.text) {
|
|
1977
|
+
const mid = { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
|
|
1978
|
+
b.text("DIMS", mid, 150, dm.text);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function toDxf(ir) {
|
|
1982
|
+
const b = new DxfBuilder();
|
|
1983
|
+
b.pair(0, "SECTION");
|
|
1984
|
+
b.pair(2, "ENTITIES");
|
|
1985
|
+
const labelAt = (at, w, h) => ({ x: at.x + w / 2, y: at.y + h / 2 });
|
|
1986
|
+
for (const el of ir.elements) {
|
|
1987
|
+
switch (el.kind) {
|
|
1988
|
+
case "wall":
|
|
1989
|
+
for (const s of segmentsOfWall(el)) {
|
|
1990
|
+
const d = unit(sub(s.b, s.a));
|
|
1991
|
+
const n = normal(d);
|
|
1992
|
+
const off = s.thickness / 2;
|
|
1993
|
+
b.line("WALLS", add(s.a, mul(n, off)), add(s.b, mul(n, off)));
|
|
1994
|
+
b.line("WALLS", add(s.a, mul(n, -off)), add(s.b, mul(n, -off)));
|
|
1995
|
+
}
|
|
1996
|
+
break;
|
|
1997
|
+
case "room":
|
|
1998
|
+
b.rect("ROOMS", rectCorners(el.at.x, el.at.y, el.size.w, el.size.h));
|
|
1999
|
+
if (el.label) b.text("LABELS", labelAt(el.at, el.size.w, el.size.h), 200, el.label);
|
|
2000
|
+
break;
|
|
2001
|
+
case "furniture":
|
|
2002
|
+
b.rect("FURNITURE", rectCorners(el.at.x, el.at.y, el.size.w, el.size.h));
|
|
2003
|
+
if (el.label) b.text("LABELS", labelAt(el.at, el.size.w, el.size.h), 150, el.label);
|
|
2004
|
+
break;
|
|
2005
|
+
case "column":
|
|
2006
|
+
b.rect("COLUMNS", rectCorners(el.at.x, el.at.y, el.size.w, el.size.h));
|
|
2007
|
+
break;
|
|
2008
|
+
case "door":
|
|
2009
|
+
emitDoor(b, el);
|
|
2010
|
+
break;
|
|
2011
|
+
case "window":
|
|
2012
|
+
emitWindow(b, el);
|
|
2013
|
+
break;
|
|
2014
|
+
case "dim":
|
|
2015
|
+
emitDim(b, el);
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
b.pair(0, "ENDSEC");
|
|
2020
|
+
const entities = b.toString();
|
|
2021
|
+
return header() + entities + "0\nEOF\n";
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/export/pdf.ts
|
|
2025
|
+
function svgSize(svg) {
|
|
2026
|
+
const w = /<svg[^>]*\bwidth="([\d.]+)/.exec(svg);
|
|
2027
|
+
const h = /<svg[^>]*\bheight="([\d.]+)/.exec(svg);
|
|
2028
|
+
if (w && h) return { width: parseFloat(w[1]), height: parseFloat(h[1]) };
|
|
2029
|
+
const vb = /<svg[^>]*\bviewBox="[\d.\-]+ [\d.\-]+ ([\d.]+) ([\d.]+)"/.exec(svg);
|
|
2030
|
+
if (vb) return { width: parseFloat(vb[1]), height: parseFloat(vb[2]) };
|
|
2031
|
+
return { width: 800, height: 600 };
|
|
2032
|
+
}
|
|
2033
|
+
async function toPdf(svg) {
|
|
2034
|
+
let PDFDocument;
|
|
2035
|
+
let SVGtoPDF;
|
|
2036
|
+
try {
|
|
2037
|
+
PDFDocument = (await import("pdfkit")).default;
|
|
2038
|
+
SVGtoPDF = (await import("svg-to-pdfkit")).default;
|
|
2039
|
+
} catch {
|
|
2040
|
+
throw new Error(
|
|
2041
|
+
"PDF export needs the optional dependencies 'pdfkit' and 'svg-to-pdfkit'. Install them: npm install pdfkit svg-to-pdfkit"
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
const { width, height } = svgSize(svg);
|
|
2045
|
+
const doc = new PDFDocument({ size: [width, height], margin: 0 });
|
|
2046
|
+
const chunks = [];
|
|
2047
|
+
const done = new Promise((resolve2, reject) => {
|
|
2048
|
+
doc.on("data", (c) => chunks.push(c));
|
|
2049
|
+
doc.on("end", () => resolve2());
|
|
2050
|
+
doc.on("error", (e) => reject(e));
|
|
2051
|
+
});
|
|
2052
|
+
SVGtoPDF(doc, svg, 0, 0, { width, height, assumePt: true });
|
|
2053
|
+
doc.end();
|
|
2054
|
+
await done;
|
|
2055
|
+
return concat(chunks);
|
|
2056
|
+
}
|
|
2057
|
+
function concat(chunks) {
|
|
2058
|
+
let total = 0;
|
|
2059
|
+
for (const c of chunks) total += c.length;
|
|
2060
|
+
const out = new Uint8Array(total);
|
|
2061
|
+
let offset = 0;
|
|
2062
|
+
for (const c of chunks) {
|
|
2063
|
+
out.set(c, offset);
|
|
2064
|
+
offset += c.length;
|
|
2065
|
+
}
|
|
2066
|
+
return out;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
1858
2069
|
// src/index.ts
|
|
1859
2070
|
var cache = /* @__PURE__ */ new Map();
|
|
1860
2071
|
var CACHE_MAX = 64;
|
|
@@ -1894,9 +2105,12 @@ function clearCache() {
|
|
|
1894
2105
|
}
|
|
1895
2106
|
|
|
1896
2107
|
export {
|
|
2108
|
+
resolve,
|
|
1897
2109
|
offsetToLineCol,
|
|
1898
2110
|
formatDiagnostic,
|
|
2111
|
+
toDxf,
|
|
2112
|
+
toPdf,
|
|
1899
2113
|
compile,
|
|
1900
2114
|
clearCache
|
|
1901
2115
|
};
|
|
1902
|
-
//# sourceMappingURL=chunk-
|
|
2116
|
+
//# sourceMappingURL=chunk-PABYLU6Z.js.map
|