@design-core/core 0.0.5 → 0.0.7
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/.eslintrc.json +4 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +33 -284
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +36 -13
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +27 -5
- package/core/lib/selectionManager.js +2 -2
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +129 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +3 -3
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +296 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
package/core/lib/canvas.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { Matrix } from './matrix.js';
|
|
2
|
-
import {
|
|
2
|
+
import { Colour } from './colour.js';
|
|
3
|
+
import { BoundingBox } from './boundingBox.js';
|
|
3
4
|
import { Point } from '../entities/point.js';
|
|
4
5
|
import { Input } from './input.js';
|
|
6
|
+
import { Logging } from './logging.js';
|
|
7
|
+
import { PlotOptions } from './plotOptions.js';
|
|
8
|
+
// import { RendererBase } from './renderers/rendererBase.js';
|
|
5
9
|
|
|
6
10
|
import { DesignCore } from '../designCore.js';
|
|
11
|
+
import { Property } from '../properties/property.js';
|
|
7
12
|
|
|
8
13
|
/** Canvas Class */
|
|
9
14
|
export class Canvas {
|
|
10
15
|
#panCursorTimeout;
|
|
11
16
|
#baseCursorState = 'DEFAULT';
|
|
17
|
+
#lastPanCanvasPoint = new Point();
|
|
18
|
+
#renderer = null;
|
|
12
19
|
|
|
13
20
|
/** Create Canvas */
|
|
14
21
|
constructor() {
|
|
@@ -21,19 +28,8 @@ export class Canvas {
|
|
|
21
28
|
this.width = 1;
|
|
22
29
|
this.height = 1;
|
|
23
30
|
|
|
24
|
-
this.panDelta = new Point();
|
|
25
|
-
this.lastDelta = new Point();
|
|
26
31
|
this.flipped = false;
|
|
27
32
|
|
|
28
|
-
this.paintState;
|
|
29
|
-
|
|
30
|
-
this.paintStates = {
|
|
31
|
-
ENTITIES: 'ENTITIES',
|
|
32
|
-
TEMPORARY: 'TEMPORARY',
|
|
33
|
-
SELECTED: 'SELECTED',
|
|
34
|
-
AUXILLARY: 'AUXILLARY',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
33
|
this.cursorStates = Input.Cursor;
|
|
38
34
|
|
|
39
35
|
this.cursorState = Input.Cursor.DEFAULT;
|
|
@@ -62,6 +58,15 @@ export class Canvas {
|
|
|
62
58
|
this.externalPaintCallbackFunction = callback;
|
|
63
59
|
}
|
|
64
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Set the renderer class to use for entity drawing.
|
|
63
|
+
* Must be called once during UI initialisation before the first paint.
|
|
64
|
+
* @param {Function} RendererClass - CanvasRenderer or CairoRenderer class
|
|
65
|
+
*/
|
|
66
|
+
setRenderer(RendererClass) {
|
|
67
|
+
this.#renderer = RendererClass;
|
|
68
|
+
}
|
|
69
|
+
|
|
65
70
|
/**
|
|
66
71
|
* Set external cursor callback
|
|
67
72
|
* Called when the cursor state changes.
|
|
@@ -116,8 +121,10 @@ export class Canvas {
|
|
|
116
121
|
mouseDown(button) {
|
|
117
122
|
switch (button) {
|
|
118
123
|
case 0: // left button
|
|
124
|
+
this.#lastPanCanvasPoint = DesignCore.Mouse.pointOnCanvas();
|
|
119
125
|
break;
|
|
120
126
|
case 1: // middle button
|
|
127
|
+
this.#lastPanCanvasPoint = DesignCore.Mouse.pointOnCanvas();
|
|
121
128
|
clearTimeout(this.#panCursorTimeout);
|
|
122
129
|
this.#panCursorTimeout = setTimeout(() => this.#setCursor(Input.Cursor.GRABBING), 250);
|
|
123
130
|
break;
|
|
@@ -138,7 +145,6 @@ export class Canvas {
|
|
|
138
145
|
break;
|
|
139
146
|
case 1: // middle button
|
|
140
147
|
clearTimeout(this.#panCursorTimeout);
|
|
141
|
-
this.lastDelta = new Point();
|
|
142
148
|
this.requestPaint();
|
|
143
149
|
this.#setCursor(this.#baseCursorState);
|
|
144
150
|
break;
|
|
@@ -169,13 +175,10 @@ export class Canvas {
|
|
|
169
175
|
* Pan the canvas
|
|
170
176
|
*/
|
|
171
177
|
pan() {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// set the last delta value
|
|
177
|
-
this.lastDelta = this.panDelta;
|
|
178
|
-
// add translation to the matrix object
|
|
178
|
+
const current = DesignCore.Mouse.pointOnCanvas();
|
|
179
|
+
const delta = DesignCore.Mouse.transformToScene(current)
|
|
180
|
+
.subtract(DesignCore.Mouse.transformToScene(this.#lastPanCanvasPoint));
|
|
181
|
+
this.#lastPanCanvasPoint = current;
|
|
179
182
|
this.matrix.translate(delta.x, delta.y);
|
|
180
183
|
this.requestPaint();
|
|
181
184
|
}
|
|
@@ -256,6 +259,77 @@ export class Canvas {
|
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Build a renderer transform matrix that fits a scene area onto a page.
|
|
264
|
+
* Both PdfRenderer and SvgRenderer expect d = +scale (no Y-flip).
|
|
265
|
+
* @param {object} options
|
|
266
|
+
* @param {BoundingBox} options.area - scene area to map onto the page
|
|
267
|
+
* @param {number} options.pageWidth - page width in renderer units (points)
|
|
268
|
+
* @param {number} options.pageHeight - page height in renderer units (points)
|
|
269
|
+
* @param {number|null} [options.plotScale=null] - numeric scale factor, or null to fit to page
|
|
270
|
+
* @param {number} [options.margin=40] - margin on each side in renderer units
|
|
271
|
+
* @return {{a: number, d: number, e: number, f: number}|null} matrix object, or null if area is empty
|
|
272
|
+
*/
|
|
273
|
+
buildExportMatrix({ area, pageWidth, pageHeight, plotScale = null, margin = 40 }) {
|
|
274
|
+
const sceneWidth = area.xMax - area.xMin;
|
|
275
|
+
const sceneHeight = area.yMax - area.yMin;
|
|
276
|
+
if (sceneWidth === 0 || sceneHeight === 0) return null;
|
|
277
|
+
|
|
278
|
+
const usableWidth = pageWidth - 2 * margin;
|
|
279
|
+
const usableHeight = pageHeight - 2 * margin;
|
|
280
|
+
|
|
281
|
+
const scale = plotScale === null ?
|
|
282
|
+
Math.min(usableWidth / sceneWidth, usableHeight / sceneHeight) :
|
|
283
|
+
plotScale;
|
|
284
|
+
|
|
285
|
+
const translateX = margin + (usableWidth - sceneWidth * scale) / 2 - area.xMin * scale;
|
|
286
|
+
const translateY = margin + (usableHeight - sceneHeight * scale) / 2 - area.yMin * scale;
|
|
287
|
+
|
|
288
|
+
return { a: scale, d: scale, e: translateX, f: translateY };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Export the visible scene entities to a renderer.
|
|
293
|
+
* Unlike paint(), this skips background, grid, preview, selection, and auxiliary passes.
|
|
294
|
+
* Builds and applies the export matrix from plotOptions before painting.
|
|
295
|
+
* @param {RendererBase} renderer
|
|
296
|
+
* @param {PlotOptions} plotOptions
|
|
297
|
+
* @return {boolean} false if no exportable area was found
|
|
298
|
+
*/
|
|
299
|
+
exportTo(renderer, plotOptions) {
|
|
300
|
+
let area;
|
|
301
|
+
if (plotOptions.plotArea === PlotOptions.Area.DISPLAY) {
|
|
302
|
+
const viewport = this.getSceneOffset();
|
|
303
|
+
area = new BoundingBox(new Point(viewport.xmin, viewport.ymin), new Point(viewport.xmax, viewport.ymax));
|
|
304
|
+
} else {
|
|
305
|
+
area = DesignCore.Scene.boundingBox();
|
|
306
|
+
}
|
|
307
|
+
if (!area) return false;
|
|
308
|
+
|
|
309
|
+
const matrix = this.buildExportMatrix({
|
|
310
|
+
area,
|
|
311
|
+
pageWidth: plotOptions.pageWidth,
|
|
312
|
+
pageHeight: plotOptions.pageHeight,
|
|
313
|
+
plotScale: plotOptions.plotScale,
|
|
314
|
+
});
|
|
315
|
+
if (!matrix) return false;
|
|
316
|
+
|
|
317
|
+
renderer.setTransform(matrix);
|
|
318
|
+
|
|
319
|
+
renderer.setBackgroundColour({ r: 255, g: 255, b: 255 });
|
|
320
|
+
renderer.setStyle(plotOptions.style);
|
|
321
|
+
|
|
322
|
+
const plotScale = matrix.a;
|
|
323
|
+
this.#paintEntities(
|
|
324
|
+
DesignCore.Scene.entities, renderer, null,
|
|
325
|
+
(entity) => DesignCore.LayerManager.getItemByName(entity.getProperty(Property.Names.LAYER))?.isPlottable,
|
|
326
|
+
null, null,
|
|
327
|
+
plotScale,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
|
|
259
333
|
/**
|
|
260
334
|
* Paint the canvas
|
|
261
335
|
* @param {object} context
|
|
@@ -278,151 +352,161 @@ export class Canvas {
|
|
|
278
352
|
this.flipped = true;
|
|
279
353
|
}
|
|
280
354
|
|
|
355
|
+
const renderer = new this.#renderer(context);
|
|
356
|
+
|
|
281
357
|
// set the transform for the context
|
|
282
358
|
// this will set all the pan and zoom actions
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
} catch {
|
|
286
|
-
// context.setMatrix(this.matrix)
|
|
287
|
-
context.translate(this.matrix.e, this.matrix.f);
|
|
288
|
-
context.scale(this.matrix.a, this.matrix.d);
|
|
289
|
-
}
|
|
359
|
+
renderer.setTransform(this.matrix);
|
|
360
|
+
renderer.setBackgroundColour(DesignCore.Settings.canvasbackgroundcolour);
|
|
290
361
|
|
|
291
362
|
const pos = new Point();
|
|
292
363
|
const origin = DesignCore.Mouse.transformToScene(pos);
|
|
293
|
-
|
|
294
|
-
// Paint the scene background
|
|
295
|
-
try {// HTML
|
|
296
|
-
// this.clear()
|
|
297
|
-
context.fillStyle = Colours.rgbToString(DesignCore.Settings.canvasbackgroundcolour);
|
|
298
|
-
context.fillRect(origin.x, origin.y, width / this.getScale(), height / this.getScale());
|
|
299
|
-
// context.globalAlpha = this.cvs.alpha
|
|
300
|
-
} catch { // Cairo
|
|
301
|
-
const rgbColour = Colours.rgbToScaledRGB(DesignCore.Settings.canvasbackgroundcolour);
|
|
302
|
-
context.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
|
|
303
|
-
const scaled = new Point(width, height);
|
|
304
|
-
const sc = DesignCore.Mouse.transformToScene(scaled);
|
|
305
|
-
context.moveTo(origin.x, origin.y);
|
|
306
|
-
context.lineTo(origin.x, sc.y);
|
|
307
|
-
context.lineTo(sc.x, sc.y);
|
|
308
|
-
context.lineTo(sc.x, origin.y);
|
|
309
|
-
context.lineTo(origin.x, origin.y);
|
|
310
|
-
context.fill();
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
this.paintGrid(context, width, height);
|
|
314
|
-
|
|
315
364
|
const scale = this.getScale();
|
|
316
365
|
|
|
317
|
-
// Paint the
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
366
|
+
// Paint the scene background
|
|
367
|
+
renderer.fillBackground(origin, width, height, scale);
|
|
368
|
+
// Paint the grid
|
|
369
|
+
this.paintGrid(renderer);
|
|
370
|
+
|
|
371
|
+
const bg = renderer.getBackgroundColour();
|
|
372
|
+
const hoverHaloColour = Colour.blend(DesignCore.Core.settings.accentcolour, bg, 0.5);
|
|
373
|
+
const selectionHaloColour = Colour.blend(DesignCore.Core.settings.accentcolour, bg, 0.25);
|
|
374
|
+
const selectionLineWidthDelta = 5;
|
|
375
|
+
|
|
376
|
+
// Paint the primary scene items (layer-visibility filtered).
|
|
377
|
+
// Hovered entity detected inline: renderer draws glow + entity in a single drawShape call.
|
|
378
|
+
this.#paintEntities(
|
|
379
|
+
DesignCore.Scene.entities, renderer, null,
|
|
380
|
+
(entity) => DesignCore.LayerManager.getItemByName(entity.getProperty(Property.Names.LAYER))?.isVisible,
|
|
381
|
+
DesignCore.Scene.hoverEntities,
|
|
382
|
+
{ colour: hoverHaloColour, lineWidthDelta: selectionLineWidthDelta },
|
|
383
|
+
);
|
|
332
384
|
|
|
333
385
|
// Paint the temporary scene items
|
|
334
|
-
this.
|
|
335
|
-
const tempCount = DesignCore.Scene.tempEntities.count();
|
|
336
|
-
for (let j = 0; j < tempCount; j++) {
|
|
337
|
-
const entity = DesignCore.Scene.tempEntities.get(j);
|
|
338
|
-
this.setContext(entity, context, undefined, scale);
|
|
339
|
-
entity.draw(context, scale);
|
|
340
|
-
}
|
|
386
|
+
this.#paintEntities(DesignCore.Scene.previewEntities, renderer);
|
|
341
387
|
|
|
342
|
-
// Paint the selected scene items
|
|
343
|
-
this.paintState = this.paintStates.SELECTED;
|
|
388
|
+
// Paint the selected scene items: single pass — renderer handles glow + entity internally.
|
|
344
389
|
const selectedItems = DesignCore.Scene.selectionManager.selectedItems;
|
|
345
|
-
|
|
346
|
-
const entity = selectedItems[k];
|
|
347
|
-
this.setContext(entity, context, undefined, scale);
|
|
348
|
-
entity.draw(context, scale);
|
|
349
|
-
}
|
|
390
|
+
this.#paintEntities(selectedItems, renderer, { colour: selectionHaloColour, lineWidthDelta: selectionLineWidthDelta });
|
|
350
391
|
|
|
351
392
|
// Paint the auxiliary scene items
|
|
352
393
|
// auxiliary items include things like the selection window, snap points etc
|
|
353
394
|
// these items have their own draw routine
|
|
354
|
-
|
|
395
|
+
renderer.setHighlight(false);
|
|
355
396
|
const auxCount = DesignCore.Scene.auxiliaryEntities.count();
|
|
356
397
|
for (let l = 0; l < auxCount; l++) {
|
|
357
|
-
|
|
398
|
+
renderer.save();
|
|
399
|
+
DesignCore.Scene.auxiliaryEntities.get(l).draw(renderer, scale);
|
|
400
|
+
renderer.restore();
|
|
358
401
|
}
|
|
402
|
+
}
|
|
359
403
|
|
|
360
|
-
|
|
404
|
+
/**
|
|
405
|
+
* Recursively paint a single entity and any children it returns.
|
|
406
|
+
* Container entities (Insert, BaseDimension) return an array of child items from draw().
|
|
407
|
+
* Leaf entities return undefined. Renderer state is saved/restored around each entity.
|
|
408
|
+
* @param {Object} entity - entity to paint
|
|
409
|
+
* @param {Object} renderer - renderer instance
|
|
410
|
+
* @param {Object|undefined} parent - enclosing insert/dimension for ByBlock colour resolution
|
|
411
|
+
* @param {Object} overrides - rendering overrides passed to setContext
|
|
412
|
+
* @param {number} [scale] - forwarded to setContext
|
|
413
|
+
*/
|
|
414
|
+
#paintEntity(entity, renderer, parent, overrides, scale) {
|
|
415
|
+
renderer.save();
|
|
416
|
+
try {
|
|
417
|
+
this.setContext(entity, renderer, parent, overrides, scale);
|
|
418
|
+
const children = entity.draw(renderer);
|
|
419
|
+
if (children) {
|
|
420
|
+
for (const item of children) {
|
|
421
|
+
this.#paintEntity(item, renderer, entity, overrides, scale);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
Logging.instance.warn(`draw failed for entity type '${entity.type}': ${err}`);
|
|
426
|
+
} finally {
|
|
427
|
+
renderer.restore();
|
|
428
|
+
}
|
|
361
429
|
}
|
|
362
430
|
|
|
363
431
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* @param {
|
|
367
|
-
* @param {Object}
|
|
432
|
+
* Draw a collection of entities, calling setContext before each draw.
|
|
433
|
+
* Supports EntityManager instances (with .count()/.get()) and plain arrays.
|
|
434
|
+
* @param {EntityManager|Array} entities
|
|
435
|
+
* @param {Object} renderer - renderer instance
|
|
436
|
+
* @param {Object|null} [overrides] - optional rendering overrides passed to setContext for all entities
|
|
437
|
+
* @param {Object} [overrides.colour] - highlight glow colour (entity always draws in its natural colour)
|
|
438
|
+
* @param {number} [overrides.lineWidthDelta] - glow extension in pixels (divided by scale); added to renderer highlight delta
|
|
439
|
+
* @param {Function} [filter] - optional predicate; entity is skipped when it returns falsy
|
|
440
|
+
* @param {EntityManager|null} [hoverCollection] - when provided, entities found in this set receive hoverOverrides
|
|
441
|
+
* @param {Object|null} [hoverOverrides] - overrides applied to entities matched in hoverCollection
|
|
442
|
+
* @param {number} [scale] - forwarded to #paintEntity / setContext; defaults to screen zoom when omitted
|
|
368
443
|
*/
|
|
369
|
-
|
|
370
|
-
const
|
|
444
|
+
#paintEntities(entities, renderer, overrides = null, filter = undefined, hoverCollection = null, hoverOverrides = null, scale = undefined) {
|
|
445
|
+
const isManager = typeof entities.count === 'function';
|
|
446
|
+
const count = isManager ? entities.count() : entities.length;
|
|
447
|
+
for (let i = 0; i < count; i++) {
|
|
448
|
+
const entity = isManager ? entities.get(i) : entities[i];
|
|
449
|
+
if (filter && !filter(entity)) continue;
|
|
450
|
+
const isHovered = hoverCollection !== null && hoverCollection.indexOf(entity) !== -1;
|
|
451
|
+
this.#paintEntity(entity, renderer, undefined, isHovered ? hoverOverrides : (overrides ?? {}), scale);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Set the scene context on the renderer before drawing an entity.
|
|
457
|
+
* @param {Object} item - entity to draw
|
|
458
|
+
* @param {Object} renderer - renderer instance
|
|
459
|
+
* @param {Object} [block] - insert or dimension element for the current block, required for colour ByBlock
|
|
460
|
+
* @param {Object} [overrides] - optional rendering overrides
|
|
461
|
+
* @param {Object} [overrides.colour] - overrides all colour resolution (e.g. for halo/glow passes)
|
|
462
|
+
* @param {number} [overrides.lineWidthDelta] - additional width in pixels (divided by scale) added to the entity's line width
|
|
463
|
+
* @param {number} [scale] - scale divisor for line width and dash pattern; defaults to the current screen zoom
|
|
464
|
+
*/
|
|
465
|
+
setContext(item, renderer, block = undefined, overrides = {}, scale = this.getScale()) {
|
|
371
466
|
let colour = item.getDrawColour();
|
|
372
467
|
const lineType = item.getLineType();
|
|
373
|
-
let lineWidth = item.
|
|
374
|
-
|
|
375
|
-
if (this.paintState === this.paintStates.SELECTED) {
|
|
376
|
-
// Set Context for selected items
|
|
377
|
-
colour = DesignCore.Core.settings.selecteditemscolour;
|
|
378
|
-
lineWidth = (item.lineWidth * 2) / scale;
|
|
379
|
-
} else if (this.paintState === this.paintStates.TEMPORARY) {
|
|
380
|
-
// Set context for temp items
|
|
381
|
-
lineWidth = (item.lineWidth * 2) / scale;
|
|
382
|
-
}
|
|
468
|
+
let lineWidth = (item.getProperty(Property.Names.LINEWIDTH) ?? 1) / scale;
|
|
469
|
+
const bg = renderer.getBackgroundColour() ?? { r: 0, g: 0, b: 0 };
|
|
383
470
|
|
|
384
|
-
if (block &&
|
|
385
|
-
|
|
386
|
-
if (item.entityColour.aci === 0) {
|
|
387
|
-
colour = block.getDrawColour();
|
|
388
|
-
}
|
|
471
|
+
if (block && item.entityColour.aci === 0) {
|
|
472
|
+
colour = block.getDrawColour();
|
|
389
473
|
}
|
|
390
|
-
|
|
391
474
|
// ACI 7: white on dark background, black on light background
|
|
392
|
-
if (this
|
|
393
|
-
const bg = DesignCore.Settings.canvasbackgroundcolour;
|
|
475
|
+
if (this.#isAci7(item, block)) {
|
|
394
476
|
colour = (bg.r + bg.g + bg.b) / 3 < 128 ? { r: 255, g: 255, b: 255 } : { r: 0, g: 0, b: 0 };
|
|
395
477
|
}
|
|
396
478
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
context.setDash(lineType.getPattern(scale), 1);
|
|
407
|
-
context.setLineWidth(lineWidth);
|
|
479
|
+
if (overrides.colour !== undefined) {
|
|
480
|
+
// Highlight pass: overrides.colour is the glow colour only — the entity
|
|
481
|
+
// always draws in its natural resolved colour on the normal pass.
|
|
482
|
+
// lineWidthDelta goes to the highlight delta so the glow extends beyond
|
|
483
|
+
// the entity line; the entity's own lineWidth is unchanged.
|
|
484
|
+
renderer.setHighlight(true, overrides.colour, (overrides.lineWidthDelta ?? 0) / scale);
|
|
485
|
+
} else {
|
|
486
|
+
renderer.setHighlight(false);
|
|
487
|
+
lineWidth += (overrides.lineWidthDelta ?? 0) / scale;
|
|
408
488
|
}
|
|
489
|
+
|
|
490
|
+
renderer.setColour(colour);
|
|
491
|
+
renderer.setLineWidth(lineWidth);
|
|
492
|
+
renderer.setDash(lineType.getPattern(scale), 0);
|
|
409
493
|
}
|
|
410
494
|
|
|
411
495
|
/**
|
|
412
496
|
* Resolve whether the effective draw colour for an item is ACI 7.
|
|
413
497
|
* Follows ByLayer and ByBlock indirection.
|
|
414
|
-
* @param {
|
|
415
|
-
* @param {
|
|
498
|
+
* @param {Object} item - entity to check
|
|
499
|
+
* @param {Object} [block] - enclosing insert/dimension, or undefined
|
|
416
500
|
* @return {boolean}
|
|
417
501
|
*/
|
|
418
502
|
#isAci7(item, block) {
|
|
419
503
|
let colour;
|
|
420
504
|
if (item.entityColour.byBlock && block) {
|
|
421
505
|
colour = block.entityColour.byLayer ?
|
|
422
|
-
DesignCore.LayerManager.getItemByName(block.
|
|
506
|
+
DesignCore.LayerManager.getItemByName(block.getProperty(Property.Names.LAYER))?.layerColour :
|
|
423
507
|
block.entityColour;
|
|
424
508
|
} else if (item.entityColour.byLayer) {
|
|
425
|
-
colour = DesignCore.LayerManager.getItemByName(item.
|
|
509
|
+
colour = DesignCore.LayerManager.getItemByName(item.getProperty(Property.Names.LAYER))?.layerColour;
|
|
426
510
|
} else {
|
|
427
511
|
colour = item.entityColour;
|
|
428
512
|
}
|
|
@@ -431,23 +515,18 @@ export class Canvas {
|
|
|
431
515
|
|
|
432
516
|
/**
|
|
433
517
|
* Paint the background grid
|
|
434
|
-
* @param {object}
|
|
518
|
+
* @param {object} renderer
|
|
435
519
|
*/
|
|
436
|
-
paintGrid(
|
|
520
|
+
paintGrid(renderer) {
|
|
437
521
|
const scale = this.getScale();
|
|
438
522
|
|
|
439
523
|
// TODO: Move grid linewidth to settings?
|
|
440
524
|
let lineWidth = 0.75;
|
|
441
525
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
} catch { // Cairo
|
|
447
|
-
context.setLineWidth(lineWidth / scale);
|
|
448
|
-
const rgbColour = Colours.rgbToScaledRGB(DesignCore.Settings.gridcolour);
|
|
449
|
-
context.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
|
|
450
|
-
}
|
|
526
|
+
renderer.setColour(DesignCore.Settings.gridcolour);
|
|
527
|
+
renderer.setLineWidth(lineWidth / scale);
|
|
528
|
+
renderer.setDash([], 0);
|
|
529
|
+
renderer.setHighlight(false);
|
|
451
530
|
|
|
452
531
|
const extents = this.getSceneOffset();
|
|
453
532
|
|
|
@@ -457,13 +536,11 @@ export class Canvas {
|
|
|
457
536
|
const ygridmax = extents.ymax;
|
|
458
537
|
|
|
459
538
|
// Draw major gridlines through origin
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
context.lineTo(0, ygridmin);
|
|
466
|
-
context.stroke();
|
|
539
|
+
renderer.drawSegments([
|
|
540
|
+
{ x1: xgridmin, y1: 0, x2: xgridmax, y2: 0 },
|
|
541
|
+
{ x1: 0, y1: 0, x2: 0, y2: ygridmax },
|
|
542
|
+
{ x1: 0, y1: 0, x2: 0, y2: ygridmin },
|
|
543
|
+
], []);
|
|
467
544
|
|
|
468
545
|
// only draw the grid if within scale limits
|
|
469
546
|
if (scale < this.minScaleFactor || scale > this.maxScaleFactor) {
|
|
@@ -473,50 +550,35 @@ export class Canvas {
|
|
|
473
550
|
if (DesignCore.Settings['drawgrid']) {
|
|
474
551
|
// set a feint linewidth for the grid
|
|
475
552
|
lineWidth = lineWidth * 0.25;
|
|
553
|
+
renderer.setLineWidth(lineWidth / scale);
|
|
476
554
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
555
|
+
// Target ~60px between grid lines; snap to nearest 1/2/5 × 10^n interval
|
|
556
|
+
const targetPx = 60;
|
|
557
|
+
const rawInterval = targetPx / scale;
|
|
558
|
+
const mag = Math.pow(10, Math.floor(Math.log10(rawInterval)));
|
|
559
|
+
const norm = rawInterval / mag;
|
|
560
|
+
const gridInterval = mag * (norm < 2 ? 1 : norm < 5 ? 2 : 5);
|
|
483
561
|
|
|
484
|
-
|
|
485
|
-
let gridInterval = 100;
|
|
486
|
-
|
|
487
|
-
// define the grid spacing based on zoom level
|
|
488
|
-
if (scale > 50) {
|
|
489
|
-
gridInterval = 1;
|
|
490
|
-
} else if (scale > 5) {
|
|
491
|
-
gridInterval = 10;
|
|
492
|
-
} else if (scale < 0.6) {
|
|
493
|
-
gridInterval = 1000;
|
|
494
|
-
} else {
|
|
495
|
-
gridInterval = 100;
|
|
496
|
-
}
|
|
562
|
+
const gridSegments = [];
|
|
497
563
|
|
|
498
564
|
// Draw positive minor X gridlines
|
|
499
565
|
for (let i = 0; i < xgridmax; i = i + gridInterval) {
|
|
500
|
-
|
|
501
|
-
context.lineTo(i, ygridmax);
|
|
566
|
+
gridSegments.push({ x1: i, y1: ygridmin, x2: i, y2: ygridmax });
|
|
502
567
|
}
|
|
503
568
|
// Draw negative minor X gridlines
|
|
504
569
|
for (let i = 0; i > xgridmin; i = i - gridInterval) {
|
|
505
|
-
|
|
506
|
-
context.lineTo(i, ygridmax);
|
|
570
|
+
gridSegments.push({ x1: i, y1: ygridmin, x2: i, y2: ygridmax });
|
|
507
571
|
}
|
|
508
572
|
// Draw positive minor Y gridlines
|
|
509
573
|
for (let i = 0; i < ygridmax; i = i + gridInterval) {
|
|
510
|
-
|
|
511
|
-
context.lineTo(xgridmax, i);
|
|
574
|
+
gridSegments.push({ x1: xgridmin, y1: i, x2: xgridmax, y2: i });
|
|
512
575
|
}
|
|
513
576
|
// Draw negative minor Y gridlines
|
|
514
577
|
for (let i = 0; i > ygridmin; i = i - gridInterval) {
|
|
515
|
-
|
|
516
|
-
context.lineTo(xgridmax, i);
|
|
578
|
+
gridSegments.push({ x1: xgridmin, y1: i, x2: xgridmax, y2: i });
|
|
517
579
|
}
|
|
518
580
|
|
|
519
|
-
|
|
581
|
+
renderer.drawSegments(gridSegments, []);
|
|
520
582
|
}
|
|
521
583
|
}
|
|
522
584
|
|
package/core/lib/clipboard.js
CHANGED
|
@@ -171,7 +171,13 @@ export class Clipboard {
|
|
|
171
171
|
* @return {Object} - simplified object
|
|
172
172
|
*/
|
|
173
173
|
#simplify(original) {
|
|
174
|
-
|
|
174
|
+
const names = Object.getOwnPropertyNames(original);
|
|
175
|
+
// 'points' is now stored in EntityProperties and accessed via a prototype accessor,
|
|
176
|
+
// so it's not an own property — include it explicitly when it's an accessible array.
|
|
177
|
+
if (!names.includes('points') && Array.isArray(original.points)) {
|
|
178
|
+
names.push('points');
|
|
179
|
+
}
|
|
180
|
+
return names.reduce((simplified, property) => {
|
|
175
181
|
const value = original[property];
|
|
176
182
|
if (Array.isArray(value)) {
|
|
177
183
|
simplified[property] = original[property].map((item) => this.#simplify(item));
|
package/core/lib/colour.js
CHANGED
|
@@ -63,6 +63,22 @@ export class Colour {
|
|
|
63
63
|
this.rgb = rgb;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Blend two RGB colours together.
|
|
69
|
+
* @param {{r:number,g:number,b:number}} colourA
|
|
70
|
+
* @param {{r:number,g:number,b:number}} colourB
|
|
71
|
+
* @param {number} factor - 0 returns colourA, 1 returns colourB
|
|
72
|
+
* @return {{r:number,g:number,b:number}}
|
|
73
|
+
*/
|
|
74
|
+
static blend(colourA, colourB, factor) {
|
|
75
|
+
const inverseFactor = 1 - factor;
|
|
76
|
+
return {
|
|
77
|
+
r: Math.round(colourA.r * inverseFactor + colourB.r * factor),
|
|
78
|
+
g: Math.round(colourA.g * inverseFactor + colourB.g * factor),
|
|
79
|
+
b: Math.round(colourA.b * inverseFactor + colourB.b * factor),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
/**
|
package/core/lib/commandLine.js
CHANGED
|
@@ -56,12 +56,7 @@ export class CommandLine {
|
|
|
56
56
|
this.updateCallbackFunction(this.cmdLine);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
// TODO: This should call a common function that is currently called mouseMove in the scene class
|
|
61
|
-
DesignCore.Scene.tempEntities.clear();
|
|
62
|
-
DesignCore.Scene.inputManager.activeCommand.preview();
|
|
63
|
-
DesignCore.Canvas.requestPaint();
|
|
64
|
-
}
|
|
59
|
+
DesignCore.Scene.inputManager.refreshPreview();
|
|
65
60
|
}
|
|
66
61
|
|
|
67
62
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Line } from '../entities/line.js';
|
|
2
|
+
import { Entity } from '../entities/entity.js';
|
|
2
3
|
import { Polyline } from '../entities/polyline.js';
|
|
3
4
|
import { Lwpolyline } from '../entities/lwpolyline.js';
|
|
4
5
|
import { Circle } from '../entities/circle.js';
|
|
@@ -141,21 +142,30 @@ export class CommandManager {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
/**
|
|
144
|
-
* Create a new instance of type using data
|
|
145
|
+
* Create a new instance of type using data.
|
|
146
|
+
* When data is a plain object (DXF load path), applies a two-pass normalisation:
|
|
147
|
+
* 1. Entity.fromDxf() — resolves group codes shared by all entities (e.g. colour, true colour)
|
|
148
|
+
* 2. CommandClass.fromDxf() — resolves group codes specific to the entity type (e.g. geometry)
|
|
149
|
+
* When data is absent or already a canonical object (e.g. an entity instance), it is passed through unchanged.
|
|
145
150
|
* @param {string} type
|
|
146
|
-
* @param {
|
|
151
|
+
* @param {Object} data
|
|
147
152
|
* @return {Object} instance of type
|
|
148
153
|
*/
|
|
149
154
|
createNew(type, data) {
|
|
150
|
-
|
|
151
|
-
if (this.isCommand(type)) {
|
|
152
|
-
newItem = new classes[this.getCommand(type)](data);
|
|
153
|
-
} else {
|
|
155
|
+
if (!this.isCommand(type)) {
|
|
154
156
|
Logging.instance.warn(`${Strings.Message.UNKNOWNCOMMAND}: ${type}`);
|
|
155
157
|
return;
|
|
156
158
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
const CommandClass = classes[this.getCommand(type)];
|
|
160
|
+
// Skip DXF normalisation when data is absent or is already a canonical object (e.g. an entity instance)
|
|
161
|
+
if (!data || Object.getPrototypeOf(data) !== Object.prototype) {
|
|
162
|
+
return new CommandClass(data);
|
|
163
|
+
}
|
|
164
|
+
// Resolve group codes shared by all entities (colour, true colour, etc.)
|
|
165
|
+
const baseNormalised = Entity.fromDxf(data);
|
|
166
|
+
// Resolve group codes specific to this entity type (geometry, etc.)
|
|
167
|
+
const classNormalised = Object.prototype.hasOwnProperty.call(CommandClass, 'fromDxf') ? CommandClass.fromDxf(baseNormalised) : baseNormalised;
|
|
168
|
+
return new CommandClass(classNormalised);
|
|
159
169
|
};
|
|
160
170
|
|
|
161
171
|
/**
|