tsdraft 0.2.2 → 0.4.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/CHANGELOG.md +87 -14
- package/README.md +141 -212
- package/dist/callsite.d.ts +47 -0
- package/dist/callsite.js +82 -0
- package/dist/drawing.d.ts +164 -16
- package/dist/drawing.js +339 -75
- package/dist/dxf-r2018.js +36 -15
- package/dist/dxf.d.ts +1 -1
- package/dist/format.d.ts +7 -0
- package/dist/format.js +9 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -0
- package/dist/json.d.ts +21 -0
- package/dist/json.js +244 -0
- package/dist/layers.d.ts +1 -1
- package/dist/layers.js +2 -3
- package/dist/linetypes.d.ts +27 -0
- package/dist/linetypes.js +76 -0
- package/dist/lint.d.ts +19 -5
- package/dist/lint.js +132 -7
- package/dist/paper.d.ts +33 -0
- package/dist/paper.js +58 -0
- package/dist/rules.d.ts +190 -9
- package/dist/rules.js +239 -22
- package/dist/svg.d.ts +11 -2
- package/dist/svg.js +143 -10
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,97 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- `fromPaper(length, "in" | "mm", { ratio, units })` turns a length on paper
|
|
6
|
+
into drawing units, to size text, symbols and dashes by how large they plot:
|
|
7
|
+
`fromPaper(3 / 32, "in", { ratio: 96, units: "ft" })` is 0.75. A wrong unit,
|
|
8
|
+
and a length or ratio of 0 or less, are compile errors as literals and thrown
|
|
9
|
+
errors otherwise. A result that would be written as 0, or is 1e100 or more,
|
|
10
|
+
throws.
|
|
11
|
+
- `examples/site-plan.ts` is now the example sheet: a permit-style site plan for
|
|
12
|
+
an ARCH D sheet at 1" = 10', with property lines, setbacks, a building
|
|
13
|
+
footprint, dimensions, labels, site data, notes and a legend. Every text
|
|
14
|
+
height and annotation offset comes from `fromPaper`. Its default export is the drawing.
|
|
15
|
+
- Breaking: a `Drawing` has `format: 0`, its format version, which is separate
|
|
16
|
+
from `dxf`. The builder sets it. A drawing written by hand needs it, and
|
|
17
|
+
`lint` reports one without it under the new rule `format`. Format 0 carries
|
|
18
|
+
no promise of stability.
|
|
19
|
+
- Breaking: `LintRule` gained `format`, `id` and `duplicate-id`. Code that
|
|
20
|
+
switches over every rule, or uses `Record<LintRule, …>`, needs the three.
|
|
21
|
+
- `toJSON(drawing)` writes a drawing as canonical JSON: the same drawing gives
|
|
22
|
+
the same string, whatever order its keys were set in.
|
|
23
|
+
- `fromJSON(json)` reads a drawing from a JSON string or a parsed value. It
|
|
24
|
+
refuses a format version, a field or an entity kind it does not know, naming
|
|
25
|
+
each, then runs `lint`, and throws a `DrawingError`.
|
|
26
|
+
- Entities take an `id` in their options: 1 to 64 letters, digits, `.`, `-` and
|
|
27
|
+
`_`. A malformed id and a second entity with the same id are builder errors
|
|
28
|
+
and `lint` errors, under the new rules `id` and `duplicate-id`. `toSVG` writes
|
|
29
|
+
the id as `data-id`.
|
|
30
|
+
- A `Diagnostic` about an entity carries its `id`, and `call`, the file, line
|
|
31
|
+
and column of the call that drew it, when the drawing was built with
|
|
32
|
+
`createDrawing({ recordCalls: true })`. Setting
|
|
33
|
+
`globalThis.TSDRAFT_RECORD_CALLS = true` before a drawing's code runs turns
|
|
34
|
+
recording on for every builder that does not set the option, which is how a
|
|
35
|
+
tool that runs drawing files asks for it.
|
|
36
|
+
- The builder's `layer`, `linetype` and entity methods now throw a
|
|
37
|
+
`DrawingError` with the same message as before. Its diagnostic has the rule,
|
|
38
|
+
the `id` and the `call` that broke it.
|
|
39
|
+
- [docs/format.md](docs/format.md) describes all of the above.
|
|
4
40
|
|
|
5
|
-
|
|
41
|
+
## 0.3.0
|
|
42
|
+
|
|
43
|
+
- Breaking: `createDrawing()` now targets DXF R2018. Pass `{ dxf: "R12" }` for
|
|
44
|
+
R12, which gets no new features.
|
|
45
|
+
- Linetypes. `d.linetype({ name, description, pattern })` defines one, with
|
|
46
|
+
dashes as positive lengths, gaps as negative ones and dots as 0. `DASHED`,
|
|
47
|
+
`HIDDEN`, `CENTER`, `PHANTOM`, `DASHDOT` and `DOT` are exported with the
|
|
48
|
+
patterns AutoCAD loads from its `acad.lin`. `Continuous`, `ByLayer` and
|
|
49
|
+
`ByBlock` are in every drawing. `createDrawing({ linetypeScale })` sets
|
|
50
|
+
`$LTSCALE`.
|
|
51
|
+
- Lineweights in millimetres, from AutoCAD's 24.
|
|
52
|
+
- Layers take a `linetype`, a `lineweight`, and `off`, `frozen`, `locked` and
|
|
53
|
+
`plot`. Entities take a `linetype` and a `lineweight` of their own, and draw
|
|
54
|
+
ByLayer otherwise, as before.
|
|
55
|
+
- `toSVG` draws dashes and pen weights, and leaves out layers that are off or
|
|
56
|
+
frozen. Its `strokeWidth` is now the width of a 0.25 mm line.
|
|
57
|
+
- `toSVG` marks each entity's element with `data-entity`, its index in
|
|
58
|
+
`drawing.entities`, beside `data-layer`.
|
|
59
|
+
- New rules, each a builder error and a `lint` error, and a compile error where
|
|
60
|
+
the literal shows it:
|
|
61
|
+
- a lineweight outside AutoCAD's list, which makes AutoCAD discard the drawing
|
|
62
|
+
when an entity has it;
|
|
63
|
+
- a linetype the drawing does not define, which makes AutoCAD discard it;
|
|
64
|
+
- a pattern of one length, which `AUDIT` reports, or more than 12, which makes
|
|
65
|
+
AutoCAD discard the drawing;
|
|
66
|
+
- a pattern length that is not 0 but is written as 0, which would turn a dash
|
|
67
|
+
or a gap into a dot, and a pattern whose lengths add up to 1e100 or more,
|
|
68
|
+
because the total is written too;
|
|
69
|
+
- defining `Continuous`, `ByLayer` or `ByBlock`, a layer whose linetype is
|
|
70
|
+
`ByLayer` or `ByBlock`, and freezing layer `0`, which is the current layer;
|
|
71
|
+
- a linetype scale of 0 or less, or one written as 0, which AutoCAD reads as
|
|
72
|
+
1, and a description over 249 characters, where AutoCAD cuts it;
|
|
73
|
+
- linetype names follow the rules of layer names, which AutoCAD holds them to,
|
|
74
|
+
and a name that `AUDIT` would report as a duplicate of `Continuous`,
|
|
75
|
+
`ByLayer` or `ByBlock`, such as one with a soft hyphen in it;
|
|
76
|
+
- a description with a control character, an emoji or a private-use
|
|
77
|
+
character from U+E800 to U+E8FF, as for text;
|
|
78
|
+
- an R12 drawing that defines a linetype, sets a linetype scale, or gives a
|
|
79
|
+
layer a linetype other than `Continuous`, a lineweight or a state, and an R12
|
|
80
|
+
entity with a linetype or lineweight of its own. Each message says to use
|
|
81
|
+
R2018.
|
|
82
|
+
- A layer or an entity can be given a definition in place of a linetype name.
|
|
83
|
+
It is checked as `linetype()` checks one, and is defined if the drawing does
|
|
84
|
+
not have the name. If the drawing has the name with another pattern or
|
|
85
|
+
description, the builder throws: change a linetype with `linetype()`.
|
|
86
|
+
- `lint` has the rules `linetype`, `duplicate-linetype`, `undefined-linetype`,
|
|
87
|
+
`lineweight` and `layer-state`, and a diagnostic names its `linetype`.
|
|
88
|
+
|
|
89
|
+
## 0.2.2
|
|
6
90
|
|
|
7
91
|
- Fixed: AutoCAD keeps two layers such as `straße` and `STRASSE` apart, but its
|
|
8
92
|
`AUDIT` reports them as a duplicate name. `lint` now reports the second layer
|
|
9
93
|
as a duplicate, and the builder throws at the `layer()` call. It does so for
|
|
10
|
-
|
|
11
|
-
names:
|
|
94
|
+
these kinds of pair, in either letter case and inside longer names:
|
|
12
95
|
- a letter and the letters it stands for, such as `ß` and `ss`, `æ` and `ae`,
|
|
13
96
|
`œ` and `oe`, `ij` and `ij`, `fi` and `fi`, `dž` and `dž`, and `þ` and `th`;
|
|
14
97
|
- a composed character and its decomposed form, such as `é`;
|
|
@@ -21,13 +104,9 @@ Found by opening tsdraft's output in AutoCAD 2025.
|
|
|
21
104
|
- When an entity names its layer by the other spelling of such a pair, the
|
|
22
105
|
undefined-layer error now says which declared layer AutoCAD does not take it
|
|
23
106
|
for. AutoCAD adds a layer for the entity, and `AUDIT` reports a duplicate name.
|
|
24
|
-
- `pnpm test:cad` checks each kind of pair against AutoCAD, and 600 longer names
|
|
25
|
-
beside respellings of themselves.
|
|
26
107
|
|
|
27
108
|
## 0.2.1
|
|
28
109
|
|
|
29
|
-
Every fix below was found by opening tsdraft's output in AutoCAD 2025.
|
|
30
|
-
|
|
31
110
|
- Fixed: text holding `\U+XXXX` or `\M+NXXXX` came out in AutoCAD as the
|
|
32
111
|
character those escapes stand for, so `\U+00B0` became a degree sign and
|
|
33
112
|
`\U+000A` put a line feed inside a single-line text. Both DXF versions now
|
|
@@ -81,16 +160,11 @@ Every fix below was found by opening tsdraft's output in AutoCAD 2025.
|
|
|
81
160
|
and about a line, polyline or arc that loses its length to rounding.
|
|
82
161
|
- `lint` warns about text that contains `%%`, which AutoCAD stores as written
|
|
83
162
|
but draws as a control code: `%%d` is drawn as a degree sign.
|
|
84
|
-
- `pnpm test:cad` covers each fix above, and checks every character `lint`
|
|
85
|
-
accepts in text and in layer names, and each form of number the writers
|
|
86
|
-
print, against AutoCAD.
|
|
87
163
|
|
|
88
164
|
## 0.2.0
|
|
89
165
|
|
|
90
166
|
0.1.1 was never published, so its changes ship here too.
|
|
91
167
|
|
|
92
|
-
Every fix below comes from opening tsdraft's output in AutoCAD 2025.
|
|
93
|
-
|
|
94
168
|
- `createDrawing({ dxf: "R2018" })` targets AC1032, the DXF version AutoCAD has
|
|
95
169
|
used since 2018. It writes handles, owner links, the standard tables, model
|
|
96
170
|
and paper space blocks, plot styles and `$INSUNITS`. R12 stays the default.
|
|
@@ -119,7 +193,6 @@ Every fix below comes from opening tsdraft's output in AutoCAD 2025.
|
|
|
119
193
|
- Unknown units, and rectangle sizes of 0 or less, are rejected.
|
|
120
194
|
- Breaking: a `Drawing` now has a required `dxf` field, and R12 layer names
|
|
121
195
|
AutoCAD rejects now throw (see above).
|
|
122
|
-
- `pnpm test:cad` runs the output through AutoCAD over ssh.
|
|
123
196
|
- Published from GitHub Actions through npm trusted publishing, with a
|
|
124
197
|
provenance attestation linking the package to its source commit.
|
|
125
198
|
- `tsdraft/package.json` can be imported or resolved.
|
package/README.md
CHANGED
|
@@ -1,40 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
tsdraft
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Surfside-Software/tsdraft/main/docs/assets/tsdraft-logo-dark.svg">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Surfside-Software/tsdraft/main/docs/assets/tsdraft-logo-light.svg" alt="tsdraft" width="292" height="64">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
Write 2D CAD drawings in TypeScript and export them as DXF for CAD programs, or
|
|
7
|
+
as SVG for a quick preview.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/tsdraft)
|
|
10
|
+
[](https://github.com/Surfside-Software/tsdraft/actions/workflows/ci.yml)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
<a href="https://github.com/Surfside-Software/tsdraft/tree/main/examples">
|
|
14
|
+
<picture>
|
|
15
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Surfside-Software/tsdraft/main/docs/assets/outdoor-kitchen-dark.svg">
|
|
16
|
+
<img src="https://raw.githubusercontent.com/Surfside-Software/tsdraft/main/docs/assets/outdoor-kitchen-light.svg" alt="An outdoor kitchen drawn by tsdraft at 3/8 inch to the foot. On the left, an elevation of a stucco wall with an undercounter refrigerator, a built-in gas grill under a vent hood, a sink below a window and two shelves, with dimension strings and notes. On the right, a wall section through the grill from the footing to the attic: the reinforced masonry wall on its footing, the patio slab, the cabinet, the grill, the hood and its duct, and the ceilings with insulation." width="100%">
|
|
17
|
+
</picture>
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
The picture is what `toSVG` writes for
|
|
21
|
+
[`examples/outdoor-kitchen.ts`](examples/outdoor-kitchen.ts), restyled for this
|
|
22
|
+
page with a color for each layer. Its dimensions, leaders and stipple
|
|
23
|
+
are small functions in the example that draw lines and text, and every size
|
|
24
|
+
comes from a few parameters at the top of the file.
|
|
25
|
+
[`examples`](examples) has both drawings and how to run them.
|
|
26
|
+
|
|
27
|
+
A smaller drawing, start to finish:
|
|
26
28
|
|
|
27
29
|
```ts
|
|
28
|
-
import { createDrawing, toDXF, toSVG } from "tsdraft";
|
|
30
|
+
import { createDrawing, DASHED, PHANTOM, toDXF, toSVG } from "tsdraft";
|
|
31
|
+
|
|
32
|
+
const lot = { width: 120, depth: 80 }; // ft
|
|
33
|
+
const setbacks = { front: 20, side: 10, rear: 15 };
|
|
34
|
+
const house = { width: 56, depth: 32 };
|
|
35
|
+
const x = (lot.width - house.width) / 2;
|
|
36
|
+
const y = setbacks.front + 5;
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
// Linetype patterns are in drawing units. At a scale of 4, DASHED draws dashes of 2 ft and
|
|
39
|
+
// PHANTOM long dashes of 5 ft.
|
|
40
|
+
const d = createDrawing({ units: "ft", linetypeScale: 4 });
|
|
31
41
|
|
|
32
|
-
d.layer("C-PROP", { color: 3 }, () => {
|
|
33
|
-
d.rect([0, 0],
|
|
42
|
+
d.layer("C-PROP", { color: 3, linetype: PHANTOM, lineweight: 0.5 }, () => {
|
|
43
|
+
d.rect([0, 0], lot.width, lot.depth);
|
|
44
|
+
d.text([2, -6], `LOT ${lot.width}' x ${lot.depth}'`, { height: 3 });
|
|
34
45
|
});
|
|
35
|
-
d.layer("
|
|
36
|
-
|
|
37
|
-
d.
|
|
46
|
+
d.layer("C-SETB", { color: 4, linetype: DASHED, lineweight: 0.25 }, () => {
|
|
47
|
+
const { front, side, rear } = setbacks;
|
|
48
|
+
d.rect([side, front], lot.width - side * 2, lot.depth - front - rear);
|
|
49
|
+
});
|
|
50
|
+
d.layer("A-BLDG", { color: 1, lineweight: 0.7 }, () => {
|
|
51
|
+
d.rect([x, y], house.width, house.depth);
|
|
52
|
+
d.text([x + 2, y + house.depth / 2], "PROPOSED RESIDENCE", { height: 3, lineweight: 0.35 });
|
|
53
|
+
});
|
|
54
|
+
d.layer("C-PAVE", { color: 8, lineweight: 0.25 }, () => {
|
|
55
|
+
d.line([x + 40, y], [x + 40, -12]);
|
|
56
|
+
d.line([x + 52, y], [x + 52, -12]);
|
|
57
|
+
});
|
|
58
|
+
d.layer("C-ROAD", { color: 2, lineweight: 0.35 }, () => {
|
|
59
|
+
d.line([-10, -12], [lot.width + 10, -12]);
|
|
60
|
+
d.text([lot.width / 2 - 10, -18], "MAIN STREET", { height: 3 });
|
|
61
|
+
});
|
|
62
|
+
d.layer("V-TREE", { color: 3, lineweight: 0.18 }, () => {
|
|
63
|
+
for (let i = 0; i < 6; i++) d.circle([15 + i * 18, 72], 5);
|
|
38
64
|
});
|
|
39
65
|
|
|
40
66
|
const drawing = d.build();
|
|
@@ -42,35 +68,76 @@ const dxf = toDXF(drawing);
|
|
|
42
68
|
const svg = toSVG(drawing);
|
|
43
69
|
```
|
|
44
70
|
|
|
71
|
+
A drawing is a program, so dimensions can come from variables, drawings can
|
|
72
|
+
share code, and changes show up as diffs.
|
|
73
|
+
|
|
45
74
|
Coordinates are in drawing units with Y pointing up, as in CAD. Angles are in
|
|
46
75
|
degrees, counterclockwise from +X. Layer colors are AutoCAD Color Index numbers.
|
|
47
76
|
Entities go on layer `0` unless they are drawn inside a `layer()` callback.
|
|
77
|
+
Linetypes and lineweights are set on a layer, and an entity draws ByLayer unless
|
|
78
|
+
it is given its own. `DASHED`, `HIDDEN`, `CENTER`, `PHANTOM`, `DASHDOT` and `DOT`
|
|
79
|
+
are AutoCAD's own definitions; `d.linetype({ name, pattern })` defines another,
|
|
80
|
+
with dashes as positive lengths, gaps as negative ones and dots as 0. A
|
|
81
|
+
lineweight is in millimetres.
|
|
48
82
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
83
|
+
Text, symbols and dashes are sized by how large they plot. `fromPaper` turns a
|
|
84
|
+
length on paper into drawing units for one scale, given as a plain ratio:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { fromPaper } from "tsdraft";
|
|
88
|
+
|
|
89
|
+
const view = { ratio: 120, units: "ft" } as const; // 1" = 10'
|
|
90
|
+
const note = fromPaper(3 / 32, "in", view); // 0.9375 ft
|
|
91
|
+
d.text([0, 0], "PROPERTY LINE", { height: note });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A paper unit other than `"in"` or `"mm"`, and a length or a ratio of 0 or less,
|
|
95
|
+
are compile errors as literals and thrown errors otherwise. `fromPaper` also
|
|
96
|
+
throws when the result would be written as 0 or is 1e100 or more.
|
|
97
|
+
[`examples/site-plan.ts`](examples/site-plan.ts) is a whole sheet sized this
|
|
98
|
+
way, and the file to copy to start a drawing.
|
|
99
|
+
|
|
100
|
+
## Status
|
|
101
|
+
|
|
102
|
+
tsdraft is at an early stage. It draws layers, lines, polylines, rectangles,
|
|
103
|
+
circles, arcs and single-line text, with linetypes and lineweights, and layers
|
|
104
|
+
that are off, frozen, locked or not plotted. It does not yet have dimensions,
|
|
105
|
+
hatches, blocks or paper space layouts. It writes DXF, not DWG. The drawing at
|
|
106
|
+
the top builds its dimensions, leaders and stipple from lines and text, so in
|
|
107
|
+
CAD they are plain lines and text, not dimension or hatch objects.
|
|
108
|
+
|
|
109
|
+
The rules tsdraft checks are AutoCAD's. Other CAD programs may hold a drawing to
|
|
110
|
+
other rules.
|
|
111
|
+
|
|
112
|
+
## Install
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npm install tsdraft
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
tsdraft is an ES module and needs Node 22 or later.
|
|
54
119
|
|
|
55
120
|
## DXF versions
|
|
56
121
|
|
|
57
|
-
A drawing is written for one DXF version. `createDrawing()` targets
|
|
58
|
-
|
|
59
|
-
`createDrawing({ dxf: "
|
|
60
|
-
|
|
122
|
+
A drawing is written for one DXF version. `createDrawing()` targets R2018, the
|
|
123
|
+
version AutoCAD has used since 2018, which is the newest one Autodesk has
|
|
124
|
+
published. `createDrawing({ dxf: "R12" })` targets R12, the oldest version still
|
|
125
|
+
in common use, which nearly every CAD program can open. `toDXF(drawing)` writes
|
|
61
126
|
the drawing's version, and `toDXF(drawing, { version })` writes another one.
|
|
62
127
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| Format code
|
|
66
|
-
| Drawing units | Not stored. Set them in CAD with `UNITS`. |
|
|
67
|
-
| Polylines
|
|
68
|
-
| Text
|
|
69
|
-
| Layer names
|
|
128
|
+
| | `"R2018"`, the default | `"R12"` |
|
|
129
|
+
| ------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------- |
|
|
130
|
+
| Format code | AC1032 | AC1009 |
|
|
131
|
+
| Drawing units | Stored in `$INSUNITS`. | Not stored. Set them in CAD with `UNITS`. |
|
|
132
|
+
| Polylines | `LWPOLYLINE` | `POLYLINE` with `VERTEX` records |
|
|
133
|
+
| Text | UTF-8 | ASCII, with `\U+XXXX` escapes for other characters |
|
|
134
|
+
| Layer names | Up to 255 characters, except those in [the rules](docs/rules.md#errors) | Up to 31 letters, digits, `$`, `-` and `_` |
|
|
135
|
+
| Linetypes, lineweights | Yes | No: only `Continuous`, and no lineweights |
|
|
136
|
+
| Layer off, frozen, locked | Yes, and not plotted | No |
|
|
70
137
|
|
|
71
|
-
|
|
72
|
-
needs the stored units to scale
|
|
73
|
-
R12 file in upper case.
|
|
138
|
+
R12 gets no new features. Choose it only for a program that cannot read R2018.
|
|
139
|
+
CAD needs the stored units to scale a drawing inserted into another, and AutoCAD
|
|
140
|
+
shows layer names from an R12 file in upper case.
|
|
74
141
|
|
|
75
142
|
## Checks
|
|
76
143
|
|
|
@@ -87,148 +154,28 @@ three points, so the mistake shows up where you made it.
|
|
|
87
154
|
AutoCAD would reject.
|
|
88
155
|
|
|
89
156
|
```ts
|
|
90
|
-
const d = createDrawing({ units: "ft" });
|
|
157
|
+
const d = createDrawing({ units: "ft", dxf: "R12" });
|
|
91
158
|
|
|
92
159
|
d.layer("A WALL");
|
|
93
160
|
// error TS2345: Argument of type '"A WALL"' is not assignable to parameter of
|
|
94
161
|
// type '"R12 layer names can only use letters, digits, $, - and _. Use DXF
|
|
95
162
|
// version \"R2018\" for other characters"'.
|
|
96
163
|
|
|
97
|
-
createDrawing(
|
|
164
|
+
createDrawing().layer("A WALL"); // fine
|
|
98
165
|
|
|
99
166
|
lint(drawing, { version: "R12" }); // check a drawing against another version
|
|
100
167
|
```
|
|
101
168
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- Layer names
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
does: see [Layer names and case](#layer-names-and-case).
|
|
113
|
-
- Layer colors are whole numbers from 1 to 255.
|
|
114
|
-
- Radii, text heights and rectangle sizes are greater than 0, and large enough
|
|
115
|
-
not to be written as 0. Anything from 0.000001 up is safe. AutoCAD gives text
|
|
116
|
-
of height 0 its default height.
|
|
117
|
-
- Text is a single line. It cannot contain emoji or other characters above
|
|
118
|
-
U+FFFF, because AutoCAD cannot read them from DXF, or the private-use
|
|
119
|
-
characters U+E800 to U+E8FF, which AutoCAD replaces.
|
|
120
|
-
- Coordinates, sizes and text rotations are under 1e100 in size, because
|
|
121
|
-
AutoCAD reads only two exponent digits from DXF.
|
|
122
|
-
- Arc angles are stored from 0 up to 360, so -90 becomes 270. An arc cannot come
|
|
123
|
-
within 0.000001 degrees of a full turn, because its two angles would be
|
|
124
|
-
written the same and AutoCAD holds such an arc as having no length. Draw a
|
|
125
|
-
circle instead.
|
|
126
|
-
|
|
127
|
-
`lint` also returns warnings. Warnings do not stop an export. They are:
|
|
128
|
-
|
|
129
|
-
- A line, a polyline or an arc that has no length, or has none once its numbers
|
|
130
|
-
are rounded.
|
|
131
|
-
- Text that contains `%%`. AutoCAD stores the text as written but reads `%%` as
|
|
132
|
-
the start of a control code when it draws it: `%%d` is drawn as a degree sign
|
|
133
|
-
and `%%%` as one percent sign.
|
|
134
|
-
|
|
135
|
-
`toDXF` throws a `DrawingError` for errors only. Its `diagnostics` property holds
|
|
136
|
-
them, and other code may throw one to refuse a drawing for its own reasons.
|
|
137
|
-
|
|
138
|
-
### Text
|
|
139
|
-
|
|
140
|
-
When AutoCAD opens tsdraft's DXF, it holds each text exactly as the drawing has
|
|
141
|
-
it. tsdraft escapes what DXF would otherwise read as something else: a caret,
|
|
142
|
-
and a backslash that starts `\U+XXXX` or `\M+NXXXX`, which AutoCAD decodes into
|
|
143
|
-
a character. A text `\U+00B0` reaches AutoCAD as those seven characters and not
|
|
144
|
-
as a degree sign. Write the character itself, `°`, to get the character.
|
|
145
|
-
|
|
146
|
-
That lasts until AutoCAD saves the drawing, because AutoCAD does not escape these
|
|
147
|
-
sequences in what it writes. After it saves as DWG and opens the file again, a
|
|
148
|
-
text `\M+18140` holds the character U+3000. After it saves as DXF and opens
|
|
149
|
-
that, `\U+00B0` holds a degree sign as well. tsdraft cannot change this.
|
|
150
|
-
|
|
151
|
-
### Layer names and case
|
|
152
|
-
|
|
153
|
-
AutoCAD lower-cases a layer name one character at a time, from a table of its
|
|
154
|
-
own that is older and smaller than Unicode's. An entity on `mixedcase`, `café`,
|
|
155
|
-
`αβγ` or `СЛОЙ` goes on a layer declared as `MixedCase`, `CAFÉ`, `ΑΒΓ` or `Слой`,
|
|
156
|
-
and `lint` accepts it. AutoCAD does not pair these, and `lint` reports the
|
|
157
|
-
entity's layer as undefined:
|
|
158
|
-
|
|
159
|
-
- `ΟΔΟΣ` and `οδος`, because of the final sigma `ς`. `οδοσ` does match.
|
|
160
|
-
- `STRASSE` and `straße`, and `TITLE` and `tıtle` with a dotless `ı`.
|
|
161
|
-
- `Ÿ` and `ÿ`, `İ` and `i`, and the title-case digraphs such as `Dž`.
|
|
162
|
-
- Georgian, Cherokee and most other letters that gained a case after Unicode
|
|
163
|
-
5.1.
|
|
164
|
-
|
|
165
|
-
Where `AUDIT` would take the entity's spelling for the declared layer's, as it
|
|
166
|
-
does for `STRASSE` and `straße`, the message says so. AutoCAD adds a layer for
|
|
167
|
-
the entity, and `AUDIT` then reports a duplicate name.
|
|
168
|
-
|
|
169
|
-
### Layer names that AUDIT calls duplicates
|
|
170
|
-
|
|
171
|
-
AutoCAD's `AUDIT` compares layer names another way than AutoCAD looks them up.
|
|
172
|
-
It reports "Duplicate name" for some pairs of layers that AutoCAD keeps as two.
|
|
173
|
-
`lint` reports the second layer of such a pair as a duplicate, which is an
|
|
174
|
-
error, for these kinds of pair. Each kind holds in either letter case and
|
|
175
|
-
inside a longer name, so `Hauptstraße 12` and `HAUPTSTRASSE 12` are a pair.
|
|
176
|
-
|
|
177
|
-
- Single letters: `σ` and `ς`, `Ÿ` and `ÿ`, `ß` and `ẞ`, a title-case digraph
|
|
178
|
-
such as `Dž` and `dž`, and a few others.
|
|
179
|
-
- A letter and the letters it stands for: `ß` and `ss`, `æ` and `ae`, `œ` and
|
|
180
|
-
`oe`, `ij` and `ij`, `þ` and `th`, `dž` and `dž`, `lj` and `lj`, `nj` and `nj`,
|
|
181
|
-
`dz` and `dz`, the ligatures `ff`, `fi`, `fl`, `ffi`, `ffl` and `st`, and `ȸ`, `ȹ`,
|
|
182
|
-
`ʪ`, `ʫ`, `ꜳ`, `ꜵ`, `ꜷ`, `ꜹ`, `ꜽ`, `ꝏ` and `ᴂ`.
|
|
183
|
-
- A composed character and its decomposed form, such as `é` and an `e` with a
|
|
184
|
-
combining acute accent, with its marks in any order Unicode counts as the
|
|
185
|
-
same, such as a dot below before or after a circumflex. Hangul syllables are
|
|
186
|
-
an exception, and so are 40 Arabic, Indic, Myanmar, kana, Hebrew and CJK
|
|
187
|
-
compatibility characters: `AUDIT` keeps those apart from their decomposed
|
|
188
|
-
forms.
|
|
189
|
-
- A name with a soft hyphen, U+00AD, inside it or in front of it, and the same
|
|
190
|
-
name without.
|
|
191
|
-
|
|
192
|
-
This is what was measured in AutoCAD 2025. It is not AutoCAD's whole comparison,
|
|
193
|
-
so a drawing that passes `lint` can still get a "Duplicate name" from `AUDIT`.
|
|
194
|
-
The rule is kept narrow so that it does not go the other way: in over 20,000
|
|
195
|
-
pairs tried, `lint` reported none that `AUDIT` accepts. What it leaves out:
|
|
196
|
-
|
|
197
|
-
- A name that ends with a soft hyphen and the same name without it, and a soft
|
|
198
|
-
hyphen in front of a combining mark or a Hangul jamo. `AUDIT` reports some of
|
|
199
|
-
those pairs and not others. It does not report `coop` and `coop` followed by a
|
|
200
|
-
soft hyphen. Two names that both end with a soft hyphen are compared like any
|
|
201
|
-
others.
|
|
202
|
-
- Compatibility characters that `AUDIT` takes for their plain forms, such as a
|
|
203
|
-
modifier letter `ʰ` and `h`, `㎏` and `kg`, and Arabic presentation forms.
|
|
204
|
-
- Characters that `AUDIT` cannot tell from an unassigned one, such as `Ԥ`,
|
|
205
|
-
U+0524, and `Ꙡ`, U+A660. Two names that differ only in such characters are
|
|
206
|
-
duplicates to it.
|
|
207
|
-
|
|
208
|
-
### Layer name characters
|
|
209
|
-
|
|
210
|
-
AutoCAD refuses these characters in a layer name, so `lint` does too. Over any
|
|
211
|
-
of them but the last row's, AutoCAD discards the whole drawing. All are outside
|
|
212
|
-
ASCII, so they only concern R2018 drawings.
|
|
213
|
-
|
|
214
|
-
| Characters | What they are |
|
|
215
|
-
| -------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
216
|
-
| U+0080 to U+009F | C1 control characters |
|
|
217
|
-
| U+0600 to U+0603, U+06DD, U+070F, U+17B4, U+17B5 | Arabic, Syriac and Khmer formatting characters |
|
|
218
|
-
| U+200B to U+200F, U+202A to U+202E, U+2060 to U+2064, U+206A to U+206F | Zero-width spaces and joiners, and text direction controls |
|
|
219
|
-
| U+FEFF, U+FFF9 to U+FFFB | The byte order mark and annotation characters |
|
|
220
|
-
| U+2071, U+207F, U+2090 to U+2094 | Superscript and subscript letters |
|
|
221
|
-
| Letters in U+2102 to U+214E, such as `ℓ`, `ℕ`, the ohm sign and the kelvin | Letterlike symbols. Use the Greek `Ω`, U+03A9, for ohms. |
|
|
222
|
-
| U+2180 to U+2188 | Roman numeral characters |
|
|
223
|
-
| U+2C00 to U+2C5E, except U+2C2F | Glagolitic |
|
|
224
|
-
| U+2C60 to U+2C7D, except U+2C70 | Latin Extended-C |
|
|
225
|
-
| U+2C80 to U+2CE4 | Coptic |
|
|
226
|
-
| U+2D00 to U+2D25 | Georgian Supplement |
|
|
227
|
-
| U+2D30 to U+2D65, U+2D6F | Tifinagh |
|
|
228
|
-
| Letters in U+2D80 to U+2DDE | Ethiopic Extended |
|
|
229
|
-
| U+E800 to U+E8FF | Private use. AutoCAD replaces these and keeps the drawing. |
|
|
230
|
-
|
|
231
|
-
`src/layers.ts` has the exact list.
|
|
169
|
+
## Reference
|
|
170
|
+
|
|
171
|
+
- [Rules](docs/rules.md) lists what `lint` refuses and what it warns about.
|
|
172
|
+
- [The Drawing format](docs/format.md) covers format 0, entity ids, `toJSON`
|
|
173
|
+
and `fromJSON`, and what a diagnostic carries.
|
|
174
|
+
- [Layer names](docs/layer-names.md) explains how AutoCAD compares layer and
|
|
175
|
+
linetype names, and lists the pairs `AUDIT` calls duplicates and the
|
|
176
|
+
characters it refuses.
|
|
177
|
+
- [Text](docs/text.md) explains what tsdraft escapes in text, and what AutoCAD
|
|
178
|
+
does with it after a save.
|
|
232
179
|
|
|
233
180
|
## Development
|
|
234
181
|
|
|
@@ -237,37 +184,19 @@ directly, so tests and examples need no build step.
|
|
|
237
184
|
|
|
238
185
|
```sh
|
|
239
186
|
pnpm install
|
|
240
|
-
pnpm verify
|
|
241
|
-
pnpm example
|
|
187
|
+
pnpm verify # format check, lint, type check, tests, package smoke test
|
|
188
|
+
pnpm example # writes both examples to examples/out/ as DXF and SVG
|
|
189
|
+
pnpm readme:drawing # redraws the pictures in docs/assets/ from the examples
|
|
242
190
|
```
|
|
243
191
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
`pnpm test:package` packs the package, installs the tarball into a throwaway
|
|
253
|
-
project and checks that it type-checks and runs there. `pnpm build` compiles
|
|
254
|
-
`src/` to `dist/`, and `npm pack` and `npm publish` run it automatically.
|
|
255
|
-
|
|
256
|
-
The official TypeScript 7 compiler checks and builds the package.
|
|
257
|
-
[tsrs](https://github.com/oxc-project/tsrs), the Rust port of TypeScript, runs
|
|
258
|
-
as `pnpm check:tsrs` and as an informational CI step. It tracks TypeScript 7.1
|
|
259
|
-
beta, so it does not gate releases yet. Its WebAssembly build is the planned
|
|
260
|
-
route for type-checking drawings in a browser playground.
|
|
261
|
-
|
|
262
|
-
## Releasing
|
|
263
|
-
|
|
264
|
-
Bump `version` in `package.json` and add a CHANGELOG entry in a pull request.
|
|
265
|
-
After it merges, publish a GitHub release tagged `v<version>` on the merge
|
|
266
|
-
commit. The Release workflow checks that the tag matches the version, runs
|
|
267
|
-
`pnpm verify` and publishes to npm through
|
|
268
|
-
[trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm token
|
|
269
|
-
is stored anywhere. npm adds a provenance attestation that links each version to
|
|
270
|
-
the commit it was built from.
|
|
192
|
+
[docs/development.md](docs/development.md) covers the package smoke test, the
|
|
193
|
+
TypeScript compilers and releasing.
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
tsdraft is developed in a private repository and published here through pull
|
|
198
|
+
requests from the `export` branch. Issues are welcome.
|
|
199
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md) says how changes arrive.
|
|
271
200
|
|
|
272
201
|
## License
|
|
273
202
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** A place in a source file, as the JavaScript engine names it in a stack. */
|
|
2
|
+
export interface CallSite {
|
|
3
|
+
/** A `file://` URL for an ES module in Node, a path for CommonJS, and a URL in a browser. */
|
|
4
|
+
readonly file: string;
|
|
5
|
+
/** Counted from 1. */
|
|
6
|
+
readonly line: number;
|
|
7
|
+
/** Counted from 1. */
|
|
8
|
+
readonly column: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A stack that the engine has captured and formats when `stack` is first read. `whole` is true
|
|
12
|
+
* for a stack that starts inside tsdraft, and false for one cut down to the caller's frame.
|
|
13
|
+
*/
|
|
14
|
+
export interface HeldCall {
|
|
15
|
+
readonly stack?: unknown;
|
|
16
|
+
readonly whole: boolean;
|
|
17
|
+
}
|
|
18
|
+
type Method = (...args: never[]) => unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Captures the call of the builder method `method` that is running now, to be read later, or
|
|
21
|
+
* returns undefined where the engine gives no stack. It must be called from the builder's file.
|
|
22
|
+
*
|
|
23
|
+
* With `Error.captureStackTrace` the engine leaves out every frame down to `method`, so only the
|
|
24
|
+
* caller's frame is captured. Without it, the whole stack of a new error is kept.
|
|
25
|
+
*/
|
|
26
|
+
export declare function holdCall(method: Method): HeldCall | undefined;
|
|
27
|
+
/** The location a held call names, or undefined when its stack has none. */
|
|
28
|
+
export declare function readCall(held: HeldCall | undefined): CallSite | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The caller's frame in a whole stack captured by `holdCall`: the first frame in neither of
|
|
31
|
+
* tsdraft's two files on it, which are those of the first frame, `holdCall`'s own, and of the
|
|
32
|
+
* second, the builder's. Frames are told apart by file and not counted, because an engine may
|
|
33
|
+
* leave a frame out. When the caller's code is in one file with tsdraft, as in a bundle, no
|
|
34
|
+
* frame qualifies and there is no location, which is better than a line inside tsdraft.
|
|
35
|
+
*/
|
|
36
|
+
export declare function callerInStack(stack: string): CallSite | undefined;
|
|
37
|
+
/** Keeps the calls that drew `entities`, one for each entity in order. */
|
|
38
|
+
export declare function recordCalls(entities: readonly object[], calls: readonly (HeldCall | undefined)[]): void;
|
|
39
|
+
/** The call that drew the entity at `index` of `entities`, when the builder recorded it. */
|
|
40
|
+
export declare function callOf(entities: readonly object[], index: number): CallSite | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* The location of each frame of a stack, innermost first. V8 writes a frame as
|
|
43
|
+
* `at name (file:1:2)` or `at file:1:2`, and Firefox and Safari as `name@file:1:2`. A line that
|
|
44
|
+
* is no frame, such as the error's message, is left out.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseStack(stack: string): CallSite[];
|
|
47
|
+
export {};
|