@design-core/core 0.0.6 → 50.1.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/.eslintrc.json +4 -0
- package/.github/workflows/DesignCoreQA.yml +3 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/designCore.js +1 -1
- package/core/dimensions/alignedDimension.js +12 -11
- package/core/dimensions/angularDimension.js +38 -37
- package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
- package/core/dimensions/diametricDimension.js +14 -13
- package/core/dimensions/dimension.js +62 -55
- package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
- package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
- package/core/dimensions/radialDimension.js +12 -11
- package/core/dimensions/rotatedDimension.js +20 -19
- package/core/entities/arc.js +123 -156
- package/core/entities/arctext.js +222 -184
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +346 -273
- package/core/entities/insert.js +68 -83
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/point.js +19 -19
- package/core/entities/polyline.js +7 -7
- package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +190 -201
- 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/boundingBox.js +11 -11
- package/core/lib/canvas.js +232 -168
- package/core/lib/clipboard.js +9 -3
- 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 +15 -14
- package/core/lib/dxf/dxfIterator.js +2 -2
- package/core/lib/entityManager.js +33 -26
- package/core/lib/inputManager.js +36 -12
- package/core/lib/intersect.js +25 -0
- package/core/lib/matrix.js +1 -1
- package/core/lib/patterns.js +9 -2
- 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 +28 -6
- package/core/lib/selectionManager.js +20 -20
- 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 +26 -7
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +60 -44
- package/core/tables/block.js +50 -81
- package/core/tables/dimStyle.js +5 -5
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/ltypeManager.js +1 -1
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tables/vport.js +2 -2
- package/core/tools/chamfer.js +117 -143
- package/core/tools/chamferFilletBase.js +138 -37
- package/core/tools/copy.js +11 -11
- package/core/tools/copybase.js +3 -3
- package/core/tools/copyclip.js +3 -3
- package/core/tools/cornerEntity.js +2 -2
- package/core/tools/cutclip.js +3 -3
- package/core/tools/explode.js +7 -7
- package/core/tools/extend.js +138 -25
- package/core/tools/fillet.js +92 -189
- package/core/tools/matchProp.js +3 -12
- package/core/tools/mirror.js +15 -14
- package/core/tools/move.js +8 -8
- package/core/tools/offset.js +31 -33
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/purge.js +10 -10
- package/core/tools/rotate.js +19 -16
- package/core/tools/scale.js +7 -7
- package/core/tools/trim.js +271 -29
- package/core/tools/zoom.js +1 -1
- package/cspell.json +321 -0
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +17 -10
- package/test/dimensions/angularDimension.test.js +13 -13
- package/test/dimensions/diametricDimension.test.js +15 -15
- package/test/dimensions/dimension.test.js +12 -12
- package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
- package/test/dimensions/linearDimensionBase.test.js +62 -0
- package/test/dimensions/radialDimension.test.js +15 -15
- package/test/dimensions/rotatedDimension.test.js +18 -11
- package/test/entities/arc.test.js +83 -86
- package/test/entities/arctext.test.js +184 -57
- package/test/entities/circle.test.js +68 -96
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +569 -96
- package/test/entities/insert.test.js +99 -4
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/point.test.js +3 -3
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +302 -77
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +5 -5
- 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/boundingBox.test.js +3 -3
- package/test/lib/canvas.test.js +744 -125
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +81 -20
- 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/scene.test.js +9 -9
- package/test/lib/selectionManager.test.js +3 -3
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +72 -130
- 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 +25 -25
- 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 +92 -13
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/tables/layerManager.test.js +2 -2
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +158 -6
- package/test/tools/chamfer.test.js +273 -51
- package/test/tools/chamferFilletBase.test.js +125 -36
- package/test/tools/copy.test.js +8 -8
- package/test/tools/copybase.test.js +2 -2
- package/test/tools/copyclip.test.js +2 -2
- package/test/tools/cornerEntity.test.js +8 -8
- package/test/tools/cutclip.test.js +2 -2
- package/test/tools/erase.test.js +6 -6
- package/test/tools/explode.test.js +5 -5
- package/test/tools/extend.test.js +373 -42
- package/test/tools/fillet.test.js +230 -49
- package/test/tools/matchProp.test.js +11 -11
- package/test/tools/mirror.test.js +39 -39
- package/test/tools/move.test.js +7 -7
- package/test/tools/offset.test.js +222 -55
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/purge.test.js +4 -4
- package/test/tools/rotate.test.js +39 -9
- package/test/tools/scale.test.js +33 -33
- package/test/tools/trim.test.js +579 -53
- package/test/tools/zoom.test.js +12 -12
- 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
|
}
|
|
@@ -186,7 +189,9 @@ export class Canvas {
|
|
|
186
189
|
*/
|
|
187
190
|
wheel(delta) {
|
|
188
191
|
if (DesignCore.Mouse.buttonTwoDown) return;
|
|
189
|
-
|
|
192
|
+
// Clamp to [-1, 1] so that high-resolution mice and track pads don't produce runaway jumps.
|
|
193
|
+
const clampedDelta = Math.max(-1, Math.min(1, delta));
|
|
194
|
+
const scale = Math.pow(1.2, clampedDelta);
|
|
190
195
|
if (scale < 1 && this.getScale() > this.minScaleFactor || scale > 1 && this.getScale() < this.maxScaleFactor) {
|
|
191
196
|
this.zoom(scale);
|
|
192
197
|
}
|
|
@@ -256,6 +261,77 @@ export class Canvas {
|
|
|
256
261
|
}
|
|
257
262
|
}
|
|
258
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Build a renderer transform matrix that fits a scene area onto a page.
|
|
266
|
+
* Both PdfRenderer and SvgRenderer expect d = +scale (no Y-flip).
|
|
267
|
+
* @param {object} options
|
|
268
|
+
* @param {BoundingBox} options.area - scene area to map onto the page
|
|
269
|
+
* @param {number} options.pageWidth - page width in renderer units (points)
|
|
270
|
+
* @param {number} options.pageHeight - page height in renderer units (points)
|
|
271
|
+
* @param {number|null} [options.plotScale=null] - numeric scale factor, or null to fit to page
|
|
272
|
+
* @param {number} [options.margin=40] - margin on each side in renderer units
|
|
273
|
+
* @return {{a: number, d: number, e: number, f: number}|null} matrix object, or null if area is empty
|
|
274
|
+
*/
|
|
275
|
+
buildExportMatrix({ area, pageWidth, pageHeight, plotScale = null, margin = 40 }) {
|
|
276
|
+
const sceneWidth = area.xMax - area.xMin;
|
|
277
|
+
const sceneHeight = area.yMax - area.yMin;
|
|
278
|
+
if (sceneWidth === 0 || sceneHeight === 0) return null;
|
|
279
|
+
|
|
280
|
+
const usableWidth = pageWidth - 2 * margin;
|
|
281
|
+
const usableHeight = pageHeight - 2 * margin;
|
|
282
|
+
|
|
283
|
+
const scale = plotScale === null ?
|
|
284
|
+
Math.min(usableWidth / sceneWidth, usableHeight / sceneHeight) :
|
|
285
|
+
plotScale;
|
|
286
|
+
|
|
287
|
+
const translateX = margin + (usableWidth - sceneWidth * scale) / 2 - area.xMin * scale;
|
|
288
|
+
const translateY = margin + (usableHeight - sceneHeight * scale) / 2 - area.yMin * scale;
|
|
289
|
+
|
|
290
|
+
return { a: scale, d: scale, e: translateX, f: translateY };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Export the visible scene entities to a renderer.
|
|
295
|
+
* Unlike paint(), this skips background, grid, preview, selection, and auxiliary passes.
|
|
296
|
+
* Builds and applies the export matrix from plotOptions before painting.
|
|
297
|
+
* @param {RendererBase} renderer
|
|
298
|
+
* @param {PlotOptions} plotOptions
|
|
299
|
+
* @return {boolean} false if no exportable area was found
|
|
300
|
+
*/
|
|
301
|
+
exportTo(renderer, plotOptions) {
|
|
302
|
+
let area;
|
|
303
|
+
if (plotOptions.plotArea === PlotOptions.Area.DISPLAY) {
|
|
304
|
+
const viewport = this.getSceneOffset();
|
|
305
|
+
area = new BoundingBox(new Point(viewport.xmin, viewport.ymin), new Point(viewport.xmax, viewport.ymax));
|
|
306
|
+
} else {
|
|
307
|
+
area = DesignCore.Scene.boundingBox();
|
|
308
|
+
}
|
|
309
|
+
if (!area) return false;
|
|
310
|
+
|
|
311
|
+
const matrix = this.buildExportMatrix({
|
|
312
|
+
area,
|
|
313
|
+
pageWidth: plotOptions.pageWidth,
|
|
314
|
+
pageHeight: plotOptions.pageHeight,
|
|
315
|
+
plotScale: plotOptions.plotScale,
|
|
316
|
+
});
|
|
317
|
+
if (!matrix) return false;
|
|
318
|
+
|
|
319
|
+
renderer.setTransform(matrix);
|
|
320
|
+
|
|
321
|
+
renderer.setBackgroundColour({ r: 255, g: 255, b: 255 });
|
|
322
|
+
renderer.setStyle(plotOptions.style);
|
|
323
|
+
|
|
324
|
+
const plotScale = matrix.a;
|
|
325
|
+
this.#paintEntities(
|
|
326
|
+
DesignCore.Scene.entities, renderer, null,
|
|
327
|
+
(entity) => DesignCore.LayerManager.getItemByName(entity.getProperty(Property.Names.LAYER))?.isPlottable,
|
|
328
|
+
null, null,
|
|
329
|
+
plotScale,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
259
335
|
/**
|
|
260
336
|
* Paint the canvas
|
|
261
337
|
* @param {object} context
|
|
@@ -278,151 +354,161 @@ export class Canvas {
|
|
|
278
354
|
this.flipped = true;
|
|
279
355
|
}
|
|
280
356
|
|
|
357
|
+
const renderer = new this.#renderer(context);
|
|
358
|
+
|
|
281
359
|
// set the transform for the context
|
|
282
360
|
// 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
|
-
}
|
|
361
|
+
renderer.setTransform(this.matrix);
|
|
362
|
+
renderer.setBackgroundColour(DesignCore.Settings.canvasbackgroundcolour);
|
|
290
363
|
|
|
291
364
|
const pos = new Point();
|
|
292
365
|
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
366
|
const scale = this.getScale();
|
|
316
367
|
|
|
317
|
-
// Paint the
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
368
|
+
// Paint the scene background
|
|
369
|
+
renderer.fillBackground(origin, width, height, scale);
|
|
370
|
+
// Paint the grid
|
|
371
|
+
this.paintGrid(renderer);
|
|
372
|
+
|
|
373
|
+
const bg = renderer.getBackgroundColour();
|
|
374
|
+
const hoverHaloColour = Colour.blend(DesignCore.Core.settings.accentcolour, bg, 0.5);
|
|
375
|
+
const selectionHaloColour = Colour.blend(DesignCore.Core.settings.accentcolour, bg, 0.25);
|
|
376
|
+
const selectionLineWidthDelta = 5;
|
|
377
|
+
|
|
378
|
+
// Paint the primary scene items (layer-visibility filtered).
|
|
379
|
+
// Hovered entity detected inline: renderer draws glow + entity in a single drawShape call.
|
|
380
|
+
this.#paintEntities(
|
|
381
|
+
DesignCore.Scene.entities, renderer, null,
|
|
382
|
+
(entity) => DesignCore.LayerManager.getItemByName(entity.getProperty(Property.Names.LAYER))?.isVisible,
|
|
383
|
+
DesignCore.Scene.hoverEntities,
|
|
384
|
+
{ colour: hoverHaloColour, lineWidthDelta: selectionLineWidthDelta },
|
|
385
|
+
);
|
|
332
386
|
|
|
333
387
|
// 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
|
-
}
|
|
388
|
+
this.#paintEntities(DesignCore.Scene.previewEntities, renderer);
|
|
341
389
|
|
|
342
|
-
// Paint the selected scene items
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
for (let k = 0; k < selectedItems.length; k++) {
|
|
346
|
-
const entity = selectedItems[k];
|
|
347
|
-
this.setContext(entity, context, undefined, scale);
|
|
348
|
-
entity.draw(context, scale);
|
|
349
|
-
}
|
|
390
|
+
// Paint the selected scene items: single pass — renderer handles glow + entity internally.
|
|
391
|
+
const selectedEntities = DesignCore.Scene.selectionManager.selectedEntities;
|
|
392
|
+
this.#paintEntities(selectedEntities, renderer, { colour: selectionHaloColour, lineWidthDelta: selectionLineWidthDelta });
|
|
350
393
|
|
|
351
394
|
// Paint the auxiliary scene items
|
|
352
395
|
// auxiliary items include things like the selection window, snap points etc
|
|
353
396
|
// these items have their own draw routine
|
|
354
|
-
|
|
397
|
+
renderer.setHighlight(false);
|
|
355
398
|
const auxCount = DesignCore.Scene.auxiliaryEntities.count();
|
|
356
399
|
for (let l = 0; l < auxCount; l++) {
|
|
357
|
-
|
|
400
|
+
renderer.save();
|
|
401
|
+
DesignCore.Scene.auxiliaryEntities.get(l).draw(renderer, scale);
|
|
402
|
+
renderer.restore();
|
|
358
403
|
}
|
|
404
|
+
}
|
|
359
405
|
|
|
360
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Recursively paint a single entity and any children it returns.
|
|
408
|
+
* Container entities (Insert, DimensionBase) return an array of child items from draw().
|
|
409
|
+
* Leaf entities return undefined. Renderer state is saved/restored around each entity.
|
|
410
|
+
* @param {Object} entity - entity to paint
|
|
411
|
+
* @param {Object} renderer - renderer instance
|
|
412
|
+
* @param {Object|undefined} parent - enclosing insert/dimension for ByBlock colour resolution
|
|
413
|
+
* @param {Object} overrides - rendering overrides passed to setContext
|
|
414
|
+
* @param {number} [scale] - forwarded to setContext
|
|
415
|
+
*/
|
|
416
|
+
#paintEntity(entity, renderer, parent, overrides, scale) {
|
|
417
|
+
renderer.save();
|
|
418
|
+
try {
|
|
419
|
+
this.setContext(entity, renderer, parent, overrides, scale);
|
|
420
|
+
const children = entity.draw(renderer);
|
|
421
|
+
if (children) {
|
|
422
|
+
for (const item of children) {
|
|
423
|
+
this.#paintEntity(item, renderer, entity, overrides, scale);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
Logging.instance.warn(`draw failed for entity type '${entity.type}': ${err}`);
|
|
428
|
+
} finally {
|
|
429
|
+
renderer.restore();
|
|
430
|
+
}
|
|
361
431
|
}
|
|
362
432
|
|
|
363
433
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* @param {
|
|
367
|
-
* @param {Object}
|
|
434
|
+
* Draw a collection of entities, calling setContext before each draw.
|
|
435
|
+
* Supports EntityManager instances (with .count()/.get()) and plain arrays.
|
|
436
|
+
* @param {EntityManager|Array} entities
|
|
437
|
+
* @param {Object} renderer - renderer instance
|
|
438
|
+
* @param {Object|null} [overrides] - optional rendering overrides passed to setContext for all entities
|
|
439
|
+
* @param {Object} [overrides.colour] - highlight glow colour (entity always draws in its natural colour)
|
|
440
|
+
* @param {number} [overrides.lineWidthDelta] - glow extension in pixels (divided by scale); added to renderer highlight delta
|
|
441
|
+
* @param {Function} [filter] - optional predicate; entity is skipped when it returns falsy
|
|
442
|
+
* @param {EntityManager|null} [hoverCollection] - when provided, entities found in this set receive hoverOverrides
|
|
443
|
+
* @param {Object|null} [hoverOverrides] - overrides applied to entities matched in hoverCollection
|
|
444
|
+
* @param {number} [scale] - forwarded to #paintEntity / setContext; defaults to screen zoom when omitted
|
|
368
445
|
*/
|
|
369
|
-
|
|
370
|
-
const
|
|
446
|
+
#paintEntities(entities, renderer, overrides = null, filter = undefined, hoverCollection = null, hoverOverrides = null, scale = undefined) {
|
|
447
|
+
const isManager = typeof entities.count === 'function';
|
|
448
|
+
const count = isManager ? entities.count() : entities.length;
|
|
449
|
+
for (let i = 0; i < count; i++) {
|
|
450
|
+
const entity = isManager ? entities.get(i) : entities[i];
|
|
451
|
+
if (filter && !filter(entity)) continue;
|
|
452
|
+
const isHovered = hoverCollection !== null && hoverCollection.indexOf(entity) !== -1;
|
|
453
|
+
this.#paintEntity(entity, renderer, undefined, isHovered ? hoverOverrides : (overrides ?? {}), scale);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Set the scene context on the renderer before drawing an entity.
|
|
459
|
+
* @param {Object} item - entity to draw
|
|
460
|
+
* @param {Object} renderer - renderer instance
|
|
461
|
+
* @param {Object} [block] - insert or dimension element for the current block, required for colour ByBlock
|
|
462
|
+
* @param {Object} [overrides] - optional rendering overrides
|
|
463
|
+
* @param {Object} [overrides.colour] - overrides all colour resolution (e.g. for halo/glow passes)
|
|
464
|
+
* @param {number} [overrides.lineWidthDelta] - additional width in pixels (divided by scale) added to the entity's line width
|
|
465
|
+
* @param {number} [scale] - scale divisor for line width and dash pattern; defaults to the current screen zoom
|
|
466
|
+
*/
|
|
467
|
+
setContext(item, renderer, block = undefined, overrides = {}, scale = this.getScale()) {
|
|
371
468
|
let colour = item.getDrawColour();
|
|
372
469
|
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
|
-
}
|
|
470
|
+
let lineWidth = (item.getProperty(Property.Names.LINEWIDTH) ?? 1) / scale;
|
|
471
|
+
const bg = renderer.getBackgroundColour() ?? { r: 0, g: 0, b: 0 };
|
|
383
472
|
|
|
384
|
-
if (block &&
|
|
385
|
-
|
|
386
|
-
if (item.entityColour.aci === 0) {
|
|
387
|
-
colour = block.getDrawColour();
|
|
388
|
-
}
|
|
473
|
+
if (block && item.entityColour.aci === 0) {
|
|
474
|
+
colour = block.getDrawColour();
|
|
389
475
|
}
|
|
390
|
-
|
|
391
476
|
// ACI 7: white on dark background, black on light background
|
|
392
|
-
if (this
|
|
393
|
-
const bg = DesignCore.Settings.canvasbackgroundcolour;
|
|
477
|
+
if (this.#isAci7(item, block)) {
|
|
394
478
|
colour = (bg.r + bg.g + bg.b) / 3 < 128 ? { r: 255, g: 255, b: 255 } : { r: 0, g: 0, b: 0 };
|
|
395
479
|
}
|
|
396
480
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
context.setDash(lineType.getPattern(scale), 1);
|
|
407
|
-
context.setLineWidth(lineWidth);
|
|
481
|
+
if (overrides.colour !== undefined) {
|
|
482
|
+
// Highlight pass: overrides.colour is the glow colour only — the entity
|
|
483
|
+
// always draws in its natural resolved colour on the normal pass.
|
|
484
|
+
// lineWidthDelta goes to the highlight delta so the glow extends beyond
|
|
485
|
+
// the entity line; the entity's own lineWidth is unchanged.
|
|
486
|
+
renderer.setHighlight(true, overrides.colour, (overrides.lineWidthDelta ?? 0) / scale);
|
|
487
|
+
} else {
|
|
488
|
+
renderer.setHighlight(false);
|
|
489
|
+
lineWidth += (overrides.lineWidthDelta ?? 0) / scale;
|
|
408
490
|
}
|
|
491
|
+
|
|
492
|
+
renderer.setColour(colour);
|
|
493
|
+
renderer.setLineWidth(lineWidth);
|
|
494
|
+
renderer.setDash(lineType.getPattern(scale), 0);
|
|
409
495
|
}
|
|
410
496
|
|
|
411
497
|
/**
|
|
412
498
|
* Resolve whether the effective draw colour for an item is ACI 7.
|
|
413
499
|
* Follows ByLayer and ByBlock indirection.
|
|
414
|
-
* @param {
|
|
415
|
-
* @param {
|
|
500
|
+
* @param {Object} item - entity to check
|
|
501
|
+
* @param {Object} [block] - enclosing insert/dimension, or undefined
|
|
416
502
|
* @return {boolean}
|
|
417
503
|
*/
|
|
418
504
|
#isAci7(item, block) {
|
|
419
505
|
let colour;
|
|
420
506
|
if (item.entityColour.byBlock && block) {
|
|
421
507
|
colour = block.entityColour.byLayer ?
|
|
422
|
-
DesignCore.LayerManager.getItemByName(block.
|
|
508
|
+
DesignCore.LayerManager.getItemByName(block.getProperty(Property.Names.LAYER))?.layerColour :
|
|
423
509
|
block.entityColour;
|
|
424
510
|
} else if (item.entityColour.byLayer) {
|
|
425
|
-
colour = DesignCore.LayerManager.getItemByName(item.
|
|
511
|
+
colour = DesignCore.LayerManager.getItemByName(item.getProperty(Property.Names.LAYER))?.layerColour;
|
|
426
512
|
} else {
|
|
427
513
|
colour = item.entityColour;
|
|
428
514
|
}
|
|
@@ -431,23 +517,18 @@ export class Canvas {
|
|
|
431
517
|
|
|
432
518
|
/**
|
|
433
519
|
* Paint the background grid
|
|
434
|
-
* @param {object}
|
|
520
|
+
* @param {object} renderer
|
|
435
521
|
*/
|
|
436
|
-
paintGrid(
|
|
522
|
+
paintGrid(renderer) {
|
|
437
523
|
const scale = this.getScale();
|
|
438
524
|
|
|
439
525
|
// TODO: Move grid linewidth to settings?
|
|
440
526
|
let lineWidth = 0.75;
|
|
441
527
|
|
|
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
|
-
}
|
|
528
|
+
renderer.setColour(DesignCore.Settings.gridcolour);
|
|
529
|
+
renderer.setLineWidth(lineWidth / scale);
|
|
530
|
+
renderer.setDash([], 0);
|
|
531
|
+
renderer.setHighlight(false);
|
|
451
532
|
|
|
452
533
|
const extents = this.getSceneOffset();
|
|
453
534
|
|
|
@@ -457,13 +538,11 @@ export class Canvas {
|
|
|
457
538
|
const ygridmax = extents.ymax;
|
|
458
539
|
|
|
459
540
|
// Draw major gridlines through origin
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
context.lineTo(0, ygridmin);
|
|
466
|
-
context.stroke();
|
|
541
|
+
renderer.drawSegments([
|
|
542
|
+
{ x1: xgridmin, y1: 0, x2: xgridmax, y2: 0 },
|
|
543
|
+
{ x1: 0, y1: 0, x2: 0, y2: ygridmax },
|
|
544
|
+
{ x1: 0, y1: 0, x2: 0, y2: ygridmin },
|
|
545
|
+
], []);
|
|
467
546
|
|
|
468
547
|
// only draw the grid if within scale limits
|
|
469
548
|
if (scale < this.minScaleFactor || scale > this.maxScaleFactor) {
|
|
@@ -473,50 +552,35 @@ export class Canvas {
|
|
|
473
552
|
if (DesignCore.Settings['drawgrid']) {
|
|
474
553
|
// set a feint linewidth for the grid
|
|
475
554
|
lineWidth = lineWidth * 0.25;
|
|
555
|
+
renderer.setLineWidth(lineWidth / scale);
|
|
476
556
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
557
|
+
// Target ~60px between grid lines; snap to nearest 1/2/5 × 10^n interval
|
|
558
|
+
const targetPx = 60;
|
|
559
|
+
const rawInterval = targetPx / scale;
|
|
560
|
+
const mag = Math.pow(10, Math.floor(Math.log10(rawInterval)));
|
|
561
|
+
const norm = rawInterval / mag;
|
|
562
|
+
const gridInterval = mag * (norm < 2 ? 1 : norm < 5 ? 2 : 5);
|
|
483
563
|
|
|
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
|
-
}
|
|
564
|
+
const gridSegments = [];
|
|
497
565
|
|
|
498
566
|
// Draw positive minor X gridlines
|
|
499
567
|
for (let i = 0; i < xgridmax; i = i + gridInterval) {
|
|
500
|
-
|
|
501
|
-
context.lineTo(i, ygridmax);
|
|
568
|
+
gridSegments.push({ x1: i, y1: ygridmin, x2: i, y2: ygridmax });
|
|
502
569
|
}
|
|
503
570
|
// Draw negative minor X gridlines
|
|
504
571
|
for (let i = 0; i > xgridmin; i = i - gridInterval) {
|
|
505
|
-
|
|
506
|
-
context.lineTo(i, ygridmax);
|
|
572
|
+
gridSegments.push({ x1: i, y1: ygridmin, x2: i, y2: ygridmax });
|
|
507
573
|
}
|
|
508
574
|
// Draw positive minor Y gridlines
|
|
509
575
|
for (let i = 0; i < ygridmax; i = i + gridInterval) {
|
|
510
|
-
|
|
511
|
-
context.lineTo(xgridmax, i);
|
|
576
|
+
gridSegments.push({ x1: xgridmin, y1: i, x2: xgridmax, y2: i });
|
|
512
577
|
}
|
|
513
578
|
// Draw negative minor Y gridlines
|
|
514
579
|
for (let i = 0; i > ygridmin; i = i - gridInterval) {
|
|
515
|
-
|
|
516
|
-
context.lineTo(xgridmax, i);
|
|
580
|
+
gridSegments.push({ x1: xgridmin, y1: i, x2: xgridmax, y2: i });
|
|
517
581
|
}
|
|
518
582
|
|
|
519
|
-
|
|
583
|
+
renderer.drawSegments(gridSegments, []);
|
|
520
584
|
}
|
|
521
585
|
}
|
|
522
586
|
|
package/core/lib/clipboard.js
CHANGED
|
@@ -69,7 +69,7 @@ export class Clipboard {
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// copy the
|
|
72
|
+
// copy the entity to avoid mutation of originals
|
|
73
73
|
this.#Entities = entities.map((entity) => Utils.cloneObject(entity));
|
|
74
74
|
// set basepoint to bottom left of selection bounding box
|
|
75
75
|
const bbox = BoundingBox.fromEntities(entities);
|
|
@@ -152,7 +152,7 @@ export class Clipboard {
|
|
|
152
152
|
const clipboardData = { basePoint: undefined, Entities: undefined };
|
|
153
153
|
const jsonEntities = [];
|
|
154
154
|
|
|
155
|
-
// convert each entity to a simple object - this allows non-
|
|
155
|
+
// convert each entity to a simple object - this allows non-enumerable properties (like points) to be copied
|
|
156
156
|
for (const ent of this.#Entities) {
|
|
157
157
|
const jsonEntity = this.#simplify(ent);
|
|
158
158
|
jsonEntities.push(jsonEntity);
|
|
@@ -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
|
/**
|