@ifc-lite/bcf 3.0.0 → 4.0.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 +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/reader-viewpoint-content.d.ts +6 -0
- package/dist/reader-viewpoint-content.d.ts.map +1 -0
- package/dist/reader-viewpoint-content.js +250 -0
- package/dist/reader-viewpoint-content.js.map +1 -0
- package/dist/reader.d.ts.map +1 -1
- package/dist/reader.js +17 -238
- package/dist/reader.js.map +1 -1
- package/dist/viewpoint.d.ts +1 -1
- package/dist/viewpoint.d.ts.map +1 -1
- package/dist/viewpoint.js +26 -2
- package/dist/viewpoint.js.map +1 -1
- package/dist/writer-markup-elements.d.ts +64 -0
- package/dist/writer-markup-elements.d.ts.map +1 -0
- package/dist/writer-markup-elements.js +179 -0
- package/dist/writer-markup-elements.js.map +1 -0
- package/dist/writer-viewpoint-filename.d.ts +32 -0
- package/dist/writer-viewpoint-filename.d.ts.map +1 -0
- package/dist/writer-viewpoint-filename.js +33 -0
- package/dist/writer-viewpoint-filename.js.map +1 -0
- package/dist/writer.d.ts.map +1 -1
- package/dist/writer.js +74 -213
- package/dist/writer.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ifc-lite/bcf
|
|
2
2
|
|
|
3
|
-
BCF (BIM Collaboration Format) support for IFClite. Reads and writes BCF 2.1 and 3.0 files — the
|
|
3
|
+
BCF (BIM Collaboration Format) support for IFClite. Reads and writes BCF 2.1 and 3.0 files — the topic-tracking format every BIM tool speaks (Revit, Archicad, Solibri, BIMcollab, etc.).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @ifc-lite/bcf - BIM Collaboration Format support
|
|
3
3
|
*
|
|
4
4
|
* This package provides BCF (BIM Collaboration Format) support for IFC-Lite.
|
|
5
|
-
* It implements BCF 2.1 and 3.0 specifications for
|
|
5
|
+
* It implements BCF 2.1 and 3.0 specifications for topic tracking in BIM projects.
|
|
6
6
|
*
|
|
7
7
|
* @see https://github.com/buildingSMART/BCF-XML
|
|
8
8
|
* @see https://www.buildingsmart.org/standards/bsi-standards/bim-collaboration-format/
|
|
@@ -28,7 +28,7 @@ export declare function createBCFProject(options?: {
|
|
|
28
28
|
version?: '2.1' | '3.0';
|
|
29
29
|
}): BCFProject;
|
|
30
30
|
/**
|
|
31
|
-
* Create a new BCF topic
|
|
31
|
+
* Create a new BCF topic
|
|
32
32
|
*/
|
|
33
33
|
export declare function createBCFTopic(options: {
|
|
34
34
|
title: string;
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader-viewpoint-content.d.ts","sourceRoot":"","sources":["../src/reader-viewpoint-content.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,YAAY,EAQb,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,GAAG,KAAK,GAAG,YAAY,GAAG,IAAI,CAgCpG"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Parsing of a single BCFV viewpoint file's XML content: the two camera
|
|
6
|
+
* shapes, the shared point/direction primitive, and the geometry overlays
|
|
7
|
+
* (lines, clipping planes, bitmaps). Split out of reader.ts, which still owns
|
|
8
|
+
* the zip-level orchestration (locating .bcfv files, resolving snapshots) that
|
|
9
|
+
* calls into {@link parseViewpointContent}.
|
|
10
|
+
*/
|
|
11
|
+
import { extractElement } from './xml-text.js';
|
|
12
|
+
import { parseFiniteFloat } from './numeric.js';
|
|
13
|
+
import { parseComponents } from './reader-components.js';
|
|
14
|
+
/**
|
|
15
|
+
* Parse viewpoint XML content
|
|
16
|
+
*/
|
|
17
|
+
export function parseViewpointContent(content, versionId) {
|
|
18
|
+
// Extract viewpoint GUID from root element (Guid can be anywhere in the tag)
|
|
19
|
+
const guidMatch = content.match(/<VisualizationInfo[^>]+Guid="([^"]+)"/);
|
|
20
|
+
const guid = guidMatch?.[1] || crypto.randomUUID?.() || `vp-${Date.now()}`;
|
|
21
|
+
// Parse perspective camera
|
|
22
|
+
const perspectiveCamera = parsePerspectiveCamera(content);
|
|
23
|
+
// Parse orthogonal camera
|
|
24
|
+
const orthogonalCamera = parseOrthogonalCamera(content);
|
|
25
|
+
// Parse components
|
|
26
|
+
const components = parseComponents(content, versionId);
|
|
27
|
+
// Parse lines
|
|
28
|
+
const lines = parseLines(content);
|
|
29
|
+
// Parse clipping planes
|
|
30
|
+
const clippingPlanes = parseClippingPlanes(content);
|
|
31
|
+
// Parse bitmaps
|
|
32
|
+
const bitmaps = parseBitmaps(content);
|
|
33
|
+
return {
|
|
34
|
+
guid,
|
|
35
|
+
perspectiveCamera,
|
|
36
|
+
orthogonalCamera,
|
|
37
|
+
components,
|
|
38
|
+
lines: lines.length > 0 ? lines : undefined,
|
|
39
|
+
clippingPlanes: clippingPlanes.length > 0 ? clippingPlanes : undefined,
|
|
40
|
+
bitmaps: bitmaps.length > 0 ? bitmaps : undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse perspective camera from viewpoint content
|
|
45
|
+
*/
|
|
46
|
+
function parsePerspectiveCamera(content) {
|
|
47
|
+
const match = content.match(/<PerspectiveCamera>([\s\S]*?)<\/PerspectiveCamera>/);
|
|
48
|
+
if (!match)
|
|
49
|
+
return undefined;
|
|
50
|
+
const cameraContent = match[1];
|
|
51
|
+
const viewPoint = parsePoint(cameraContent, 'CameraViewPoint');
|
|
52
|
+
const direction = parseDirection(cameraContent, 'CameraDirection');
|
|
53
|
+
const upVector = parseDirection(cameraContent, 'CameraUpVector');
|
|
54
|
+
const fieldOfView = extractElement(cameraContent, 'FieldOfView');
|
|
55
|
+
if (!viewPoint || !direction || !upVector || !fieldOfView) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
// Same treatment as the coordinates: an unusable scalar is a missing one.
|
|
59
|
+
// `fieldOfView` is converted to radians and handed to the viewer camera,
|
|
60
|
+
// and the parser already drops the whole camera when the element is absent.
|
|
61
|
+
const fov = parseFiniteFloat(fieldOfView);
|
|
62
|
+
if (fov === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
return {
|
|
65
|
+
cameraViewPoint: viewPoint,
|
|
66
|
+
cameraDirection: direction,
|
|
67
|
+
cameraUpVector: upVector,
|
|
68
|
+
fieldOfView: fov,
|
|
69
|
+
...parseAspectRatio(cameraContent),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse the optional `<AspectRatio>` of either camera type.
|
|
74
|
+
*
|
|
75
|
+
* BCF 3.0's visinfo.xsd makes `AspectRatio` (a `PositiveDouble`) a REQUIRED
|
|
76
|
+
* child of both camera types; BCF 2.1 has no such element. It is read here for
|
|
77
|
+
* both, since the element's presence — not the archive's declared version — is
|
|
78
|
+
* what says whether there is a value to keep.
|
|
79
|
+
*
|
|
80
|
+
* Reading it matters beyond fidelity: the writer refuses to emit a 3.0 camera
|
|
81
|
+
* without one, so a 3.0 archive from another tool could otherwise be read and
|
|
82
|
+
* then not written back. Returned as a spread-able partial so an absent or
|
|
83
|
+
* unusable value leaves the property off entirely rather than setting it to
|
|
84
|
+
* `undefined`, and a non-positive value is dropped rather than carried into
|
|
85
|
+
* output the schema would reject.
|
|
86
|
+
*/
|
|
87
|
+
function parseAspectRatio(cameraContent) {
|
|
88
|
+
const raw = extractElement(cameraContent, 'AspectRatio');
|
|
89
|
+
if (!raw)
|
|
90
|
+
return {};
|
|
91
|
+
const value = parseFiniteFloat(raw);
|
|
92
|
+
if (value === undefined || !(value > 0))
|
|
93
|
+
return {};
|
|
94
|
+
return { aspectRatio: value };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse orthogonal camera from viewpoint content
|
|
98
|
+
*/
|
|
99
|
+
function parseOrthogonalCamera(content) {
|
|
100
|
+
const match = content.match(/<OrthogonalCamera>([\s\S]*?)<\/OrthogonalCamera>/);
|
|
101
|
+
if (!match)
|
|
102
|
+
return undefined;
|
|
103
|
+
const cameraContent = match[1];
|
|
104
|
+
const viewPoint = parsePoint(cameraContent, 'CameraViewPoint');
|
|
105
|
+
const direction = parseDirection(cameraContent, 'CameraDirection');
|
|
106
|
+
const upVector = parseDirection(cameraContent, 'CameraUpVector');
|
|
107
|
+
const viewToWorldScale = extractElement(cameraContent, 'ViewToWorldScale');
|
|
108
|
+
if (!viewPoint || !direction || !upVector || !viewToWorldScale) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
// `viewToWorldScale` becomes the orthographic half-height, which is the
|
|
112
|
+
// value `getOrthoSize()` hands back into a saved viewpoint — so a
|
|
113
|
+
// non-finite one persists past the session if it is allowed in (#2461).
|
|
114
|
+
const scale = parseFiniteFloat(viewToWorldScale);
|
|
115
|
+
if (scale === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
return {
|
|
118
|
+
cameraViewPoint: viewPoint,
|
|
119
|
+
cameraDirection: direction,
|
|
120
|
+
cameraUpVector: upVector,
|
|
121
|
+
viewToWorldScale: scale,
|
|
122
|
+
...parseAspectRatio(cameraContent),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse a 3D point from XML
|
|
127
|
+
*/
|
|
128
|
+
function parsePoint(content, elementName) {
|
|
129
|
+
const match = content.match(new RegExp(`<${elementName}>([\\s\\S]*?)<\\/${elementName}>`));
|
|
130
|
+
if (!match)
|
|
131
|
+
return undefined;
|
|
132
|
+
const x = extractElement(match[1], 'X');
|
|
133
|
+
const y = extractElement(match[1], 'Y');
|
|
134
|
+
const z = extractElement(match[1], 'Z');
|
|
135
|
+
if (x === undefined || y === undefined || z === undefined) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
// A coordinate that is not a real number is treated as a missing one.
|
|
139
|
+
// `parseFloat` has no out-of-band failure value — `"NaN"` parses to `NaN`
|
|
140
|
+
// and the well-formed literal `"1e999"` parses to `Infinity` — and from here
|
|
141
|
+
// the value reaches `Camera.setPosition`/`setTarget`, which store a pose
|
|
142
|
+
// verbatim by design. Once stored, a single non-finite coordinate spreads
|
|
143
|
+
// across the whole pose on the next gesture. Rejecting at the file boundary
|
|
144
|
+
// means it never gets there, and it costs no new branch: every caller
|
|
145
|
+
// already drops the thing it was parsing when a coordinate is missing
|
|
146
|
+
// (#2466).
|
|
147
|
+
const px = parseFiniteFloat(x);
|
|
148
|
+
const py = parseFiniteFloat(y);
|
|
149
|
+
const pz = parseFiniteFloat(z);
|
|
150
|
+
if (px === undefined || py === undefined || pz === undefined) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return { x: px, y: py, z: pz };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse a 3D direction from XML
|
|
157
|
+
*/
|
|
158
|
+
function parseDirection(content, elementName) {
|
|
159
|
+
return parsePoint(content, elementName);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Parse lines
|
|
163
|
+
*/
|
|
164
|
+
function parseLines(content) {
|
|
165
|
+
const lines = [];
|
|
166
|
+
const linesMatch = content.match(/<Lines>([\s\S]*?)<\/Lines>/);
|
|
167
|
+
if (!linesMatch)
|
|
168
|
+
return lines;
|
|
169
|
+
const lineMatches = linesMatch[1].matchAll(/<Line>([\s\S]*?)<\/Line>/g);
|
|
170
|
+
for (const match of lineMatches) {
|
|
171
|
+
const startPoint = parsePoint(match[1], 'StartPoint');
|
|
172
|
+
const endPoint = parsePoint(match[1], 'EndPoint');
|
|
173
|
+
if (startPoint && endPoint) {
|
|
174
|
+
lines.push({ startPoint, endPoint });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return lines;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Parse clipping planes
|
|
181
|
+
*/
|
|
182
|
+
function parseClippingPlanes(content) {
|
|
183
|
+
const planes = [];
|
|
184
|
+
const planesMatch = content.match(/<ClippingPlanes>([\s\S]*?)<\/ClippingPlanes>/);
|
|
185
|
+
if (!planesMatch)
|
|
186
|
+
return planes;
|
|
187
|
+
const planeMatches = planesMatch[1].matchAll(/<ClippingPlane>([\s\S]*?)<\/ClippingPlane>/g);
|
|
188
|
+
for (const match of planeMatches) {
|
|
189
|
+
const location = parsePoint(match[1], 'Location');
|
|
190
|
+
const direction = parseDirection(match[1], 'Direction');
|
|
191
|
+
if (location && direction) {
|
|
192
|
+
planes.push({ location, direction });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return planes;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Parse bitmaps
|
|
199
|
+
*
|
|
200
|
+
* The two BCF versions diverge in shape (see writer.ts's writeBitmap/
|
|
201
|
+
* writeViewpointFiles for the write side of this):
|
|
202
|
+
* - BCF 3.0: entries sit inside a `<Bitmaps>` wrapper, and the per-entry
|
|
203
|
+
* format element is named `<Format>`. No tag inside an entry shares the
|
|
204
|
+
* entry's own name, so a plain non-greedy `<Bitmap>...</Bitmap>` match
|
|
205
|
+
* is unambiguous.
|
|
206
|
+
* - BCF 2.1: entries sit DIRECTLY under `<VisualizationInfo>` (no wrapper),
|
|
207
|
+
* and the format element is confusingly also named `<Bitmap>`, nested one
|
|
208
|
+
* level inside the entry (`<Bitmap><Bitmap>PNG</Bitmap><Reference>...`).
|
|
209
|
+
* A naive non-greedy `<Bitmap>...</Bitmap>` match on that shape terminates
|
|
210
|
+
* at the FIRST `</Bitmap>` it sees -- the inner format tag's closing tag,
|
|
211
|
+
* not the entry's -- and silently drops the rest of the entry. It must be
|
|
212
|
+
* matched with an explicit two-level pattern instead.
|
|
213
|
+
*/
|
|
214
|
+
function parseBitmaps(content) {
|
|
215
|
+
const bitmaps = [];
|
|
216
|
+
const bitmapsMatch = content.match(/<Bitmaps>([\s\S]*?)<\/Bitmaps>/);
|
|
217
|
+
const pushBitmap = (format, body) => {
|
|
218
|
+
const reference = extractElement(body, 'Reference');
|
|
219
|
+
const location = parsePoint(body, 'Location');
|
|
220
|
+
const normal = parseDirection(body, 'Normal');
|
|
221
|
+
const up = parseDirection(body, 'Up');
|
|
222
|
+
const height = extractElement(body, 'Height');
|
|
223
|
+
const parsedHeight = height === undefined ? undefined : parseFiniteFloat(height);
|
|
224
|
+
if (format && reference && location && normal && up && parsedHeight !== undefined) {
|
|
225
|
+
bitmaps.push({
|
|
226
|
+
format: format.toUpperCase() === 'JPG' ? 'JPG' : 'PNG',
|
|
227
|
+
reference,
|
|
228
|
+
location,
|
|
229
|
+
normal,
|
|
230
|
+
up,
|
|
231
|
+
height: parsedHeight,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
if (bitmapsMatch) {
|
|
236
|
+
// BCF 3.0 shape: <Bitmaps><Bitmap><Format>...</Format>...</Bitmap>...</Bitmaps>
|
|
237
|
+
for (const match of bitmapsMatch[1].matchAll(/<Bitmap>([\s\S]*?)<\/Bitmap>/g)) {
|
|
238
|
+
pushBitmap(extractElement(match[1], 'Format'), match[1]);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// BCF 2.1 shape: <Bitmap><Bitmap>PNG</Bitmap>...</Bitmap>, unwrapped,
|
|
243
|
+
// directly under VisualizationInfo.
|
|
244
|
+
for (const match of content.matchAll(/<Bitmap>\s*<Bitmap>([\s\S]*?)<\/Bitmap>([\s\S]*?)<\/Bitmap>/g)) {
|
|
245
|
+
pushBitmap(match[1], match[2]);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return bitmaps;
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=reader-viewpoint-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader-viewpoint-content.js","sourceRoot":"","sources":["../src/reader-viewpoint-content.ts"],"names":[],"mappings":"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"}
|
package/dist/reader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,UAAU,EASX,MAAM,YAAY,CAAC;AAyIpB;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,WAAW,GAAG,UAAU,EAC5C,MAAM,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GACpF,OAAO,CAAC,UAAU,CAAC,CA4CrB"}
|
package/dist/reader.js
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* Parses .bcfzip files into BCFProject structure
|
|
8
8
|
*/
|
|
9
9
|
import JSZip from 'jszip';
|
|
10
|
-
import { parseComponents } from './reader-components.js';
|
|
11
10
|
import { extractElement, unescapeXml, parseLabels, parseXsBoolean } from './xml-text.js';
|
|
12
|
-
import {
|
|
11
|
+
import { parseViewpointContent } from './reader-viewpoint-content.js';
|
|
13
12
|
/**
|
|
14
13
|
* Resource caps guarding against a malicious (zip-bomb) .bcfzip: a tiny
|
|
15
14
|
* compressed archive that expands to gigabytes, or one with a pathological
|
|
@@ -207,6 +206,18 @@ async function readTopics(zip, budget, versionId) {
|
|
|
207
206
|
try {
|
|
208
207
|
const topic = await readTopic(zip, topicGuid, budget, versionId);
|
|
209
208
|
if (topic) {
|
|
209
|
+
// A second topic folder whose internal Topic/@Guid collides with one
|
|
210
|
+
// already parsed must not silently overwrite it in the map -- that
|
|
211
|
+
// would drop a whole topic with no signal the caller could ever act
|
|
212
|
+
// on (#3960). Keep the first occurrence (folder set iteration order
|
|
213
|
+
// is insertion order, so this is deterministic) and warn, the same
|
|
214
|
+
// way every other "skip this piece, keep going" decision in this
|
|
215
|
+
// function is already reported.
|
|
216
|
+
if (topics.has(topic.guid)) {
|
|
217
|
+
console.warn(`Duplicate topic Guid ${topic.guid}: folder "${topicGuid}" collides with an ` +
|
|
218
|
+
`already-read topic folder and is being dropped. Keeping the first one read.`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
210
221
|
topics.set(topic.guid, topic);
|
|
211
222
|
}
|
|
212
223
|
}
|
|
@@ -253,6 +264,7 @@ async function readTopic(zip, topicFolder, budget, versionId) {
|
|
|
253
264
|
const description = extractElement(topicContent, 'Description');
|
|
254
265
|
const priority = extractElement(topicContent, 'Priority');
|
|
255
266
|
const index = extractElement(topicContent, 'Index');
|
|
267
|
+
const parsedIndex = index ? Number.parseInt(index, 10) : undefined;
|
|
256
268
|
const creationDate = extractElement(topicContent, 'CreationDate'); // required no-default in markup.xsd; leave undefined, don't fabricate
|
|
257
269
|
const creationAuthor = extractElement(topicContent, 'CreationAuthor'); // same: required no-default, don't fabricate 'Unknown'
|
|
258
270
|
const modifiedDate = extractElement(topicContent, 'ModifiedDate');
|
|
@@ -285,7 +297,9 @@ async function readTopic(zip, topicFolder, budget, versionId) {
|
|
|
285
297
|
topicType,
|
|
286
298
|
topicStatus,
|
|
287
299
|
priority,
|
|
288
|
-
|
|
300
|
+
// Preserve integer parsing: accepting fractions makes re-export throw.
|
|
301
|
+
// Guard malformed/overflowed tokens instead of storing NaN/Infinity (#3961).
|
|
302
|
+
index: Number.isFinite(parsedIndex) ? parsedIndex : undefined,
|
|
289
303
|
creationDate,
|
|
290
304
|
creationAuthor,
|
|
291
305
|
modifiedDate,
|
|
@@ -637,241 +651,6 @@ async function parseViewpoints(zip, topicFolder, markupContent, budget, versionI
|
|
|
637
651
|
}
|
|
638
652
|
return viewpoints;
|
|
639
653
|
}
|
|
640
|
-
/**
|
|
641
|
-
* Parse viewpoint XML content
|
|
642
|
-
*/
|
|
643
|
-
function parseViewpointContent(content, versionId) {
|
|
644
|
-
// Extract viewpoint GUID from root element (Guid can be anywhere in the tag)
|
|
645
|
-
const guidMatch = content.match(/<VisualizationInfo[^>]+Guid="([^"]+)"/);
|
|
646
|
-
const guid = guidMatch?.[1] || crypto.randomUUID?.() || `vp-${Date.now()}`;
|
|
647
|
-
// Parse perspective camera
|
|
648
|
-
const perspectiveCamera = parsePerspectiveCamera(content);
|
|
649
|
-
// Parse orthogonal camera
|
|
650
|
-
const orthogonalCamera = parseOrthogonalCamera(content);
|
|
651
|
-
// Parse components
|
|
652
|
-
const components = parseComponents(content, versionId);
|
|
653
|
-
// Parse lines
|
|
654
|
-
const lines = parseLines(content);
|
|
655
|
-
// Parse clipping planes
|
|
656
|
-
const clippingPlanes = parseClippingPlanes(content);
|
|
657
|
-
// Parse bitmaps
|
|
658
|
-
const bitmaps = parseBitmaps(content);
|
|
659
|
-
return {
|
|
660
|
-
guid,
|
|
661
|
-
perspectiveCamera,
|
|
662
|
-
orthogonalCamera,
|
|
663
|
-
components,
|
|
664
|
-
lines: lines.length > 0 ? lines : undefined,
|
|
665
|
-
clippingPlanes: clippingPlanes.length > 0 ? clippingPlanes : undefined,
|
|
666
|
-
bitmaps: bitmaps.length > 0 ? bitmaps : undefined,
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
/**
|
|
670
|
-
* Parse perspective camera from viewpoint content
|
|
671
|
-
*/
|
|
672
|
-
function parsePerspectiveCamera(content) {
|
|
673
|
-
const match = content.match(/<PerspectiveCamera>([\s\S]*?)<\/PerspectiveCamera>/);
|
|
674
|
-
if (!match)
|
|
675
|
-
return undefined;
|
|
676
|
-
const cameraContent = match[1];
|
|
677
|
-
const viewPoint = parsePoint(cameraContent, 'CameraViewPoint');
|
|
678
|
-
const direction = parseDirection(cameraContent, 'CameraDirection');
|
|
679
|
-
const upVector = parseDirection(cameraContent, 'CameraUpVector');
|
|
680
|
-
const fieldOfView = extractElement(cameraContent, 'FieldOfView');
|
|
681
|
-
if (!viewPoint || !direction || !upVector || !fieldOfView) {
|
|
682
|
-
return undefined;
|
|
683
|
-
}
|
|
684
|
-
// Same treatment as the coordinates: an unusable scalar is a missing one.
|
|
685
|
-
// `fieldOfView` is converted to radians and handed to the viewer camera,
|
|
686
|
-
// and the parser already drops the whole camera when the element is absent.
|
|
687
|
-
const fov = parseFiniteFloat(fieldOfView);
|
|
688
|
-
if (fov === undefined)
|
|
689
|
-
return undefined;
|
|
690
|
-
return {
|
|
691
|
-
cameraViewPoint: viewPoint,
|
|
692
|
-
cameraDirection: direction,
|
|
693
|
-
cameraUpVector: upVector,
|
|
694
|
-
fieldOfView: fov,
|
|
695
|
-
...parseAspectRatio(cameraContent),
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Parse the optional `<AspectRatio>` of either camera type.
|
|
700
|
-
*
|
|
701
|
-
* BCF 3.0's visinfo.xsd makes `AspectRatio` (a `PositiveDouble`) a REQUIRED
|
|
702
|
-
* child of both camera types; BCF 2.1 has no such element. It is read here for
|
|
703
|
-
* both, since the element's presence — not the archive's declared version — is
|
|
704
|
-
* what says whether there is a value to keep.
|
|
705
|
-
*
|
|
706
|
-
* Reading it matters beyond fidelity: the writer refuses to emit a 3.0 camera
|
|
707
|
-
* without one, so a 3.0 archive from another tool could otherwise be read and
|
|
708
|
-
* then not written back. Returned as a spread-able partial so an absent or
|
|
709
|
-
* unusable value leaves the property off entirely rather than setting it to
|
|
710
|
-
* `undefined`, and a non-positive value is dropped rather than carried into
|
|
711
|
-
* output the schema would reject.
|
|
712
|
-
*/
|
|
713
|
-
function parseAspectRatio(cameraContent) {
|
|
714
|
-
const raw = extractElement(cameraContent, 'AspectRatio');
|
|
715
|
-
if (!raw)
|
|
716
|
-
return {};
|
|
717
|
-
const value = parseFiniteFloat(raw);
|
|
718
|
-
if (value === undefined || !(value > 0))
|
|
719
|
-
return {};
|
|
720
|
-
return { aspectRatio: value };
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Parse orthogonal camera from viewpoint content
|
|
724
|
-
*/
|
|
725
|
-
function parseOrthogonalCamera(content) {
|
|
726
|
-
const match = content.match(/<OrthogonalCamera>([\s\S]*?)<\/OrthogonalCamera>/);
|
|
727
|
-
if (!match)
|
|
728
|
-
return undefined;
|
|
729
|
-
const cameraContent = match[1];
|
|
730
|
-
const viewPoint = parsePoint(cameraContent, 'CameraViewPoint');
|
|
731
|
-
const direction = parseDirection(cameraContent, 'CameraDirection');
|
|
732
|
-
const upVector = parseDirection(cameraContent, 'CameraUpVector');
|
|
733
|
-
const viewToWorldScale = extractElement(cameraContent, 'ViewToWorldScale');
|
|
734
|
-
if (!viewPoint || !direction || !upVector || !viewToWorldScale) {
|
|
735
|
-
return undefined;
|
|
736
|
-
}
|
|
737
|
-
// `viewToWorldScale` becomes the orthographic half-height, which is the
|
|
738
|
-
// value `getOrthoSize()` hands back into a saved viewpoint — so a
|
|
739
|
-
// non-finite one persists past the session if it is allowed in (#2461).
|
|
740
|
-
const scale = parseFiniteFloat(viewToWorldScale);
|
|
741
|
-
if (scale === undefined)
|
|
742
|
-
return undefined;
|
|
743
|
-
return {
|
|
744
|
-
cameraViewPoint: viewPoint,
|
|
745
|
-
cameraDirection: direction,
|
|
746
|
-
cameraUpVector: upVector,
|
|
747
|
-
viewToWorldScale: scale,
|
|
748
|
-
...parseAspectRatio(cameraContent),
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Parse a 3D point from XML
|
|
753
|
-
*/
|
|
754
|
-
function parsePoint(content, elementName) {
|
|
755
|
-
const match = content.match(new RegExp(`<${elementName}>([\\s\\S]*?)<\\/${elementName}>`));
|
|
756
|
-
if (!match)
|
|
757
|
-
return undefined;
|
|
758
|
-
const x = extractElement(match[1], 'X');
|
|
759
|
-
const y = extractElement(match[1], 'Y');
|
|
760
|
-
const z = extractElement(match[1], 'Z');
|
|
761
|
-
if (x === undefined || y === undefined || z === undefined) {
|
|
762
|
-
return undefined;
|
|
763
|
-
}
|
|
764
|
-
// A coordinate that is not a real number is treated as a missing one.
|
|
765
|
-
// `parseFloat` has no out-of-band failure value — `"NaN"` parses to `NaN`
|
|
766
|
-
// and the well-formed literal `"1e999"` parses to `Infinity` — and from here
|
|
767
|
-
// the value reaches `Camera.setPosition`/`setTarget`, which store a pose
|
|
768
|
-
// verbatim by design. Once stored, a single non-finite coordinate spreads
|
|
769
|
-
// across the whole pose on the next gesture. Rejecting at the file boundary
|
|
770
|
-
// means it never gets there, and it costs no new branch: every caller
|
|
771
|
-
// already drops the thing it was parsing when a coordinate is missing
|
|
772
|
-
// (#2466).
|
|
773
|
-
const px = parseFiniteFloat(x);
|
|
774
|
-
const py = parseFiniteFloat(y);
|
|
775
|
-
const pz = parseFiniteFloat(z);
|
|
776
|
-
if (px === undefined || py === undefined || pz === undefined) {
|
|
777
|
-
return undefined;
|
|
778
|
-
}
|
|
779
|
-
return { x: px, y: py, z: pz };
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Parse a 3D direction from XML
|
|
783
|
-
*/
|
|
784
|
-
function parseDirection(content, elementName) {
|
|
785
|
-
return parsePoint(content, elementName);
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Parse lines
|
|
789
|
-
*/
|
|
790
|
-
function parseLines(content) {
|
|
791
|
-
const lines = [];
|
|
792
|
-
const linesMatch = content.match(/<Lines>([\s\S]*?)<\/Lines>/);
|
|
793
|
-
if (!linesMatch)
|
|
794
|
-
return lines;
|
|
795
|
-
const lineMatches = linesMatch[1].matchAll(/<Line>([\s\S]*?)<\/Line>/g);
|
|
796
|
-
for (const match of lineMatches) {
|
|
797
|
-
const startPoint = parsePoint(match[1], 'StartPoint');
|
|
798
|
-
const endPoint = parsePoint(match[1], 'EndPoint');
|
|
799
|
-
if (startPoint && endPoint) {
|
|
800
|
-
lines.push({ startPoint, endPoint });
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
return lines;
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Parse clipping planes
|
|
807
|
-
*/
|
|
808
|
-
function parseClippingPlanes(content) {
|
|
809
|
-
const planes = [];
|
|
810
|
-
const planesMatch = content.match(/<ClippingPlanes>([\s\S]*?)<\/ClippingPlanes>/);
|
|
811
|
-
if (!planesMatch)
|
|
812
|
-
return planes;
|
|
813
|
-
const planeMatches = planesMatch[1].matchAll(/<ClippingPlane>([\s\S]*?)<\/ClippingPlane>/g);
|
|
814
|
-
for (const match of planeMatches) {
|
|
815
|
-
const location = parsePoint(match[1], 'Location');
|
|
816
|
-
const direction = parseDirection(match[1], 'Direction');
|
|
817
|
-
if (location && direction) {
|
|
818
|
-
planes.push({ location, direction });
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return planes;
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Parse bitmaps
|
|
825
|
-
*
|
|
826
|
-
* The two BCF versions diverge in shape (see writer.ts's writeBitmap/
|
|
827
|
-
* writeViewpointFiles for the write side of this):
|
|
828
|
-
* - BCF 3.0: entries sit inside a `<Bitmaps>` wrapper, and the per-entry
|
|
829
|
-
* format element is named `<Format>`. No tag inside an entry shares the
|
|
830
|
-
* entry's own name, so a plain non-greedy `<Bitmap>...</Bitmap>` match
|
|
831
|
-
* is unambiguous.
|
|
832
|
-
* - BCF 2.1: entries sit DIRECTLY under `<VisualizationInfo>` (no wrapper),
|
|
833
|
-
* and the format element is confusingly also named `<Bitmap>`, nested one
|
|
834
|
-
* level inside the entry (`<Bitmap><Bitmap>PNG</Bitmap><Reference>...`).
|
|
835
|
-
* A naive non-greedy `<Bitmap>...</Bitmap>` match on that shape terminates
|
|
836
|
-
* at the FIRST `</Bitmap>` it sees -- the inner format tag's closing tag,
|
|
837
|
-
* not the entry's -- and silently drops the rest of the entry. It must be
|
|
838
|
-
* matched with an explicit two-level pattern instead.
|
|
839
|
-
*/
|
|
840
|
-
function parseBitmaps(content) {
|
|
841
|
-
const bitmaps = [];
|
|
842
|
-
const bitmapsMatch = content.match(/<Bitmaps>([\s\S]*?)<\/Bitmaps>/);
|
|
843
|
-
const pushBitmap = (format, body) => {
|
|
844
|
-
const reference = extractElement(body, 'Reference');
|
|
845
|
-
const location = parsePoint(body, 'Location');
|
|
846
|
-
const normal = parseDirection(body, 'Normal');
|
|
847
|
-
const up = parseDirection(body, 'Up');
|
|
848
|
-
const height = extractElement(body, 'Height');
|
|
849
|
-
if (format && reference && location && normal && up && height) {
|
|
850
|
-
bitmaps.push({
|
|
851
|
-
format: format.toUpperCase() === 'JPG' ? 'JPG' : 'PNG',
|
|
852
|
-
reference,
|
|
853
|
-
location,
|
|
854
|
-
normal,
|
|
855
|
-
up,
|
|
856
|
-
height: parseFloat(height),
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
if (bitmapsMatch) {
|
|
861
|
-
// BCF 3.0 shape: <Bitmaps><Bitmap><Format>...</Format>...</Bitmap>...</Bitmaps>
|
|
862
|
-
for (const match of bitmapsMatch[1].matchAll(/<Bitmap>([\s\S]*?)<\/Bitmap>/g)) {
|
|
863
|
-
pushBitmap(extractElement(match[1], 'Format'), match[1]);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
// BCF 2.1 shape: <Bitmap><Bitmap>PNG</Bitmap>...</Bitmap>, unwrapped,
|
|
868
|
-
// directly under VisualizationInfo.
|
|
869
|
-
for (const match of content.matchAll(/<Bitmap>\s*<Bitmap>([\s\S]*?)<\/Bitmap>([\s\S]*?)<\/Bitmap>/g)) {
|
|
870
|
-
pushBitmap(match[1], match[2]);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
return bitmaps;
|
|
874
|
-
}
|
|
875
654
|
/**
|
|
876
655
|
* Convert Uint8Array to base64 string
|
|
877
656
|
*/
|