@snaptrude/plugin-core 0.9.16-networks-20261012.1 → 0.9.17
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 +6 -0
- package/api-manifest.json +9711 -0
- package/dist/api/analysis/daylight.d.ts +5 -5
- package/dist/api/analysis/solar.d.ts +2 -2
- package/dist/api/core/camera/index.d.ts +0 -29
- package/dist/api/core/camera/index.d.ts.map +1 -1
- package/dist/api/core/zoom/index.d.ts +0 -13
- package/dist/api/core/zoom/index.d.ts.map +1 -1
- package/dist/api/design/create/bulk-items.d.ts +21 -40
- package/dist/api/design/create/bulk-items.d.ts.map +1 -1
- package/dist/api/design/create/index.d.ts +97 -120
- package/dist/api/design/create/index.d.ts.map +1 -1
- package/dist/api/design/create/opening-fields.d.ts +12 -13
- package/dist/api/design/create/opening-fields.d.ts.map +1 -1
- package/dist/api/design/doors/index.d.ts +24 -18
- package/dist/api/design/doors/index.d.ts.map +1 -1
- package/dist/api/design/family.d.ts +27 -797
- package/dist/api/design/family.d.ts.map +1 -1
- package/dist/api/design/index.d.ts +0 -16
- package/dist/api/design/index.d.ts.map +1 -1
- package/dist/api/design/query/index.d.ts +30 -30
- package/dist/api/design/selection/index.d.ts +10 -10
- package/dist/api/design/types/index.d.ts +24 -40
- package/dist/api/design/types/index.d.ts.map +1 -1
- package/dist/api/design/update/index.d.ts +3 -40
- package/dist/api/design/update/index.d.ts.map +1 -1
- package/dist/api/design/windows/index.d.ts +8 -8
- package/dist/api/design/windows/index.d.ts.map +1 -1
- package/dist/api/presentation/aiInspiration.d.ts +10 -10
- package/dist/api/presentation/annotate.d.ts +22 -38
- package/dist/api/presentation/annotate.d.ts.map +1 -1
- package/dist/api/presentation/export.d.ts +5 -23
- package/dist/api/presentation/export.d.ts.map +1 -1
- package/dist/api/presentation/index.d.ts +21 -32
- package/dist/api/presentation/index.d.ts.map +1 -1
- package/dist/api/presentation/placedViews.d.ts +6 -106
- package/dist/api/presentation/placedViews.d.ts.map +1 -1
- package/dist/api/presentation/sections.d.ts +461 -0
- package/dist/api/presentation/sections.d.ts.map +1 -0
- package/dist/api/presentation/shapes.d.ts +2 -2
- package/dist/api/presentation/sheets.d.ts +19 -175
- package/dist/api/presentation/sheets.d.ts.map +1 -1
- package/dist/api/presentation/views.d.ts +5 -103
- package/dist/api/presentation/views.d.ts.map +1 -1
- package/dist/api/presentation/walkthroughs.d.ts +6 -6
- package/dist/index.cjs +2434 -3619
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2421 -3501
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-manifest.mjs +0 -5
- package/src/api/core/camera/index.ts +0 -29
- package/src/api/core/zoom/index.ts +0 -13
- package/src/api/design/create/bulk-items.ts +16 -23
- package/src/api/design/create/index.ts +119 -129
- package/src/api/design/create/opening-fields.ts +12 -15
- package/src/api/design/doors/index.ts +42 -58
- package/src/api/design/family.ts +32 -662
- package/src/api/design/index.ts +4 -34
- package/src/api/design/types/index.ts +24 -31
- package/src/api/design/update/index.ts +3 -41
- package/src/api/design/windows/index.ts +22 -40
- package/src/api/presentation/annotate.ts +58 -90
- package/src/api/presentation/export.ts +6 -32
- package/src/api/presentation/index.ts +23 -39
- package/src/api/presentation/placedViews.ts +28 -119
- package/src/api/presentation/sections.ts +420 -0
- package/src/api/presentation/sheets.ts +20 -181
- package/src/api/presentation/views.ts +6 -46
- package/dist/api/design/bim.d.ts +0 -209
- package/dist/api/design/bim.d.ts.map +0 -1
- package/dist/api/design/catalog.d.ts +0 -320
- package/dist/api/design/catalog.d.ts.map +0 -1
- package/dist/api/design/networks.d.ts +0 -1703
- package/dist/api/design/networks.d.ts.map +0 -1
- package/dist/api/presentation/drawingContract.d.ts +0 -32
- package/dist/api/presentation/drawingContract.d.ts.map +0 -1
- package/dist/api/presentation/drawings.d.ts +0 -3523
- package/dist/api/presentation/drawings.d.ts.map +0 -1
- package/dist/api/presentation/viewPresets.d.ts +0 -222
- package/dist/api/presentation/viewPresets.d.ts.map +0 -1
- package/src/api/design/bim.ts +0 -121
- package/src/api/design/catalog.ts +0 -203
- package/src/api/design/networks.ts +0 -607
- package/src/api/presentation/drawingContract.ts +0 -70
- package/src/api/presentation/drawings.ts +0 -1179
- package/src/api/presentation/viewPresets.ts +0 -241
- package/test/drawingContract.test.mjs +0 -288
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import * as z from "zod"
|
|
2
|
-
import type { PluginApiReturn } from "../../types"
|
|
3
|
-
|
|
4
|
-
/** Presets are document-local. Their ids are stable across reload; built-ins are immutable. */
|
|
5
|
-
export const PluginViewPreset = z.object({
|
|
6
|
-
id: z.string(),
|
|
7
|
-
name: z.string(),
|
|
8
|
-
builtIn: z.boolean(),
|
|
9
|
-
revision: z.number().int().nonnegative(),
|
|
10
|
-
})
|
|
11
|
-
export type PluginViewPreset = z.infer<typeof PluginViewPreset>
|
|
12
|
-
export const PluginViewPresetIdArgs = z
|
|
13
|
-
.object({ presetId: z.string().min(1) })
|
|
14
|
-
.strict()
|
|
15
|
-
export type PluginViewPresetIdArgs = z.infer<typeof PluginViewPresetIdArgs>
|
|
16
|
-
export const PluginViewPresetCreateArgs = z
|
|
17
|
-
.object({ name: z.string().trim().min(1), fromDrawingId: z.string().min(1) })
|
|
18
|
-
.strict()
|
|
19
|
-
export type PluginViewPresetCreateArgs = z.infer<
|
|
20
|
-
typeof PluginViewPresetCreateArgs
|
|
21
|
-
>
|
|
22
|
-
export const PluginViewPresetRenameArgs = PluginViewPresetIdArgs.extend({
|
|
23
|
-
name: z.string().trim().min(1),
|
|
24
|
-
})
|
|
25
|
-
export type PluginViewPresetRenameArgs = z.infer<
|
|
26
|
-
typeof PluginViewPresetRenameArgs
|
|
27
|
-
>
|
|
28
|
-
export const PluginViewPresetUpdateArgs = PluginViewPresetIdArgs.extend({
|
|
29
|
-
fromDrawingId: z.string().min(1),
|
|
30
|
-
})
|
|
31
|
-
export type PluginViewPresetUpdateArgs = z.infer<
|
|
32
|
-
typeof PluginViewPresetUpdateArgs
|
|
33
|
-
>
|
|
34
|
-
export const PluginViewPresetDeleteArgs = PluginViewPresetIdArgs.extend({
|
|
35
|
-
replacementId: z.string().min(1),
|
|
36
|
-
})
|
|
37
|
-
export type PluginViewPresetDeleteArgs = z.infer<
|
|
38
|
-
typeof PluginViewPresetDeleteArgs
|
|
39
|
-
>
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Reusable styles for kernel-plan drawings, stored in the Present document.
|
|
43
|
-
*
|
|
44
|
-
* A preset holds every style category of a native kernel plan. A drawing is
|
|
45
|
-
* bound to one with {@linkcode PluginPresentationDrawingsApi.applyPreset}; its
|
|
46
|
-
* own style changes are overrides on top, dropped by
|
|
47
|
-
* {@linkcode PluginPresentationDrawingsApi.resetOverrides}. Presets apply only
|
|
48
|
-
* to drawings whose `drawings.getCapabilities(drawingId).presets` is `true`.
|
|
49
|
-
* Local title text is excluded from preset values.
|
|
50
|
-
*
|
|
51
|
-
* Presets are document-local; their ids survive reload. Built-in presets are
|
|
52
|
-
* immutable. Catalog writes (`create`, `copy`, `rename`, `update`, `delete`)
|
|
53
|
-
* are native document metadata and are **not undoable**; binding a drawing or
|
|
54
|
-
* changing its overrides uses Present undo. Every method requires **Present
|
|
55
|
-
* mode to be open** and throws `PRECONDITION_FAILED` ("Present mode is not
|
|
56
|
-
* open") when it is closed; no method switches tabs.
|
|
57
|
-
*
|
|
58
|
-
* Accessed via `snaptrude.presentation.viewPresets`.
|
|
59
|
-
*/
|
|
60
|
-
export abstract class PluginPresentationViewPresetsApi {
|
|
61
|
-
/**
|
|
62
|
-
* List the preset catalog: built-ins first, then custom presets by name.
|
|
63
|
-
*
|
|
64
|
-
* @returns Every {@linkcode PluginViewPreset}.
|
|
65
|
-
* @throws `PRECONDITION_FAILED` if Present mode is not open.
|
|
66
|
-
*
|
|
67
|
-
* @examplePrompt List the available drawing style presets
|
|
68
|
-
* @examplePrompt Which custom presets does this presentation have?
|
|
69
|
-
* @examplePrompt Is there a Planning Issue preset?
|
|
70
|
-
*
|
|
71
|
-
* # Example
|
|
72
|
-
* ```ts
|
|
73
|
-
* const presets = await snaptrude.presentation.viewPresets.list()
|
|
74
|
-
* const custom = presets.filter((preset) => !preset.builtIn)
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
public abstract list(): PluginApiReturn<PluginViewPreset[]>
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Read one preset by id.
|
|
81
|
-
*
|
|
82
|
-
* @param presetId - The preset to read.
|
|
83
|
-
* @returns The {@linkcode PluginViewPreset}, or `null` if no preset has that id.
|
|
84
|
-
* @throws `PRECONDITION_FAILED` if Present mode is not open.
|
|
85
|
-
*
|
|
86
|
-
* @examplePrompt Which preset is this drawing using?
|
|
87
|
-
* @examplePrompt Is this preset a built-in one?
|
|
88
|
-
* @examplePrompt Read the preset bound to the ground floor plan
|
|
89
|
-
*
|
|
90
|
-
* # Example
|
|
91
|
-
* ```ts
|
|
92
|
-
* const drawing = await snaptrude.presentation.drawings.get(drawingId)
|
|
93
|
-
* const preset = drawing?.presetId
|
|
94
|
-
* ? await snaptrude.presentation.viewPresets.get(drawing.presetId)
|
|
95
|
-
* : null
|
|
96
|
-
* ```
|
|
97
|
-
*/
|
|
98
|
-
public abstract get(
|
|
99
|
-
presetId: string,
|
|
100
|
-
): PluginApiReturn<PluginViewPreset | null>
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Save a kernel-plan drawing's effective styles as a new custom preset and
|
|
104
|
-
* bind that drawing to it. Not undoable (catalog write).
|
|
105
|
-
*
|
|
106
|
-
* @param name - A unique preset name (non-empty after trimming).
|
|
107
|
-
* @param options - `fromDrawingId`: the kernel-plan drawing to take styles from.
|
|
108
|
-
* @returns The new {@linkcode PluginViewPreset}.
|
|
109
|
-
* @throws `VALIDATION` for an empty or duplicate name (with a hint);
|
|
110
|
-
* `PRECONDITION_FAILED` if Present mode is not open, the document is
|
|
111
|
-
* read-only, or the drawing is not a kernel vector plan (each with a
|
|
112
|
-
* hint); `HANDLE_INVALID` if `fromDrawingId` is not a drawing.
|
|
113
|
-
*
|
|
114
|
-
* @examplePrompt Save this drawing's styles as a new preset
|
|
115
|
-
* @examplePrompt Make a preset called Planning Issue from this plan
|
|
116
|
-
* @examplePrompt Turn my restyled drawing into a reusable style
|
|
117
|
-
*
|
|
118
|
-
* # Example
|
|
119
|
-
* ```ts
|
|
120
|
-
* const capabilities = await snaptrude.presentation.drawings.getCapabilities(drawing.id)
|
|
121
|
-
* if (capabilities.presets) {
|
|
122
|
-
* const preset = await snaptrude.presentation.viewPresets.create("Planning Issue", {
|
|
123
|
-
* fromDrawingId: drawing.id,
|
|
124
|
-
* })
|
|
125
|
-
* }
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
public abstract create(
|
|
129
|
-
name: string,
|
|
130
|
-
options: { fromDrawingId: string },
|
|
131
|
-
): PluginApiReturn<PluginViewPreset>
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Duplicate a preset under a native-generated unique name. Drawing bindings
|
|
135
|
-
* do not change. Not undoable (catalog write). Built-ins can be copied.
|
|
136
|
-
*
|
|
137
|
-
* @param presetId - The preset to copy.
|
|
138
|
-
* @returns The new custom {@linkcode PluginViewPreset}.
|
|
139
|
-
* @throws `PRECONDITION_FAILED` if Present mode is not open or the document
|
|
140
|
-
* is read-only (each with a hint); `HANDLE_INVALID` if `presetId` does not
|
|
141
|
-
* exist.
|
|
142
|
-
*
|
|
143
|
-
* @examplePrompt Duplicate this preset
|
|
144
|
-
* @examplePrompt Make an editable copy of the built-in preset
|
|
145
|
-
* @examplePrompt Copy the Planning Issue preset so I can change it
|
|
146
|
-
*
|
|
147
|
-
* # Example
|
|
148
|
-
* ```ts
|
|
149
|
-
* const copy = await snaptrude.presentation.viewPresets.copy(preset.id)
|
|
150
|
-
* await snaptrude.presentation.viewPresets.rename(copy.id, "Planning Issue B")
|
|
151
|
-
* ```
|
|
152
|
-
*/
|
|
153
|
-
public abstract copy(presetId: string): PluginApiReturn<PluginViewPreset>
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Rename a custom preset. Names must stay unique; built-ins cannot be
|
|
157
|
-
* renamed. Not undoable (catalog write).
|
|
158
|
-
*
|
|
159
|
-
* @param presetId - The custom preset to rename.
|
|
160
|
-
* @param name - The new unique name (non-empty after trimming).
|
|
161
|
-
* @returns The renamed {@linkcode PluginViewPreset}.
|
|
162
|
-
* @throws `VALIDATION` when the catalog refuses: an empty or duplicate name,
|
|
163
|
-
* a built-in preset, or an unknown `presetId` (with a hint);
|
|
164
|
-
* `PRECONDITION_FAILED` if Present mode is not open or the document is
|
|
165
|
-
* read-only (each with a hint).
|
|
166
|
-
*
|
|
167
|
-
* @examplePrompt Rename this preset to Client Review
|
|
168
|
-
* @examplePrompt Call the copied preset Planning Issue B
|
|
169
|
-
* @examplePrompt Change the name of my custom drawing style
|
|
170
|
-
*
|
|
171
|
-
* # Example
|
|
172
|
-
* ```ts
|
|
173
|
-
* await snaptrude.presentation.viewPresets.rename(preset.id, "Client Review")
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
|
-
public abstract rename(
|
|
177
|
-
presetId: string,
|
|
178
|
-
name: string,
|
|
179
|
-
): PluginApiReturn<PluginViewPreset>
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Replace a custom preset's values with the effective styles of a drawing
|
|
183
|
-
* already bound to it, then refresh every bound drawing. Built-ins cannot
|
|
184
|
-
* be updated. Not undoable (catalog write).
|
|
185
|
-
*
|
|
186
|
-
* @param presetId - The custom preset to update.
|
|
187
|
-
* @param options - `fromDrawingId`: a kernel-plan drawing bound to this preset.
|
|
188
|
-
* @returns The updated {@linkcode PluginViewPreset} (its `revision` grows).
|
|
189
|
-
* @throws `VALIDATION` when the catalog refuses: a built-in preset (with a
|
|
190
|
-
* hint); `PRECONDITION_FAILED` if Present mode is not open, the document
|
|
191
|
-
* is read-only, or the drawing is not a kernel vector plan bound to this
|
|
192
|
-
* preset (each with a hint); `HANDLE_INVALID` if `fromDrawingId` is not a
|
|
193
|
-
* drawing.
|
|
194
|
-
*
|
|
195
|
-
* @examplePrompt Update this preset from the current drawing styles
|
|
196
|
-
* @examplePrompt Push my style changes to every drawing using this preset
|
|
197
|
-
* @examplePrompt Save these overrides into the Planning Issue preset
|
|
198
|
-
*
|
|
199
|
-
* # Example
|
|
200
|
-
* ```ts
|
|
201
|
-
* await snaptrude.presentation.viewPresets.update(drawing.presetId!, {
|
|
202
|
-
* fromDrawingId: drawing.id,
|
|
203
|
-
* })
|
|
204
|
-
* ```
|
|
205
|
-
*/
|
|
206
|
-
public abstract update(
|
|
207
|
-
presetId: string,
|
|
208
|
-
options: { fromDrawingId: string },
|
|
209
|
-
): PluginApiReturn<PluginViewPreset>
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Delete a custom preset and rebind its drawings to another existing
|
|
213
|
-
* preset. Built-ins cannot be deleted. Not undoable (catalog write).
|
|
214
|
-
*
|
|
215
|
-
* @param presetId - The custom preset to delete.
|
|
216
|
-
* @param options - `replacementId`: the existing preset its drawings move to
|
|
217
|
-
* (not the deleted one).
|
|
218
|
-
* @returns Nothing.
|
|
219
|
-
* @throws `VALIDATION` when the catalog refuses: a built-in or unknown
|
|
220
|
-
* `presetId`, or an unknown `replacementId` (with a hint);
|
|
221
|
-
* `PRECONDITION_FAILED` if Present mode is not open or the document is
|
|
222
|
-
* read-only (each with a hint).
|
|
223
|
-
*
|
|
224
|
-
* @examplePrompt Delete this preset
|
|
225
|
-
* @examplePrompt Remove the old style preset and switch its drawings to the default
|
|
226
|
-
* @examplePrompt Get rid of the Planning Issue B preset
|
|
227
|
-
*
|
|
228
|
-
* # Example
|
|
229
|
-
* ```ts
|
|
230
|
-
* const presets = await snaptrude.presentation.viewPresets.list()
|
|
231
|
-
* const replacement = presets.find((each) => each.builtIn)!
|
|
232
|
-
* await snaptrude.presentation.viewPresets.delete(customPreset.id, {
|
|
233
|
-
* replacementId: replacement.id,
|
|
234
|
-
* })
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
public abstract delete(
|
|
238
|
-
presetId: string,
|
|
239
|
-
options: { replacementId: string },
|
|
240
|
-
): PluginApiReturn<void>
|
|
241
|
-
}
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
// The drawing tool contract the agent reads: export file names are base names
|
|
2
|
-
// without an extension, and architectural scales are standard denominators only.
|
|
3
|
-
// Run: node --test test/
|
|
4
|
-
// Imports the BUILT package (dist) — run `pnpm build` first.
|
|
5
|
-
import test from "node:test"
|
|
6
|
-
import assert from "node:assert/strict"
|
|
7
|
-
import { readFileSync } from "node:fs"
|
|
8
|
-
import {
|
|
9
|
-
STANDARD_DRAWING_SCALES,
|
|
10
|
-
METRIC_DRAWING_SCALES,
|
|
11
|
-
IMPERIAL_DRAWING_SCALES,
|
|
12
|
-
IMPERIAL_DETAIL_DRAWING_SCALES,
|
|
13
|
-
IMPERIAL_SITE_DRAWING_SCALES,
|
|
14
|
-
PluginDrawingCreateOptions,
|
|
15
|
-
PluginDrawingCreateArgs,
|
|
16
|
-
PluginDrawingScaleArgs,
|
|
17
|
-
PluginDrawingExportOptions,
|
|
18
|
-
PluginDrawingExportArgs,
|
|
19
|
-
PluginPresentationExportArgs,
|
|
20
|
-
PluginPresentationSheetsPlaceArgs,
|
|
21
|
-
PluginPresentationSheetsPlaceDrawingArgs,
|
|
22
|
-
PluginPresentationSheetsPlaceDrawingResult,
|
|
23
|
-
PluginPresentationSheetsPlaceResult,
|
|
24
|
-
PluginPresentationPlacedViewsSetScaleArgs,
|
|
25
|
-
} from "../dist/index.js"
|
|
26
|
-
import { buildManifest } from "../scripts/generate-manifest.mjs"
|
|
27
|
-
|
|
28
|
-
// The Scale dropdown's values in the frontend (ALL_SCALES_DICTIONARY in
|
|
29
|
-
// src/app/common/utils/scaleMeasurements.ts): metric, imperial detail, imperial site.
|
|
30
|
-
const METRIC = [10, 20, 50, 100, 150, 200, 250, 500, 1000]
|
|
31
|
-
const IMPERIAL_DETAIL = [12, 16, 24, 32, 48, 64, 96, 128, 192, 384, 768, 1536]
|
|
32
|
-
const IMPERIAL_SITE = [120, 240, 360, 480, 600, 1200, 2400, 3600, 4800]
|
|
33
|
-
|
|
34
|
-
const messagesOf = (result) => result.error.issues.map((issue) => issue.message)
|
|
35
|
-
|
|
36
|
-
// --- standard scales ---------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
test("STANDARD_DRAWING_SCALES is the frontend's metric and imperial list, ascending", () => {
|
|
39
|
-
const expected = [...METRIC, ...IMPERIAL_DETAIL, ...IMPERIAL_SITE].sort((a, b) => a - b)
|
|
40
|
-
assert.deepEqual([...STANDARD_DRAWING_SCALES], expected)
|
|
41
|
-
assert.deepEqual([...METRIC_DRAWING_SCALES], METRIC)
|
|
42
|
-
assert.deepEqual([...IMPERIAL_DRAWING_SCALES], [...IMPERIAL_DETAIL, ...IMPERIAL_SITE].sort((a, b) => a - b))
|
|
43
|
-
assert.deepEqual([...IMPERIAL_DETAIL_DRAWING_SCALES], IMPERIAL_DETAIL)
|
|
44
|
-
assert.deepEqual([...IMPERIAL_SITE_DRAWING_SCALES], IMPERIAL_SITE)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const scaleSchemas = [
|
|
48
|
-
["drawings.create options", (scale) => PluginDrawingCreateOptions.safeParse({ scale })],
|
|
49
|
-
["drawings.create args", (scale) => PluginDrawingCreateArgs.safeParse({ viewId: "v1", scale })],
|
|
50
|
-
["drawings.setScale", (scale) => PluginDrawingScaleArgs.safeParse({ drawingId: "d1", scale })],
|
|
51
|
-
["sheets.place", (scale) => PluginPresentationSheetsPlaceArgs.safeParse({ sheetId: "s1", viewId: "v1", scale })],
|
|
52
|
-
["placedViews.setScale", (scale) => PluginPresentationPlacedViewsSetScaleArgs.safeParse({ shapeId: "p1", scale })],
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
for (const [name, parse] of scaleSchemas) {
|
|
56
|
-
test(`${name} accepts every standard scale`, () => {
|
|
57
|
-
for (const scale of STANDARD_DRAWING_SCALES) {
|
|
58
|
-
assert.equal(parse(scale).success, true, `${name} rejected ${scale}`)
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test(`${name} rejects a non-standard scale and lists the allowed ones`, () => {
|
|
63
|
-
for (const scale of [137, 75, 1, 99.5]) {
|
|
64
|
-
const result = parse(scale)
|
|
65
|
-
assert.equal(result.success, false, `${name} accepted ${scale}`)
|
|
66
|
-
const message = messagesOf(result).join("\n")
|
|
67
|
-
assert.match(message, /standard/i)
|
|
68
|
-
assert.ok(message.includes(`Metric: ${METRIC.join(", ")}`), message)
|
|
69
|
-
assert.ok(message.includes(`imperial detail: ${IMPERIAL_DETAIL.join(", ")}`), message)
|
|
70
|
-
assert.ok(message.includes(`imperial site: ${IMPERIAL_SITE.join(", ")}`), message)
|
|
71
|
-
assert.match(message, /largest/i)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
test("create options still allow omitting the scale", () => {
|
|
77
|
-
assert.equal(PluginDrawingCreateOptions.safeParse({}).success, true)
|
|
78
|
-
assert.equal(PluginPresentationSheetsPlaceArgs.safeParse({ sheetId: "s", viewId: "v" }).success, true)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
// --- placing a drawing on a sheet -------------------------------------------
|
|
82
|
-
|
|
83
|
-
test("sheets.placeDrawing takes an optional boolean fit", () => {
|
|
84
|
-
const base = { sheetId: "s", drawingId: "d" }
|
|
85
|
-
assert.equal(PluginPresentationSheetsPlaceDrawingArgs.safeParse(base).success, true)
|
|
86
|
-
assert.equal(PluginPresentationSheetsPlaceDrawingArgs.safeParse({ ...base, fit: true }).success, true)
|
|
87
|
-
assert.equal(PluginPresentationSheetsPlaceDrawingArgs.safeParse({ ...base, fit: false }).success, true)
|
|
88
|
-
assert.equal(PluginPresentationSheetsPlaceDrawingArgs.safeParse({ ...base, fit: "yes" }).success, false)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test("sheets.placeDrawing reports the scale it used and whether it rescaled", () => {
|
|
92
|
-
const ok = (value) => PluginPresentationSheetsPlaceDrawingResult.safeParse(value).success
|
|
93
|
-
assert.equal(ok({ shapeId: "shape:a", scale: 192, rescaled: true }), true)
|
|
94
|
-
assert.equal(ok({ shapeId: "shape:a", scale: null, rescaled: false }), true)
|
|
95
|
-
assert.equal(ok({ shapeId: "shape:a" }), false)
|
|
96
|
-
assert.equal(ok({ shapeId: "shape:a", scale: 100 }), false)
|
|
97
|
-
assert.equal(ok({ shapeId: "shape:a", scale: 100, rescaled: false, overlaps: true }), true)
|
|
98
|
-
assert.equal(ok({ shapeId: "shape:a", scale: 100, rescaled: false, overlaps: "yes" }), false)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
test("sheets.place may report the scale, whether it rescaled and whether it overlaps", () => {
|
|
102
|
-
const ok = (value) => PluginPresentationSheetsPlaceResult.safeParse(value).success
|
|
103
|
-
assert.equal(ok({ shapeId: "shape:a" }), true)
|
|
104
|
-
assert.equal(ok({ shapeId: "shape:a", scale: 192, rescaled: true, overlaps: false }), true)
|
|
105
|
-
assert.equal(ok({ shapeId: "shape:a", scale: null, rescaled: false }), true)
|
|
106
|
-
assert.equal(ok({ shapeId: "shape:a", overlaps: "no" }), false)
|
|
107
|
-
assert.equal(ok({ shapeId: "shape:a", rescaled: 1 }), false)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test("sheets.place may report that a drawing at an explicit scale overflows the sheet; placeDrawing never does", () => {
|
|
111
|
-
const place = (value) => PluginPresentationSheetsPlaceResult.safeParse(value).success
|
|
112
|
-
assert.equal(place({ shapeId: "shape:a", scale: 100, rescaled: false, overflows: true }), true)
|
|
113
|
-
assert.equal(place({ shapeId: "shape:a", scale: 100, rescaled: false, overflows: "yes" }), false)
|
|
114
|
-
const placeDrawing = PluginPresentationSheetsPlaceDrawingResult.safeParse({
|
|
115
|
-
shapeId: "shape:a",
|
|
116
|
-
scale: 100,
|
|
117
|
-
rescaled: false,
|
|
118
|
-
overflows: true,
|
|
119
|
-
})
|
|
120
|
-
assert.equal(placeDrawing.success, true)
|
|
121
|
-
assert.equal("overflows" in placeDrawing.data, false)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
// --- file names --------------------------------------------------------------
|
|
125
|
-
|
|
126
|
-
const fileNameSchemas = [
|
|
127
|
-
["drawings.export options", (fileName) => PluginDrawingExportOptions.safeParse({ fileName })],
|
|
128
|
-
["drawings.export args", (fileName) => PluginDrawingExportArgs.safeParse({ drawingId: "d1", format: "pdf", fileName })],
|
|
129
|
-
["presentation.export", (fileName) => PluginPresentationExportArgs.safeParse({ format: "pdf", fileName })],
|
|
130
|
-
]
|
|
131
|
-
|
|
132
|
-
for (const [name, parse] of fileNameSchemas) {
|
|
133
|
-
test(`${name} rejects a file name with an extension`, () => {
|
|
134
|
-
for (const fileName of ["Floor Pattern View.pdf", "plan.PDF", "a.svg", "a.png", "a.jpg", "a.JPEG"]) {
|
|
135
|
-
const result = parse(fileName)
|
|
136
|
-
assert.equal(result.success, false, `${name} accepted ${fileName}`)
|
|
137
|
-
assert.match(messagesOf(result).join("\n"), /without an extension/i)
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
test(`${name} rejects a file name ending in a dot or whitespace`, () => {
|
|
142
|
-
// The host appends the extension, so "a.pdf." would become "a.pdf..pdf".
|
|
143
|
-
for (const fileName of ["a.pdf.", "plan.", "plan..", "plan ", "plan\t", "a.pdf .", "a.pdf. "]) {
|
|
144
|
-
const result = parse(fileName)
|
|
145
|
-
assert.equal(result.success, false, `${name} accepted ${JSON.stringify(fileName)}`)
|
|
146
|
-
assert.match(messagesOf(result).join("\n"), /without an extension/i)
|
|
147
|
-
}
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
test(`${name} accepts a base name`, () => {
|
|
151
|
-
for (const fileName of ["Floor Pattern View", "Level 1.2 plan", "pdf plan", "notes.v2"]) {
|
|
152
|
-
assert.equal(parse(fileName).success, true, `${name} rejected ${fileName}`)
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// --- the contract the agent reads (api-manifest summaries) -------------------
|
|
158
|
-
|
|
159
|
-
// Overloaded methods produce one leaf per signature; the documented one comes first.
|
|
160
|
-
const manifest = new Map()
|
|
161
|
-
for (const leaf of buildManifest()) if (!manifest.has(leaf.path)) manifest.set(leaf.path, leaf)
|
|
162
|
-
const summaryOf = (path) => {
|
|
163
|
-
const leaf = manifest.get(path)
|
|
164
|
-
assert.ok(leaf, `no manifest leaf ${path}`)
|
|
165
|
-
return leaf.summary
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
test("export summaries tell the agent to pass fileName without an extension", () => {
|
|
169
|
-
for (const path of ["presentation.drawings.export", "presentation.export"]) {
|
|
170
|
-
assert.match(summaryOf(path), /fileName[^.]*without (an|its) extension/i, path)
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
test("scale summaries list the standard scales and say how to choose one", () => {
|
|
175
|
-
for (const path of [
|
|
176
|
-
"presentation.drawings.create",
|
|
177
|
-
"presentation.drawings.setScale",
|
|
178
|
-
"presentation.sheets.place",
|
|
179
|
-
"presentation.placedViews.setScale",
|
|
180
|
-
]) {
|
|
181
|
-
const summary = summaryOf(path)
|
|
182
|
-
assert.ok(summary.includes(`Metric: ${METRIC.join(", ")}.`), `${path}: ${summary}`)
|
|
183
|
-
assert.ok(summary.includes(`Imperial detail: ${IMPERIAL_DETAIL.join(", ")}`), `${path}: ${summary}`)
|
|
184
|
-
assert.ok(summary.includes(`Imperial site: ${IMPERIAL_SITE.join(", ")}`), `${path}: ${summary}`)
|
|
185
|
-
assert.match(summary, /Pick the LARGEST standard scale \(smallest denominator\)/, path)
|
|
186
|
-
assert.match(summary, /fits its frame or sheet/i, path)
|
|
187
|
-
assert.match(summary, /usually 1:50 to 1:200/, path)
|
|
188
|
-
assert.match(summary, /Never use a site-sized scale/, path)
|
|
189
|
-
assert.match(summary, /project's unit system/, path)
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
test("drawings.create tells the agent to omit position and follow the label layout's suggested scale", () => {
|
|
194
|
-
const summary = summaryOf("presentation.drawings.create")
|
|
195
|
-
assert.match(summary, /Omit `position` unless the user asked for a specific place/, summary)
|
|
196
|
-
assert.match(summary, /next to its source view/, summary)
|
|
197
|
-
assert.match(summary, /labels\.getLayout\(drawingId\)/, summary)
|
|
198
|
-
assert.match(summary, /suggestedScale/, summary)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
test("drawing writes send the agent to presentation.camera instead of promising to frame the camera", () => {
|
|
202
|
-
for (const path of [
|
|
203
|
-
"presentation.drawings.create",
|
|
204
|
-
"presentation.drawings.setScale",
|
|
205
|
-
"presentation.drawings.setCrop",
|
|
206
|
-
]) {
|
|
207
|
-
const summary = summaryOf(path)
|
|
208
|
-
assert.doesNotMatch(summary, /frames the camera/i, path)
|
|
209
|
-
assert.match(summary, /Changes the drawing, not the Present camera/, path)
|
|
210
|
-
assert.match(summary, /presentation\.camera\.zoomTo\(drawing\.id\)/, path)
|
|
211
|
-
assert.match(summary, /presentation\.camera\.capture\(\)/, path)
|
|
212
|
-
}
|
|
213
|
-
for (const path of ["presentation.drawings.setScale", "presentation.drawings.setCrop"]) {
|
|
214
|
-
assert.match(summaryOf(path), /moves\s+to the nearest free spot/, path)
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
test("drawings.create says it never puts a drawing on a sheet and names the sheet calls", () => {
|
|
219
|
-
const summary = summaryOf("presentation.drawings.create")
|
|
220
|
-
assert.match(summary, /beside its source view/, summary)
|
|
221
|
-
assert.match(summary, /never on a sheet/, summary)
|
|
222
|
-
assert.match(summary, /sheets\.place\(sheetId, viewId\)/, summary)
|
|
223
|
-
assert.match(summary, /sheets\.placeDrawing\(sheetId, drawingId\)/, summary)
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
test("sheets.placeDrawing documents fitting, fit: false and one sheet per drawing", () => {
|
|
227
|
-
const summary = summaryOf("presentation.sheets.placeDrawing")
|
|
228
|
-
assert.match(summary, /scaled down to fit/, summary)
|
|
229
|
-
assert.match(summary, /never scaled up/, summary)
|
|
230
|
-
assert.match(summary, /`fit: false`/, summary)
|
|
231
|
-
assert.match(summary, /naming the\s+drawing, the sheet and the scale/, summary)
|
|
232
|
-
assert.match(summary, /without\s+`fit: false`\s+to fit it, or\s+use a larger sheet/, summary)
|
|
233
|
-
assert.doesNotMatch(summary, /use\s+`?sheets\.place`?\s+to\s+fit/, summary)
|
|
234
|
-
assert.match(summary, /reports the architectural scale/, summary)
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
// Without a position the host puts the drawing at the sheet centre or the nearest free spot,
|
|
238
|
-
// and only centres it over other drawings (reporting overlaps: true) when there is none.
|
|
239
|
-
const NO_STACK = /lands\s+on\s+top/
|
|
240
|
-
const FREE_SPOT = /nearest\s+free\s+spot/
|
|
241
|
-
const CENTRED_WHEN_FULL = /no\s+free\s+spot[^.]*centred[^.]*`overlaps:\s*true`/
|
|
242
|
-
const EXPLICIT_POSITION = /`position`[^.]*placed\s+as\s+given[^.]*`overlaps:\s*true`[^.]*not\s+moved/
|
|
243
|
-
const DRAWINGS_ONLY = /only\s+other\s+drawings\s+count[^.]*text,\s+images\s+and\s+other\s+shapes/
|
|
244
|
-
|
|
245
|
-
const assertPlacementLayout = (summary) => {
|
|
246
|
-
assert.doesNotMatch(summary, NO_STACK, summary)
|
|
247
|
-
assert.match(summary, FREE_SPOT, summary)
|
|
248
|
-
assert.match(summary, CENTRED_WHEN_FULL, summary)
|
|
249
|
-
assert.match(summary, EXPLICIT_POSITION, summary)
|
|
250
|
-
assert.match(summary, DRAWINGS_ONLY, summary)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
test("sheets.placeDrawing says where a drawing goes without a position and when it overlaps", () => {
|
|
254
|
-
assertPlacementLayout(summaryOf("presentation.sheets.placeDrawing"))
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
test("sheets.place says it is the call that puts a view on a sheet and fits it to that sheet", () => {
|
|
258
|
-
const summary = summaryOf("presentation.sheets.place")
|
|
259
|
-
assert.match(summary, /This is the call that puts a view on a\s+sheet/, summary)
|
|
260
|
-
assert.match(summary, /largest\s+standard scale at which it fits inside the target sheet's margin/, summary)
|
|
261
|
-
assert.match(summary, /never at a larger scale than the normal view drop/, summary)
|
|
262
|
-
assert.match(summary, /3D view[^.]*shrunk\s+evenly/, summary)
|
|
263
|
-
assertPlacementLayout(summary)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
// The full JSDoc of sheets.place, for the @returns and @throws tags the summary leaves out.
|
|
267
|
-
const placeJsDoc = () => {
|
|
268
|
-
const source = readFileSync(new URL("../src/api/presentation/sheets.ts", import.meta.url), "utf8")
|
|
269
|
-
const end = source.indexOf("public abstract place(")
|
|
270
|
-
const start = source.lastIndexOf("/**", end)
|
|
271
|
-
return source
|
|
272
|
-
.slice(start, end)
|
|
273
|
-
.replace(/\{@linkcode (\w+)\}/g, "$1")
|
|
274
|
-
.replace(/\s*\*\s*/g, " ")
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
test("sheets.place documents the scale and rescaled it returns", () => {
|
|
278
|
-
const doc = placeJsDoc()
|
|
279
|
-
assert.match(doc, /@returns[^@]*`scale` used \(`null` for a 3D view\)/, doc)
|
|
280
|
-
assert.match(doc, /@returns[^@]*`rescaled`/, doc)
|
|
281
|
-
assert.doesNotMatch(doc, /a host that reports them/, doc)
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
test("sheets.place documents PRECONDITION_FAILED for a view that does not fit", () => {
|
|
285
|
-
const doc = placeJsDoc()
|
|
286
|
-
assert.match(doc, /@throws[^@]*`PRECONDITION_FAILED`[^@]*smallest standard scale/, doc)
|
|
287
|
-
assert.match(doc, /@throws[^@]*`PRECONDITION_FAILED`[^@]*title block alone/, doc)
|
|
288
|
-
})
|