@file-viewer/renderer-cad 2.1.17 → 2.1.18
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/dist/cad.js +525 -23
- package/package.json +2 -2
package/dist/cad.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { CadViewer, } from '@flyfish-dev/cad-viewer';
|
|
1
|
+
import { CadViewer, inferEntityKind, } from '@flyfish-dev/cad-viewer';
|
|
2
2
|
import { resolveFileViewerCadAssetUrls } from '@file-viewer/core/assets';
|
|
3
3
|
import { createFileViewerViewStateChange, createFileViewerViewStateChangeEmitter, createFileViewerTranslator, createFileViewerZoomChangeEmitter, registerFileViewerViewStateProvider, registerFileViewerZoomProvider, resolveFileViewerLocale, unregisterFileViewerViewStateProvider, unregisterFileViewerZoomProvider, } from '@file-viewer/core';
|
|
4
4
|
const CAD_WORKER_TIMEOUT = 120000;
|
|
5
|
+
const CAD_DEFAULT_FIT_PADDING = 0.92;
|
|
6
|
+
const CAD_BOUNDS_EPSILON = 1e-9;
|
|
7
|
+
const CAD_BEST_FIT_OUTLIER_RATIO = 8;
|
|
5
8
|
const cadStyle = `
|
|
6
9
|
.cad-shell{display:flex;height:100%;min-height:100%;flex-direction:column;background:#f5f7fb;color:#142335}
|
|
7
10
|
.cad-shell *{box-sizing:border-box}
|
|
@@ -11,17 +14,19 @@ const cadStyle = `
|
|
|
11
14
|
.cad-tools button:hover{background:rgba(31,150,110,.14);color:#0f8f62}
|
|
12
15
|
.cad-zoom,.cad-meta span{color:#64748b;font-size:12px;font-weight:800;letter-spacing:0}
|
|
13
16
|
.cad-meta span{border-radius:999px;padding:5px 9px;background:rgba(15,23,42,.06)}
|
|
14
|
-
.cad-body{display:grid;min-height:0;flex:1;grid-template-columns:minmax(
|
|
15
|
-
.cad-body.without-layers{grid-template-columns:minmax(0,1fr) minmax(
|
|
17
|
+
.cad-body{display:grid;min-height:0;flex:1;grid-template-columns:minmax(176px,236px) minmax(0,1fr) minmax(142px,176px);background:#eef2f7}
|
|
18
|
+
.cad-body.without-layers{grid-template-columns:minmax(0,1fr) minmax(142px,176px)}
|
|
16
19
|
.cad-layers,.cad-inspector{min-height:0;overflow:auto;border-right:1px solid rgba(15,23,42,.08);background:#fff}
|
|
17
20
|
.cad-layers[hidden]{display:none}
|
|
18
21
|
.cad-inspector{border-right:0;border-left:1px solid rgba(15,23,42,.08);padding:14px}
|
|
19
|
-
.cad-layers-head{position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;padding:12px 12px
|
|
20
|
-
.cad-layers-head span{color:#7b8ca5;font-size:
|
|
21
|
-
.cad-layers-list{display:flex;flex-direction:column;padding:
|
|
22
|
-
.cad-layers button{display:
|
|
23
|
-
.cad-layers button
|
|
24
|
-
.cad-
|
|
22
|
+
.cad-layers-head{position:sticky;top:0;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 12px 9px;border-bottom:1px solid rgba(148,163,184,.16);background:rgba(255,255,255,.96);color:#1f2a3d;font-size:12px;z-index:1}
|
|
23
|
+
.cad-layers-head span{color:#7b8ca5;font-size:11px;font-weight:800;white-space:nowrap}
|
|
24
|
+
.cad-layers-list{display:flex;flex-direction:column;gap:2px;padding:7px 8px 10px}
|
|
25
|
+
.cad-layers button{display:grid;width:100%;min-height:30px;grid-template-columns:14px minmax(0,1fr);align-items:center;gap:7px;border:0;border-radius:6px;background:transparent;color:#25344c;cursor:pointer;font-size:12px;font-weight:700;text-align:left}
|
|
26
|
+
.cad-layers button:hover{background:#f1f5f9}
|
|
27
|
+
.cad-layers button.muted{color:#94a3b8}
|
|
28
|
+
.cad-layers button span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
29
|
+
.cad-layer-color{width:8px;height:8px;justify-self:center;border-radius:50%;background:#1f966e;box-shadow:0 0 0 1px rgba(15,23,42,.12),0 0 0 3px rgba(31,150,110,.1)}
|
|
25
30
|
.cad-canvas-wrap{position:relative;min-width:0;min-height:0;overflow:hidden;background:linear-gradient(90deg,rgba(15,23,42,.04) 1px,transparent 1px),linear-gradient(180deg,rgba(15,23,42,.04) 1px,transparent 1px),#f8fafc;background-size:28px 28px}
|
|
26
31
|
.cad-stage{position:relative;width:100%;height:100%;min-height:420px;overflow:hidden}
|
|
27
32
|
.cad-stage canvas{position:absolute;inset:0;display:block;width:100%!important;height:100%!important}
|
|
@@ -99,13 +104,79 @@ const createElement = (tagName, className, text) => {
|
|
|
99
104
|
const normalizeType = (type) => {
|
|
100
105
|
return (type || 'dxf').toLowerCase();
|
|
101
106
|
};
|
|
107
|
+
const normalizeLayerKey = (name) => name.trim().toLocaleLowerCase();
|
|
108
|
+
const getLayerNameScore = (name) => {
|
|
109
|
+
const letters = name.replace(/[^a-z]/gi, '');
|
|
110
|
+
if (!letters) {
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
const isUpper = letters === letters.toLocaleUpperCase();
|
|
114
|
+
const isLower = letters === letters.toLocaleLowerCase();
|
|
115
|
+
if (!isUpper && !isLower) {
|
|
116
|
+
return 3;
|
|
117
|
+
}
|
|
118
|
+
return isUpper ? 2 : 1;
|
|
119
|
+
};
|
|
120
|
+
const shouldPreferLayerName = (candidate, current) => {
|
|
121
|
+
const nextScore = getLayerNameScore(candidate);
|
|
122
|
+
const currentScore = getLayerNameScore(current);
|
|
123
|
+
if (nextScore !== currentScore) {
|
|
124
|
+
return nextScore > currentScore;
|
|
125
|
+
}
|
|
126
|
+
if (candidate.length !== current.length) {
|
|
127
|
+
return candidate.length > current.length;
|
|
128
|
+
}
|
|
129
|
+
return candidate.localeCompare(current, undefined, { sensitivity: 'base' }) < 0;
|
|
130
|
+
};
|
|
131
|
+
const addLayerSourceName = (layer, name) => {
|
|
132
|
+
if (typeof name !== 'string' && typeof name !== 'number') {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const nextName = String(name);
|
|
136
|
+
if (!nextName || layer.sourceNames.includes(nextName)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
layer.sourceNames.push(nextName);
|
|
140
|
+
};
|
|
102
141
|
const collectLayers = (result) => {
|
|
103
142
|
if (!result) {
|
|
104
143
|
return [];
|
|
105
144
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
145
|
+
const groups = new Map();
|
|
146
|
+
Object.entries(result.document.layers).forEach(([key, layer]) => {
|
|
147
|
+
var _a, _b, _c, _d, _e;
|
|
148
|
+
const sourceName = typeof layer.name === 'string' && layer.name ? layer.name : key;
|
|
149
|
+
const groupKey = normalizeLayerKey(sourceName || key);
|
|
150
|
+
const current = groups.get(groupKey);
|
|
151
|
+
if (!current) {
|
|
152
|
+
groups.set(groupKey, {
|
|
153
|
+
...layer,
|
|
154
|
+
name: sourceName || key,
|
|
155
|
+
sourceNames: Array.from(new Set([key, sourceName].filter((name) => typeof name === 'string' && name.length > 0))),
|
|
156
|
+
duplicateCount: 1,
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
current.duplicateCount += 1;
|
|
161
|
+
addLayerSourceName(current, key);
|
|
162
|
+
addLayerSourceName(current, sourceName);
|
|
163
|
+
current.isVisible = current.isVisible !== false || layer.isVisible !== false;
|
|
164
|
+
current.isFrozen = !!current.isFrozen && !!layer.isFrozen;
|
|
165
|
+
current.isLocked = !!current.isLocked || !!layer.isLocked;
|
|
166
|
+
current.color = (_a = current.color) !== null && _a !== void 0 ? _a : layer.color;
|
|
167
|
+
current.colorIndex = (_b = current.colorIndex) !== null && _b !== void 0 ? _b : layer.colorIndex;
|
|
168
|
+
current.trueColor = (_c = current.trueColor) !== null && _c !== void 0 ? _c : layer.trueColor;
|
|
169
|
+
current.lineType = (_d = current.lineType) !== null && _d !== void 0 ? _d : layer.lineType;
|
|
170
|
+
current.lineweight = (_e = current.lineweight) !== null && _e !== void 0 ? _e : layer.lineweight;
|
|
171
|
+
if (shouldPreferLayerName(sourceName, current.name)) {
|
|
172
|
+
current.name = sourceName;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return Array.from(groups.values())
|
|
176
|
+
.sort((left, right) => left.name.localeCompare(right.name, undefined, {
|
|
177
|
+
numeric: true,
|
|
178
|
+
sensitivity: 'base',
|
|
179
|
+
}));
|
|
109
180
|
};
|
|
110
181
|
const formatNumber = (value, locale = 'zh-CN') => {
|
|
111
182
|
if (!Number.isFinite(value)) {
|
|
@@ -115,7 +186,350 @@ const formatNumber = (value, locale = 'zh-CN') => {
|
|
|
115
186
|
};
|
|
116
187
|
const getCadDocumentBaseUrl = (target) => {
|
|
117
188
|
var _a;
|
|
118
|
-
return ((_a = target.ownerDocument) === null || _a === void 0 ? void 0 : _a.baseURI) || '
|
|
189
|
+
return ((_a = target.ownerDocument) === null || _a === void 0 ? void 0 : _a.baseURI) || 'file:///';
|
|
190
|
+
};
|
|
191
|
+
const createEmptyCadBounds = () => ({
|
|
192
|
+
minX: Number.POSITIVE_INFINITY,
|
|
193
|
+
minY: Number.POSITIVE_INFINITY,
|
|
194
|
+
maxX: Number.NEGATIVE_INFINITY,
|
|
195
|
+
maxY: Number.NEGATIVE_INFINITY,
|
|
196
|
+
});
|
|
197
|
+
const isCadBoundsValid = (bounds) => {
|
|
198
|
+
return !!bounds &&
|
|
199
|
+
Number.isFinite(bounds.minX) &&
|
|
200
|
+
Number.isFinite(bounds.minY) &&
|
|
201
|
+
Number.isFinite(bounds.maxX) &&
|
|
202
|
+
Number.isFinite(bounds.maxY) &&
|
|
203
|
+
bounds.maxX >= bounds.minX &&
|
|
204
|
+
bounds.maxY >= bounds.minY;
|
|
205
|
+
};
|
|
206
|
+
const getCadBoundsWidth = (bounds) => bounds.maxX - bounds.minX;
|
|
207
|
+
const getCadBoundsHeight = (bounds) => bounds.maxY - bounds.minY;
|
|
208
|
+
const isCadBoundsMeaningful = (bounds) => {
|
|
209
|
+
return getCadBoundsWidth(bounds) > CAD_BOUNDS_EPSILON ||
|
|
210
|
+
getCadBoundsHeight(bounds) > CAD_BOUNDS_EPSILON;
|
|
211
|
+
};
|
|
212
|
+
const isCadPoint = (value) => {
|
|
213
|
+
return !!value &&
|
|
214
|
+
typeof value === 'object' &&
|
|
215
|
+
Number.isFinite(value.x) &&
|
|
216
|
+
Number.isFinite(value.y);
|
|
217
|
+
};
|
|
218
|
+
const addCadPointToBounds = (bounds, point) => {
|
|
219
|
+
if (!isCadPoint(point)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
bounds.minX = Math.min(bounds.minX, point.x);
|
|
223
|
+
bounds.minY = Math.min(bounds.minY, point.y);
|
|
224
|
+
bounds.maxX = Math.max(bounds.maxX, point.x);
|
|
225
|
+
bounds.maxY = Math.max(bounds.maxY, point.y);
|
|
226
|
+
};
|
|
227
|
+
const addCadBounds = (target, source) => {
|
|
228
|
+
if (!isCadBoundsValid(source)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
addCadPointToBounds(target, { x: source.minX, y: source.minY });
|
|
232
|
+
addCadPointToBounds(target, { x: source.maxX, y: source.maxY });
|
|
233
|
+
};
|
|
234
|
+
const unionCadBounds = (boundsList) => {
|
|
235
|
+
const union = createEmptyCadBounds();
|
|
236
|
+
boundsList.forEach(bounds => addCadBounds(union, bounds));
|
|
237
|
+
return isCadBoundsValid(union) ? union : null;
|
|
238
|
+
};
|
|
239
|
+
const getCadBoundsArea = (bounds) => {
|
|
240
|
+
return Math.max(getCadBoundsWidth(bounds), CAD_BOUNDS_EPSILON) *
|
|
241
|
+
Math.max(getCadBoundsHeight(bounds), CAD_BOUNDS_EPSILON);
|
|
242
|
+
};
|
|
243
|
+
const addCadCircleBounds = (bounds, center, radius) => {
|
|
244
|
+
const value = Number(radius);
|
|
245
|
+
if (!isCadPoint(center) || !Number.isFinite(value)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const nextRadius = Math.abs(value);
|
|
249
|
+
addCadPointToBounds(bounds, { x: center.x - nextRadius, y: center.y - nextRadius });
|
|
250
|
+
addCadPointToBounds(bounds, { x: center.x + nextRadius, y: center.y + nextRadius });
|
|
251
|
+
};
|
|
252
|
+
const addCadPointListToBounds = (bounds, points) => {
|
|
253
|
+
if (!Array.isArray(points)) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
points.forEach(point => addCadPointToBounds(bounds, point));
|
|
257
|
+
};
|
|
258
|
+
const addCadPathCommandsToBounds = (bounds, commands) => {
|
|
259
|
+
if (!Array.isArray(commands)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
commands.forEach(command => {
|
|
263
|
+
const points = command.points;
|
|
264
|
+
addCadPointListToBounds(bounds, points);
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
const addCadTextBounds = (bounds, entity) => {
|
|
268
|
+
var _a, _b, _c, _d;
|
|
269
|
+
const anchor = entity.insertionPoint || entity.startPoint || entity.center;
|
|
270
|
+
if (!isCadPoint(anchor)) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const text = String((_b = (_a = entity.text) !== null && _a !== void 0 ? _a : entity.value) !== null && _b !== void 0 ? _b : '');
|
|
274
|
+
const height = Math.abs(Number((_d = (_c = entity.textHeight) !== null && _c !== void 0 ? _c : entity.height) !== null && _d !== void 0 ? _d : 1));
|
|
275
|
+
const lines = text.split(/\r?\n/g);
|
|
276
|
+
const width = Math.max(1, ...lines.map(line => line.length)) * Math.max(height, 1) * 0.62;
|
|
277
|
+
const blockHeight = Math.max(1, lines.length) * Math.max(height, 1) * 1.22;
|
|
278
|
+
addCadPointToBounds(bounds, anchor);
|
|
279
|
+
addCadPointToBounds(bounds, { x: anchor.x + width, y: anchor.y - blockHeight });
|
|
280
|
+
};
|
|
281
|
+
const getCadEntityAnchor = (entity) => {
|
|
282
|
+
var _a;
|
|
283
|
+
for (const key of ['startPoint', 'insertionPoint', 'center', 'point', 'location']) {
|
|
284
|
+
const point = entity[key];
|
|
285
|
+
if (isCadPoint(point)) {
|
|
286
|
+
return point;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const vertices = entity.vertices || entity.points;
|
|
290
|
+
if (Array.isArray(vertices) && isCadPoint(vertices[0])) {
|
|
291
|
+
return vertices[0];
|
|
292
|
+
}
|
|
293
|
+
const command = (_a = entity.commands) === null || _a === void 0 ? void 0 : _a.find(item => item.points.length > 0);
|
|
294
|
+
return command === null || command === void 0 ? void 0 : command.points[0];
|
|
295
|
+
};
|
|
296
|
+
const getCadEntityLocalBounds = (entity) => {
|
|
297
|
+
var _a, _b, _c, _d, _e;
|
|
298
|
+
const bounds = createEmptyCadBounds();
|
|
299
|
+
const type = String((_a = entity.type) !== null && _a !== void 0 ? _a : '').toUpperCase();
|
|
300
|
+
const kind = entity.kind || inferEntityKind(type);
|
|
301
|
+
if (kind === 'viewport') {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
if (kind === 'line') {
|
|
305
|
+
addCadPointToBounds(bounds, entity.startPoint);
|
|
306
|
+
addCadPointToBounds(bounds, entity.endPoint);
|
|
307
|
+
}
|
|
308
|
+
else if (kind === 'circle' || kind === 'arc') {
|
|
309
|
+
addCadCircleBounds(bounds, entity.center, entity.radius);
|
|
310
|
+
}
|
|
311
|
+
else if (kind === 'polyline' || kind === 'solid' || kind === 'spline') {
|
|
312
|
+
addCadPointListToBounds(bounds, entity.vertices);
|
|
313
|
+
addCadPointListToBounds(bounds, entity.points);
|
|
314
|
+
addCadPointListToBounds(bounds, entity.controlPoints);
|
|
315
|
+
addCadPointListToBounds(bounds, entity.fitPoints);
|
|
316
|
+
}
|
|
317
|
+
else if (kind === 'ellipse') {
|
|
318
|
+
const center = entity.center;
|
|
319
|
+
const major = entity.majorAxisEndPoint;
|
|
320
|
+
if (isCadPoint(center) && isCadPoint(major)) {
|
|
321
|
+
const majorRadius = Math.hypot(major.x, major.y);
|
|
322
|
+
const minorRadius = majorRadius * Math.abs(Number((_b = entity.axisRatio) !== null && _b !== void 0 ? _b : 1));
|
|
323
|
+
addCadCircleBounds(bounds, center, Math.max(majorRadius, minorRadius));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else if (kind === 'text') {
|
|
327
|
+
addCadTextBounds(bounds, entity);
|
|
328
|
+
}
|
|
329
|
+
else if (kind === 'path') {
|
|
330
|
+
addCadPathCommandsToBounds(bounds, entity.commands);
|
|
331
|
+
}
|
|
332
|
+
else if (kind === 'hatch') {
|
|
333
|
+
(_c = entity.loops) === null || _c === void 0 ? void 0 : _c.forEach(loop => {
|
|
334
|
+
addCadPointListToBounds(bounds, loop.vertices);
|
|
335
|
+
addCadPathCommandsToBounds(bounds, loop.commands);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
else if (kind === 'image') {
|
|
339
|
+
const point = entity.insertionPoint;
|
|
340
|
+
const width = Number((_d = entity.width) !== null && _d !== void 0 ? _d : 32);
|
|
341
|
+
const height = Number((_e = entity.height) !== null && _e !== void 0 ? _e : 32);
|
|
342
|
+
if (isCadPoint(point) && Number.isFinite(width) && Number.isFinite(height)) {
|
|
343
|
+
addCadPointToBounds(bounds, point);
|
|
344
|
+
addCadPointToBounds(bounds, { x: point.x + width, y: point.y - height });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
addCadPointToBounds(bounds, getCadEntityAnchor(entity));
|
|
349
|
+
}
|
|
350
|
+
return isCadBoundsValid(bounds) ? bounds : null;
|
|
351
|
+
};
|
|
352
|
+
const identityCadMatrix = () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 });
|
|
353
|
+
const translateCadMatrix = (x, y) => ({ a: 1, b: 0, c: 0, d: 1, e: x, f: y });
|
|
354
|
+
const scaleCadMatrix = (x, y) => ({ a: x, b: 0, c: 0, d: y, e: 0, f: 0 });
|
|
355
|
+
const rotateCadMatrix = (rotation) => {
|
|
356
|
+
const cos = Math.cos(rotation);
|
|
357
|
+
const sin = Math.sin(rotation);
|
|
358
|
+
return { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
|
|
359
|
+
};
|
|
360
|
+
const multiplyCadMatrix = (left, right) => ({
|
|
361
|
+
a: left.a * right.a + left.c * right.b,
|
|
362
|
+
b: left.b * right.a + left.d * right.b,
|
|
363
|
+
c: left.a * right.c + left.c * right.d,
|
|
364
|
+
d: left.b * right.c + left.d * right.d,
|
|
365
|
+
e: left.a * right.e + left.c * right.f + left.e,
|
|
366
|
+
f: left.b * right.e + left.d * right.f + left.f,
|
|
367
|
+
});
|
|
368
|
+
const transformCadPoint = (point, matrix) => ({
|
|
369
|
+
x: point.x * matrix.a + point.y * matrix.c + matrix.e,
|
|
370
|
+
y: point.x * matrix.b + point.y * matrix.d + matrix.f,
|
|
371
|
+
});
|
|
372
|
+
const transformCadBounds = (bounds, matrix) => {
|
|
373
|
+
const next = createEmptyCadBounds();
|
|
374
|
+
[
|
|
375
|
+
{ x: bounds.minX, y: bounds.minY },
|
|
376
|
+
{ x: bounds.minX, y: bounds.maxY },
|
|
377
|
+
{ x: bounds.maxX, y: bounds.minY },
|
|
378
|
+
{ x: bounds.maxX, y: bounds.maxY },
|
|
379
|
+
].forEach(point => addCadPointToBounds(next, transformCadPoint(point, matrix)));
|
|
380
|
+
return isCadBoundsValid(next) ? next : null;
|
|
381
|
+
};
|
|
382
|
+
const createCadInsertMatrix = (entity, blockBasePoint = { x: 0, y: 0 }) => {
|
|
383
|
+
var _a, _b, _c, _d, _e;
|
|
384
|
+
const insertion = entity.insertionPoint || { x: 0, y: 0 };
|
|
385
|
+
const scale = entity.scale;
|
|
386
|
+
const scaleX = Number(typeof scale === 'object' && scale ? (_a = scale.x) !== null && _a !== void 0 ? _a : 1 : (_b = entity.scaleX) !== null && _b !== void 0 ? _b : 1);
|
|
387
|
+
const scaleY = Number(typeof scale === 'object' && scale ? (_c = scale.y) !== null && _c !== void 0 ? _c : scaleX : (_d = entity.scaleY) !== null && _d !== void 0 ? _d : scaleX);
|
|
388
|
+
const rotation = Number((_e = entity.rotation) !== null && _e !== void 0 ? _e : 0);
|
|
389
|
+
let matrix = translateCadMatrix(isCadPoint(insertion) ? insertion.x : 0, isCadPoint(insertion) ? insertion.y : 0);
|
|
390
|
+
matrix = multiplyCadMatrix(matrix, rotateCadMatrix(Number.isFinite(rotation) ? rotation : 0));
|
|
391
|
+
matrix = multiplyCadMatrix(matrix, scaleCadMatrix(Number.isFinite(scaleX) ? scaleX : 1, Number.isFinite(scaleY) ? scaleY : 1));
|
|
392
|
+
matrix = multiplyCadMatrix(matrix, translateCadMatrix(-blockBasePoint.x, -blockBasePoint.y));
|
|
393
|
+
return matrix;
|
|
394
|
+
};
|
|
395
|
+
const findCadLayer = (document, name) => {
|
|
396
|
+
if (typeof name !== 'string' && typeof name !== 'number') {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
const key = String(name);
|
|
400
|
+
return document.layers[key] ||
|
|
401
|
+
document.layers[key.toLocaleLowerCase()] ||
|
|
402
|
+
Object.values(document.layers).find(layer => normalizeLayerKey(layer.name) === normalizeLayerKey(key));
|
|
403
|
+
};
|
|
404
|
+
const findCadBlock = (document, name) => {
|
|
405
|
+
if (typeof name !== 'string' && typeof name !== 'number') {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
const key = String(name);
|
|
409
|
+
return document.blocks[key] ||
|
|
410
|
+
document.blocks[key.toLocaleLowerCase()] ||
|
|
411
|
+
Object.values(document.blocks).find(block => normalizeLayerKey(block.name) === normalizeLayerKey(key));
|
|
412
|
+
};
|
|
413
|
+
const isCadEntityVisible = (document, entity) => {
|
|
414
|
+
if (entity.isVisible === false) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
const layer = findCadLayer(document, entity.layer);
|
|
418
|
+
return layer ? layer.isVisible !== false && !layer.isFrozen : true;
|
|
419
|
+
};
|
|
420
|
+
const collectCadVisibleEntityBounds = (document, maxInsertDepth) => {
|
|
421
|
+
const boundsList = [];
|
|
422
|
+
const visit = (entity, depth, matrix) => {
|
|
423
|
+
var _a, _b;
|
|
424
|
+
if (!isCadEntityVisible(document, entity)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const type = String((_a = entity.type) !== null && _a !== void 0 ? _a : '').toUpperCase();
|
|
428
|
+
const kind = entity.kind || inferEntityKind(type);
|
|
429
|
+
if (kind === 'insert') {
|
|
430
|
+
const block = findCadBlock(document, (_b = entity.blockName) !== null && _b !== void 0 ? _b : entity.name);
|
|
431
|
+
if (block && depth < maxInsertDepth) {
|
|
432
|
+
const nextMatrix = multiplyCadMatrix(matrix, createCadInsertMatrix(entity, block.basePoint || { x: 0, y: 0 }));
|
|
433
|
+
block.entities.forEach(child => visit(child, depth + 1, nextMatrix));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const localBounds = getCadEntityLocalBounds(entity);
|
|
438
|
+
if (!localBounds) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const nextBounds = transformCadBounds(localBounds, matrix);
|
|
442
|
+
if (nextBounds) {
|
|
443
|
+
boundsList.push(nextBounds);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
document.entities.forEach(entity => visit(entity, 0, identityCadMatrix()));
|
|
447
|
+
return boundsList;
|
|
448
|
+
};
|
|
449
|
+
const inflateCadDegenerateBounds = (bounds) => {
|
|
450
|
+
const width = getCadBoundsWidth(bounds);
|
|
451
|
+
const height = getCadBoundsHeight(bounds);
|
|
452
|
+
if (width > CAD_BOUNDS_EPSILON && height > CAD_BOUNDS_EPSILON) {
|
|
453
|
+
return bounds;
|
|
454
|
+
}
|
|
455
|
+
const centerX = (bounds.minX + bounds.maxX) / 2;
|
|
456
|
+
const centerY = (bounds.minY + bounds.maxY) / 2;
|
|
457
|
+
const pad = Math.max(width, height, 1) * 0.5;
|
|
458
|
+
return {
|
|
459
|
+
minX: centerX - (width > CAD_BOUNDS_EPSILON ? width / 2 : pad),
|
|
460
|
+
maxX: centerX + (width > CAD_BOUNDS_EPSILON ? width / 2 : pad),
|
|
461
|
+
minY: centerY - (height > CAD_BOUNDS_EPSILON ? height / 2 : pad),
|
|
462
|
+
maxY: centerY + (height > CAD_BOUNDS_EPSILON ? height / 2 : pad),
|
|
463
|
+
};
|
|
464
|
+
};
|
|
465
|
+
const selectCadBestFitBounds = (boundsList) => {
|
|
466
|
+
let candidates = boundsList.filter(isCadBoundsValid);
|
|
467
|
+
const meaningful = candidates.filter(isCadBoundsMeaningful);
|
|
468
|
+
if (meaningful.length > 0) {
|
|
469
|
+
candidates = meaningful;
|
|
470
|
+
}
|
|
471
|
+
const globalBounds = unionCadBounds(candidates);
|
|
472
|
+
if (!globalBounds || candidates.length < 8) {
|
|
473
|
+
return globalBounds ? inflateCadDegenerateBounds(globalBounds) : null;
|
|
474
|
+
}
|
|
475
|
+
const centers = candidates.map(bounds => ({
|
|
476
|
+
bounds,
|
|
477
|
+
x: (bounds.minX + bounds.maxX) / 2,
|
|
478
|
+
y: (bounds.minY + bounds.maxY) / 2,
|
|
479
|
+
}));
|
|
480
|
+
const trim = Math.max(1, Math.floor(centers.length * 0.05));
|
|
481
|
+
const sortedX = [...centers].sort((left, right) => left.x - right.x);
|
|
482
|
+
const sortedY = [...centers].sort((left, right) => left.y - right.y);
|
|
483
|
+
const minX = sortedX[Math.min(trim, sortedX.length - 1)].x;
|
|
484
|
+
const maxX = sortedX[Math.max(sortedX.length - 1 - trim, 0)].x;
|
|
485
|
+
const minY = sortedY[Math.min(trim, sortedY.length - 1)].y;
|
|
486
|
+
const maxY = sortedY[Math.max(sortedY.length - 1 - trim, 0)].y;
|
|
487
|
+
const trimmed = centers
|
|
488
|
+
.filter(item => item.x >= minX && item.x <= maxX && item.y >= minY && item.y <= maxY)
|
|
489
|
+
.map(item => item.bounds);
|
|
490
|
+
const minKeep = Math.max(4, Math.floor(candidates.length * 0.55));
|
|
491
|
+
const trimmedBounds = trimmed.length >= minKeep ? unionCadBounds(trimmed) : null;
|
|
492
|
+
if (!trimmedBounds) {
|
|
493
|
+
return inflateCadDegenerateBounds(globalBounds);
|
|
494
|
+
}
|
|
495
|
+
const globalSpan = Math.max(getCadBoundsWidth(globalBounds), getCadBoundsHeight(globalBounds), CAD_BOUNDS_EPSILON);
|
|
496
|
+
const trimmedSpan = Math.max(getCadBoundsWidth(trimmedBounds), getCadBoundsHeight(trimmedBounds), CAD_BOUNDS_EPSILON);
|
|
497
|
+
const areaRatio = getCadBoundsArea(globalBounds) / getCadBoundsArea(trimmedBounds);
|
|
498
|
+
if (globalSpan / trimmedSpan >= CAD_BEST_FIT_OUTLIER_RATIO || areaRatio >= CAD_BEST_FIT_OUTLIER_RATIO ** 2) {
|
|
499
|
+
return inflateCadDegenerateBounds(trimmedBounds);
|
|
500
|
+
}
|
|
501
|
+
return inflateCadDegenerateBounds(globalBounds);
|
|
502
|
+
};
|
|
503
|
+
const resolveCadFitPadding = (options) => {
|
|
504
|
+
const padding = Number(options.fitPadding);
|
|
505
|
+
if (!Number.isFinite(padding) || padding <= 0) {
|
|
506
|
+
return CAD_DEFAULT_FIT_PADDING;
|
|
507
|
+
}
|
|
508
|
+
return Math.min(1, Math.max(0.2, padding));
|
|
509
|
+
};
|
|
510
|
+
const resolveCadFitBounds = (document, rendererBounds, options) => {
|
|
511
|
+
var _a;
|
|
512
|
+
if (!document || options.fitMode === 'native') {
|
|
513
|
+
return rendererBounds && isCadBoundsValid(rendererBounds)
|
|
514
|
+
? inflateCadDegenerateBounds(rendererBounds)
|
|
515
|
+
: null;
|
|
516
|
+
}
|
|
517
|
+
const configuredDepth = Number((_a = options.maxInsertDepth) !== null && _a !== void 0 ? _a : 16);
|
|
518
|
+
const maxInsertDepth = Number.isFinite(configuredDepth) ? Math.max(0, configuredDepth) : 16;
|
|
519
|
+
const entityBounds = collectCadVisibleEntityBounds(document, maxInsertDepth);
|
|
520
|
+
return selectCadBestFitBounds(entityBounds) ||
|
|
521
|
+
(rendererBounds && isCadBoundsValid(rendererBounds) ? inflateCadDegenerateBounds(rendererBounds) : null);
|
|
522
|
+
};
|
|
523
|
+
const resolveLayerSourceNames = (document, layer) => {
|
|
524
|
+
const normalized = normalizeLayerKey(layer.name);
|
|
525
|
+
const names = new Set(layer.sourceNames);
|
|
526
|
+
Object.entries(document.layers).forEach(([key, source]) => {
|
|
527
|
+
if (normalizeLayerKey(key) === normalized || normalizeLayerKey(source.name) === normalized) {
|
|
528
|
+
names.add(key);
|
|
529
|
+
names.add(source.name);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
return Array.from(names);
|
|
119
533
|
};
|
|
120
534
|
export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
121
535
|
var _a;
|
|
@@ -133,6 +547,7 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
133
547
|
let viewer = null;
|
|
134
548
|
let resizeObserver = null;
|
|
135
549
|
let abortController = null;
|
|
550
|
+
let fitViewActive = true;
|
|
136
551
|
let disposed = false;
|
|
137
552
|
const style = createStyle();
|
|
138
553
|
const shell = createElement('div', 'cad-shell');
|
|
@@ -247,7 +662,12 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
247
662
|
layersList.replaceChildren(...layers.map(layer => {
|
|
248
663
|
const button = createElement('button');
|
|
249
664
|
button.type = 'button';
|
|
250
|
-
|
|
665
|
+
const layerVisible = layer.isVisible !== false && !layer.isFrozen;
|
|
666
|
+
button.classList.toggle('muted', !layerVisible);
|
|
667
|
+
button.title = layer.duplicateCount > 1
|
|
668
|
+
? `${layer.name} (${layer.duplicateCount} merged)`
|
|
669
|
+
: layer.name;
|
|
670
|
+
button.setAttribute('aria-pressed', String(layerVisible));
|
|
251
671
|
const color = createElement('span', 'cad-layer-color');
|
|
252
672
|
if (typeof layer.color === 'string') {
|
|
253
673
|
color.style.background = layer.color;
|
|
@@ -255,16 +675,22 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
255
675
|
button.append(color, createElement('span', undefined, layer.name));
|
|
256
676
|
button.addEventListener('click', () => {
|
|
257
677
|
const document = viewer === null || viewer === void 0 ? void 0 : viewer.getDocument();
|
|
258
|
-
if (!
|
|
678
|
+
if (!document) {
|
|
259
679
|
return;
|
|
260
680
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
681
|
+
const nextVisible = !layerVisible;
|
|
682
|
+
resolveLayerSourceNames(document, layer).forEach(sourceName => {
|
|
683
|
+
const current = document.layers[sourceName] || findCadLayer(document, sourceName);
|
|
684
|
+
if (current) {
|
|
685
|
+
current.isVisible = nextVisible;
|
|
686
|
+
}
|
|
687
|
+
});
|
|
263
688
|
loadResult = (viewer === null || viewer === void 0 ? void 0 : viewer.setDocument(document, buildFileName())) || null;
|
|
264
689
|
layers = collectLayers(loadResult);
|
|
265
690
|
syncUi();
|
|
266
691
|
queueMicrotask(() => {
|
|
267
|
-
|
|
692
|
+
fitViewActive = true;
|
|
693
|
+
applyCadFit();
|
|
268
694
|
cadZoomEmitter.emit();
|
|
269
695
|
emitCadViewStateChange('layer-toggle', 'user');
|
|
270
696
|
});
|
|
@@ -295,19 +721,83 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
295
721
|
progressMessage = `${prefix}${progress.message}${percent}`;
|
|
296
722
|
syncState();
|
|
297
723
|
};
|
|
298
|
-
const
|
|
299
|
-
|
|
724
|
+
const applyCadFit = () => {
|
|
725
|
+
var _a, _b, _c, _d;
|
|
726
|
+
if (!viewer) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
if ((_a = viewer.isNativeRendererActive) === null || _a === void 0 ? void 0 : _a.call(viewer)) {
|
|
730
|
+
viewer.fit();
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
const renderer = viewer.renderer;
|
|
734
|
+
const rendererBounds = (_b = renderer.getBounds) === null || _b === void 0 ? void 0 : _b.call(renderer);
|
|
735
|
+
const fitBounds = resolveCadFitBounds(viewer.getDocument(), rendererBounds, options);
|
|
736
|
+
if (!fitBounds || !renderer.setViewState) {
|
|
737
|
+
viewer.fit();
|
|
738
|
+
return true;
|
|
739
|
+
}
|
|
740
|
+
const width = Math.max(1, stage.clientWidth || viewer.canvas.clientWidth || target.clientWidth);
|
|
741
|
+
const height = Math.max(1, stage.clientHeight || viewer.canvas.clientHeight || target.clientHeight);
|
|
742
|
+
const boundsWidth = Math.max(getCadBoundsWidth(fitBounds), CAD_BOUNDS_EPSILON);
|
|
743
|
+
const boundsHeight = Math.max(getCadBoundsHeight(fitBounds), CAD_BOUNDS_EPSILON);
|
|
744
|
+
const canvasOptions = options.canvasOptions || {};
|
|
745
|
+
const minScale = Number((_c = canvasOptions.minScale) !== null && _c !== void 0 ? _c : CAD_BOUNDS_EPSILON);
|
|
746
|
+
const maxScale = Number((_d = canvasOptions.maxScale) !== null && _d !== void 0 ? _d : 1e9);
|
|
747
|
+
const unclampedScale = Math.min(width / boundsWidth, height / boundsHeight) * resolveCadFitPadding(options);
|
|
748
|
+
const nextScale = Math.min(Number.isFinite(maxScale) ? maxScale : 1e9, Math.max(Number.isFinite(minScale) ? minScale : CAD_BOUNDS_EPSILON, unclampedScale));
|
|
749
|
+
const nextView = {
|
|
750
|
+
centerX: (fitBounds.minX + fitBounds.maxX) / 2,
|
|
751
|
+
centerY: (fitBounds.minY + fitBounds.maxY) / 2,
|
|
752
|
+
scale: nextScale,
|
|
753
|
+
};
|
|
754
|
+
// Keep cad-viewer's zoom label at 100% for File Viewer's best-fit bounds.
|
|
755
|
+
renderer.fitScale = nextScale;
|
|
756
|
+
renderer.setViewState(nextView);
|
|
757
|
+
return true;
|
|
758
|
+
};
|
|
759
|
+
const fitToView = (source = 'user', action = 'zoom-reset') => {
|
|
760
|
+
fitViewActive = true;
|
|
761
|
+
applyCadFit();
|
|
762
|
+
cadZoomEmitter.emit();
|
|
763
|
+
syncState();
|
|
764
|
+
emitCadViewStateChange(action, source);
|
|
765
|
+
};
|
|
766
|
+
const applyCadFitRequest = (request) => {
|
|
767
|
+
fitViewActive = true;
|
|
768
|
+
const applied = applyCadFit();
|
|
769
|
+
if (!applied) {
|
|
770
|
+
return {
|
|
771
|
+
applied: false,
|
|
772
|
+
mode: request.mode,
|
|
773
|
+
resize: request.resize,
|
|
774
|
+
source: request.source,
|
|
775
|
+
reason: 'not-ready',
|
|
776
|
+
provider: 'view-state',
|
|
777
|
+
};
|
|
778
|
+
}
|
|
300
779
|
cadZoomEmitter.emit();
|
|
301
780
|
syncState();
|
|
302
|
-
emitCadViewStateChange('
|
|
781
|
+
const state = emitCadViewStateChange('fit', request.source);
|
|
782
|
+
return {
|
|
783
|
+
applied: true,
|
|
784
|
+
mode: request.mode,
|
|
785
|
+
resize: request.resize,
|
|
786
|
+
scale: state.scale,
|
|
787
|
+
source: request.source,
|
|
788
|
+
provider: 'view-state',
|
|
789
|
+
state,
|
|
790
|
+
};
|
|
303
791
|
};
|
|
304
792
|
const zoomIn = () => {
|
|
793
|
+
fitViewActive = false;
|
|
305
794
|
viewer === null || viewer === void 0 ? void 0 : viewer.zoomIn();
|
|
306
795
|
cadZoomEmitter.emit();
|
|
307
796
|
syncState();
|
|
308
797
|
emitCadViewStateChange('zoom-in', 'user');
|
|
309
798
|
};
|
|
310
799
|
const zoomOut = () => {
|
|
800
|
+
fitViewActive = false;
|
|
311
801
|
viewer === null || viewer === void 0 ? void 0 : viewer.zoomOut();
|
|
312
802
|
cadZoomEmitter.emit();
|
|
313
803
|
syncState();
|
|
@@ -326,6 +816,7 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
326
816
|
fitToView();
|
|
327
817
|
return getCadZoomState();
|
|
328
818
|
},
|
|
819
|
+
fit: applyCadFitRequest,
|
|
329
820
|
getState: getCadZoomState,
|
|
330
821
|
subscribe: cadZoomEmitter.subscribe,
|
|
331
822
|
});
|
|
@@ -340,9 +831,10 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
340
831
|
}
|
|
341
832
|
return getCadViewState();
|
|
342
833
|
},
|
|
834
|
+
fit: applyCadFitRequest,
|
|
343
835
|
subscribe: cadViewStateEmitter.subscribe,
|
|
344
836
|
});
|
|
345
|
-
fitButton.addEventListener('click', fitToView);
|
|
837
|
+
fitButton.addEventListener('click', () => fitToView());
|
|
346
838
|
zoomInButton.addEventListener('click', zoomIn);
|
|
347
839
|
zoomOutButton.addEventListener('click', zoomOut);
|
|
348
840
|
const createViewer = () => {
|
|
@@ -424,6 +916,7 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
424
916
|
renderStats = null;
|
|
425
917
|
viewState = null;
|
|
426
918
|
layers = [];
|
|
919
|
+
fitViewActive = true;
|
|
427
920
|
syncUi();
|
|
428
921
|
abortController === null || abortController === void 0 ? void 0 : abortController.abort();
|
|
429
922
|
const controller = new AbortController();
|
|
@@ -448,7 +941,7 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
448
941
|
status = 'ready';
|
|
449
942
|
syncUi();
|
|
450
943
|
queueMicrotask(() => {
|
|
451
|
-
|
|
944
|
+
applyCadFit();
|
|
452
945
|
cadZoomEmitter.emit();
|
|
453
946
|
emitCadViewStateChange('init', 'viewer');
|
|
454
947
|
syncState();
|
|
@@ -467,6 +960,15 @@ export default async function renderCad(buffer, target, type = 'dxf', context) {
|
|
|
467
960
|
if (typeof ResizeObserver !== 'undefined') {
|
|
468
961
|
resizeObserver = new ResizeObserver(() => {
|
|
469
962
|
viewer === null || viewer === void 0 ? void 0 : viewer.resize();
|
|
963
|
+
if (fitViewActive) {
|
|
964
|
+
requestAnimationFrame(() => {
|
|
965
|
+
if (!disposed && fitViewActive) {
|
|
966
|
+
applyCadFit();
|
|
967
|
+
cadZoomEmitter.emit();
|
|
968
|
+
syncState();
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
}
|
|
470
972
|
});
|
|
471
973
|
resizeObserver.observe(stage);
|
|
472
974
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-cad",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.18",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone CAD renderer plugin for Flyfish File Viewer powered by @flyfish-dev/cad-viewer.",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"LICENSE"
|
|
57
57
|
],
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@file-viewer/core": "^2.1.
|
|
59
|
+
"@file-viewer/core": "^2.1.18",
|
|
60
60
|
"@flyfish-dev/cad-viewer": "^0.6.4"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|