Skip to content

Commit 10ad915

Browse files
author
mmiscool
committed
improve 2D sheet erganimics to edit PMI views.
1 parent 8861b23 commit 10ad915

4 files changed

Lines changed: 139 additions & 8 deletions

File tree

src/UI/pmi/PMIViewsWidget.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const PMI_EXPORT_CAPTURE_WIDTH_PX = 2400;
1515
const PMI_EXPORT_CAPTURE_HEIGHT_PX = 1800;
1616
const DEFAULT_PMI_VIEW_TEXT_SIZE_PT = 12;
1717
const SVG_NS = 'http://www.w3.org/2000/svg';
18+
const CSS_COLOR_RE = /^#[0-9a-f]{3,8}$/i;
19+
const CSS_FUNCTION_COLOR_RE = /^(rgb|rgba|hsl|hsla)\([^)]+\)$/i;
20+
const CSS_NAMED_COLOR_RE = /^[a-zA-Z]+$/;
1821

1922
export class PMIViewsWidget {
2023
constructor(viewer, { readOnly = false } = {}) {
@@ -309,6 +312,21 @@ export class PMIViewsWidget {
309312
return this._isMonochromeExport(renderContext) && renderContext?.showCenterLines === true;
310313
}
311314

315+
_normalizeExportBackdropColor(color, fallback = null) {
316+
const text = String(color ?? '').trim();
317+
if (!text) return fallback;
318+
if (/^transparent$/i.test(text)) return null;
319+
if (CSS_COLOR_RE.test(text) || CSS_FUNCTION_COLOR_RE.test(text) || CSS_NAMED_COLOR_RE.test(text)) {
320+
return text;
321+
}
322+
return fallback;
323+
}
324+
325+
_getMonochromeLabelBackdropColor(renderContext = null) {
326+
if (!this._isMonochromeExport(renderContext)) return null;
327+
return this._normalizeExportBackdropColor(renderContext?.labelBackdropColor, null);
328+
}
329+
312330
_createTransparentImageDataUrl(width, height) {
313331
const canvas = document.createElement('canvas');
314332
canvas.width = Math.max(1, Number(width) || 1);
@@ -711,19 +729,20 @@ export class PMIViewsWidget {
711729
fontSize,
712730
layout,
713731
} = layoutData;
732+
const monochromeBackdrop = this._getMonochromeLabelBackdropColor(renderContext);
714733

715734
for (const entry of layout) {
716-
if (!isMonochrome) {
735+
if (!isMonochrome || monochromeBackdrop) {
717736
const rect = document.createElementNS(SVG_NS, 'rect');
718737
rect.setAttribute('x', entry.x.toFixed(3));
719738
rect.setAttribute('y', entry.y.toFixed(3));
720739
rect.setAttribute('rx', String(radius));
721740
rect.setAttribute('ry', String(radius));
722741
rect.setAttribute('width', entry.boxWidth.toFixed(3));
723742
rect.setAttribute('height', entry.boxHeight.toFixed(3));
724-
rect.setAttribute('fill', 'rgba(17,24,39,0.92)');
725-
rect.setAttribute('stroke', '#111827');
726-
rect.setAttribute('stroke-width', '1');
743+
rect.setAttribute('fill', isMonochrome ? monochromeBackdrop : 'rgba(17,24,39,0.92)');
744+
rect.setAttribute('stroke', isMonochrome ? 'none' : '#111827');
745+
rect.setAttribute('stroke-width', isMonochrome ? '0' : '1');
727746
labelGroup.appendChild(rect);
728747
}
729748

@@ -995,6 +1014,7 @@ export class PMIViewsWidget {
9951014
viewport,
9961015
renderMode,
9971016
showCenterLines,
1017+
labelBackdropColor: this._normalizeExportBackdropColor(options?.labelBackdropColor, null),
9981018
targetFrameWidthIn: Number(options?.targetFrameWidthIn) > 0 ? Number(options.targetFrameWidthIn) : null,
9991019
targetFrameHeightIn: Number(options?.targetFrameHeightIn) > 0 ? Number(options.targetFrameHeightIn) : null,
10001020
dispose: () => {
@@ -1459,6 +1479,7 @@ export class PMIViewsWidget {
14591479
hideViewCube = true,
14601480
renderMode = 'shaded',
14611481
showCenterLines = false,
1482+
labelBackdropColor = null,
14621483
targetFrameWidthIn = null,
14631484
targetFrameHeightIn = null,
14641485
} = {}) {
@@ -1470,6 +1491,7 @@ export class PMIViewsWidget {
14701491
const renderContext = this._createExportRenderContext(captureViewport, view, {
14711492
renderMode,
14721493
showCenterLines,
1494+
labelBackdropColor,
14731495
targetFrameWidthIn,
14741496
targetFrameHeightIn,
14751497
});
@@ -1609,8 +1631,7 @@ export class PMIViewsWidget {
16091631
this._applyView(v, { index: idx });
16101632
return;
16111633
}
1612-
this._enterEditMode(v, idx);
1613-
setTimeout(() => this._enterEditMode(v, idx), 200);
1634+
this.enterEditMode(v, idx);
16141635
});
16151636
row.appendChild(nameButton);
16161637

@@ -2275,8 +2296,12 @@ export class PMIViewsWidget {
22752296
}
22762297

22772298
const escape = (s) => this._escapeXML(String(s));
2299+
const monochromeBackdrop = this._getMonochromeLabelBackdropColor(renderContext);
22782300
const rects = isMonochrome
2279-
? ''
2301+
? (monochromeBackdrop
2302+
? layout.map(({ x, y, boxWidth, boxHeight }) =>
2303+
`<rect x="${x.toFixed(3)}" y="${y.toFixed(3)}" rx="${radius}" ry="${radius}" width="${boxWidth.toFixed(3)}" height="${boxHeight.toFixed(3)}" fill="${escape(monochromeBackdrop)}" stroke="none" stroke-width="0"/>`).join('')
2304+
: '')
22802305
: layout.map(({ x, y, boxWidth, boxHeight }) =>
22812306
`<rect x="${x.toFixed(3)}" y="${y.toFixed(3)}" rx="${radius}" ry="${radius}" width="${boxWidth.toFixed(3)}" height="${boxHeight.toFixed(3)}" fill="rgba(17,24,39,0.92)" stroke="#111827" stroke-width="1"/>`).join('');
22822307

@@ -2687,6 +2712,11 @@ export class PMIViewsWidget {
26872712
try { this.viewer.startPMIMode?.(view, index, this, { fromViewClick: true }); } catch {}
26882713
}
26892714

2715+
enterEditMode(view, index) {
2716+
this._enterEditMode(view, index);
2717+
setTimeout(() => this._enterEditMode(view, index), 200);
2718+
}
2719+
26902720
// --- Helpers: view settings ---
26912721
_isFaceObject(obj) {
26922722
return !!obj && (obj.type === 'FACE' || (obj.isMesh && typeof obj.userData?.faceName === 'string'));

src/UI/sheets/Sheet2DEditorWindow.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2826,6 +2826,10 @@ export class Sheet2DEditorWindow {
28262826
}
28272827

28282828
if (addedTableItems) this._appendContextMenuSeparator(menu);
2829+
if (singleSelection?.type === "pmiInset") {
2830+
this._appendContextMenuItem(menu, "Edit view", () => this._editPmiViewElement(singleSelection));
2831+
this._appendContextMenuSeparator(menu);
2832+
}
28292833
if (this._canGroupSelectedElements()) {
28302834
this._appendContextMenuItem(menu, "Group", () => this._groupSelectedElements());
28312835
}
@@ -2841,6 +2845,32 @@ export class Sheet2DEditorWindow {
28412845
this._appendContextMenuItem(menu, "Delete", () => this._deleteSelectedElement(), { danger: true });
28422846
}
28432847

2848+
_editPmiViewElement(element) {
2849+
if (!element || element.type !== "pmiInset") return false;
2850+
this._refreshPmiViews();
2851+
const view = this._resolvePmiViewForElement(element);
2852+
const viewIndex = this._resolvePmiViewIndexForElement(element, view);
2853+
const widget = this.viewer?.pmiViewsWidget || null;
2854+
const editView = typeof widget?.enterEditMode === "function"
2855+
? widget.enterEditMode.bind(widget)
2856+
: null;
2857+
if (!view || viewIndex < 0 || !editView) {
2858+
this._setStatus("PMI view unavailable");
2859+
return false;
2860+
}
2861+
2862+
const previousSheetId = this.sheetId;
2863+
this.close();
2864+
try {
2865+
editView(view, viewIndex);
2866+
return true;
2867+
} catch {
2868+
this.open(previousSheetId || null);
2869+
this._setStatus("Could not open PMI view");
2870+
return false;
2871+
}
2872+
}
2873+
28442874
_openPmiPicker() {
28452875
this._refreshPmiViews();
28462876
this._pmiPickerOpen = true;
@@ -6964,13 +6994,14 @@ export class Sheet2DEditorWindow {
69646994
_getPmiImageCaptureKey(element, view = this._resolvePmiViewForElement(element), viewIndex = this._resolvePmiViewIndexForElement(element, view)) {
69656995
const frame = this._getMediaFrameBox(element);
69666996
return [
6967-
"trim-v5",
6997+
"trim-v6",
69686998
this._getCurrentModelRevision(),
69696999
this._pmiViewRevision,
69707000
Number.isInteger(viewIndex) ? viewIndex : -1,
69717001
String(view?.viewName || view?.name || element?.pmiViewName || "PMI View"),
69727002
this._getPmiRenderMode(element),
69737003
this._getPmiShowCenterLines(element) ? "centerlines:on" : "centerlines:off",
7004+
`labelbg:${isTransparentColor(element?.fill) ? "transparent" : toCssColor(element?.fill, "#ffffff").toLowerCase()}`,
69747005
`frame:${toFiniteNumber(frame?.w, 0).toFixed(4)}x${toFiniteNumber(frame?.h, 0).toFixed(4)}`,
69757006
this._getPmiViewStateSignature(view),
69767007
].join(":");
@@ -6992,6 +7023,7 @@ export class Sheet2DEditorWindow {
69927023
.then(() => this._capturePmiViewImage(view, viewIndex, {
69937024
renderMode: this._getPmiRenderMode(element),
69947025
showCenterLines: this._getPmiShowCenterLines(element),
7026+
labelBackdropColor: element?.fill,
69957027
targetFrameWidthIn: this._getMediaFrameBox(element)?.w,
69967028
targetFrameHeightIn: this._getMediaFrameBox(element)?.h,
69977029
}))
@@ -7131,6 +7163,7 @@ export class Sheet2DEditorWindow {
71317163
const dataUrl = cached || await this._capturePmiViewImage(group.view, group.viewIndex, {
71327164
renderMode: this._getPmiRenderMode(group.elementSnapshot),
71337165
showCenterLines: this._getPmiShowCenterLines(group.elementSnapshot),
7166+
labelBackdropColor: group.elementSnapshot?.fill,
71347167
targetFrameWidthIn: this._getMediaFrameBox(group.elementSnapshot)?.w,
71357168
targetFrameHeightIn: this._getMediaFrameBox(group.elementSnapshot)?.h,
71367169
});
@@ -7189,6 +7222,7 @@ export class Sheet2DEditorWindow {
71897222
async _capturePmiViewImage(view, viewIndex, {
71907223
renderMode = "shaded",
71917224
showCenterLines = false,
7225+
labelBackdropColor = null,
71927226
targetFrameWidthIn = null,
71937227
targetFrameHeightIn = null,
71947228
} = {}) {
@@ -7200,6 +7234,7 @@ export class Sheet2DEditorWindow {
72007234
dataUrl = await widget.captureViewImageDataUrl(view, viewIndex, {
72017235
renderMode: normalizePmiRenderMode(renderMode, "shaded"),
72027236
showCenterLines: !!showCenterLines,
7237+
labelBackdropColor: String(labelBackdropColor ?? "").trim() || null,
72037238
targetFrameWidthIn: Number(targetFrameWidthIn) > 0 ? Number(targetFrameWidthIn) : null,
72047239
targetFrameHeightIn: Number(targetFrameHeightIn) > 0 ? Number(targetFrameHeightIn) : null,
72057240
});

src/tests/test_pmiViewsWidget.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as THREE from 'three';
2+
import { PMIViewsWidget } from '../UI/pmi/PMIViewsWidget.js';
3+
4+
function assert(condition, message) {
5+
if (!condition) {
6+
throw new Error(message || 'Assertion failed.');
7+
}
8+
}
9+
10+
export function test_pmi_monochrome_label_svg_uses_backdrop_color() {
11+
const widget = Object.create(PMIViewsWidget.prototype);
12+
widget.viewer = null;
13+
14+
const camera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.1, 100);
15+
camera.position.set(0, 0, 10);
16+
camera.lookAt(0, 0, 0);
17+
camera.updateProjectionMatrix();
18+
camera.updateMatrixWorld(true);
19+
20+
const svg = widget._composeLabelSVG(
21+
'data:image/png;base64,',
22+
[{ world: new THREE.Vector3(0, 0, 0), text: 'R1.500', anchor: 'center middle' }],
23+
400,
24+
300,
25+
400,
26+
{
27+
viewer: { camera },
28+
renderMode: 'monochrome',
29+
labelBackdropColor: '#ff0000',
30+
},
31+
);
32+
33+
assert(svg.includes('fill="#ff0000"'), 'Expected monochrome PMI label mask to use the view backdrop color.');
34+
assert(svg.includes('stroke="none"'), 'Expected monochrome PMI label mask to avoid drawing a border.');
35+
}
36+
37+
export function test_pmi_enter_edit_mode_reuses_shared_flow() {
38+
const widget = Object.create(PMIViewsWidget.prototype);
39+
const calls = [];
40+
const view = { viewName: 'View 1' };
41+
const originalSetTimeout = globalThis.setTimeout;
42+
43+
widget._enterEditMode = (viewArg, indexArg) => {
44+
calls.push({ view: viewArg, index: indexArg });
45+
};
46+
47+
try {
48+
globalThis.setTimeout = (fn, _delay) => {
49+
fn();
50+
return 0;
51+
};
52+
widget.enterEditMode(view, 3);
53+
} finally {
54+
globalThis.setTimeout = originalSetTimeout;
55+
}
56+
57+
assert(calls.length === 2, 'Expected shared PMI edit flow to invoke the edit handoff twice.');
58+
assert(calls[0]?.view === view && calls[1]?.view === view, 'Expected shared PMI edit flow to forward the same view.');
59+
assert(calls[0]?.index === 3 && calls[1]?.index === 3, 'Expected shared PMI edit flow to forward the same view index.');
60+
}

src/tests/tests.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ import {
132132
test_wire_harness_formboard_insert,
133133
} from './test_wireHarnessFormboard.js';
134134
import { test_pmi_view_text_size_setting_normalizes } from './test_pmiViewsManager.js';
135+
import {
136+
test_pmi_enter_edit_mode_reuses_shared_flow,
137+
test_pmi_monochrome_label_svg_uses_backdrop_color,
138+
} from './test_pmiViewsWidget.js';
135139
import {
136140
test_feature_dimension_overlay_supports_port,
137141
test_port_extension_annotation_geometry_preserves_extension_value,
@@ -264,6 +268,8 @@ export const testFunctions = [
264268
{ test: test_wire_harness_connection_endpoint_resolution, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
265269
{ test: test_sheet_custom_size_persists, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
266270
{ test: test_pmi_view_text_size_setting_normalizes, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
271+
{ test: test_pmi_monochrome_label_svg_uses_backdrop_color, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
272+
{ test: test_pmi_enter_edit_mode_reuses_shared_flow, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
267273
{ test: test_sheet_clipboard_image_utils, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
268274
{ test: test_wire_harness_formboard_insert, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
269275
{ test: test_wire_harness_sheet_table_insert, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },

0 commit comments

Comments
 (0)