Skip to content

Commit d15c6e6

Browse files
author
mmiscool
committed
PMI views on sheets allow specify sheet specific view size
1 parent db42373 commit d15c6e6

5 files changed

Lines changed: 280 additions & 21 deletions

File tree

src/UI/pmi/PMIViewsWidget.js

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { adjustOrthographicFrustum, applyCameraSnapshot, captureCameraSnapshot }
1313
const UPDATE_CAMERA_TOOLTIP = 'Update this view to match the current camera';
1414
const PMI_EXPORT_CAPTURE_WIDTH_PX = 2400;
1515
const PMI_EXPORT_CAPTURE_HEIGHT_PX = 1800;
16+
const DEFAULT_PMI_VIEW_TEXT_SIZE_PT = 12;
1617
const SVG_NS = 'http://www.w3.org/2000/svg';
1718

1819
export class PMIViewsWidget {
@@ -113,6 +114,18 @@ export class PMIViewsWidget {
113114
};
114115
}
115116

117+
_normalizeViewTextSizePt(value, fallback = DEFAULT_PMI_VIEW_TEXT_SIZE_PT) {
118+
const numeric = Number(value);
119+
if (Number.isFinite(numeric) && numeric > 0) {
120+
return Math.max(1, Math.min(288, numeric));
121+
}
122+
return fallback;
123+
}
124+
125+
_getViewTextSizePt(view, fallback = DEFAULT_PMI_VIEW_TEXT_SIZE_PT) {
126+
return this._normalizeViewTextSizePt((view?.viewSettings || view?.settings)?.pmiTextSizePt, fallback);
127+
}
128+
116129
_updateViewportSensitiveRendering(width, height, viewerContext = this.viewer) {
117130
const safeWidth = Math.max(1, Number(width) || 1);
118131
const safeHeight = Math.max(1, Number(height) || 1);
@@ -612,17 +625,44 @@ export class PMIViewsWidget {
612625
return group;
613626
}
614627

628+
_resolveSheetPlacementPpi(renderContext = null) {
629+
const viewportWidth = Number(renderContext?.viewport?.width);
630+
const viewportHeight = Number(renderContext?.viewport?.height);
631+
const frameWidthIn = Number(renderContext?.targetFrameWidthIn);
632+
const frameHeightIn = Number(renderContext?.targetFrameHeightIn);
633+
if (!(viewportWidth > 0) || !(viewportHeight > 0) || !(frameWidthIn > 0) || !(frameHeightIn > 0)) {
634+
return null;
635+
}
636+
return Math.max(viewportWidth / frameWidthIn, viewportHeight / frameHeightIn);
637+
}
638+
639+
_resolveLabelFontSizePx(renderContext = null) {
640+
const ppi = this._resolveSheetPlacementPpi(renderContext);
641+
if (!(ppi > 0)) return null;
642+
const textSizePt = this._getViewTextSizePt(renderContext?.view || null);
643+
return (textSizePt / 72) * ppi;
644+
}
645+
646+
_getLabelLayoutMetrics(width, cssWidth = null, renderContext = null) {
647+
const safeCssWidth = Math.max(1, cssWidth || width);
648+
const dpr = Math.max(1, width / safeCssWidth);
649+
const fontSize = this._resolveLabelFontSizePx(renderContext) || (14 * dpr);
650+
return {
651+
dpr,
652+
paddingX: Math.max(4 * dpr, fontSize * (8 / 14)),
653+
paddingY: Math.max(3 * dpr, fontSize * (6 / 14)),
654+
lineHeight: Math.max(fontSize, fontSize * (18 / 14)),
655+
radius: Math.max(4 * dpr, fontSize * (8 / 14)),
656+
fontSize,
657+
};
658+
}
659+
615660
_buildLabelLayout(labels, width, height, cssWidth = null, renderContext = null, { svgCentered = false } = {}) {
616661
const camera = renderContext?.viewer?.camera || this.viewer?.camera;
617662
const isMonochrome = this._isMonochromeExport(renderContext);
618-
const safeCssWidth = Math.max(1, cssWidth || width);
619-
const dpr = Math.max(1, width / safeCssWidth);
620-
const paddingX = 8 * dpr;
621-
const paddingY = 6 * dpr;
622-
const lineHeight = 18 * dpr;
623-
const radius = 8 * dpr;
663+
const { dpr, paddingX, paddingY, lineHeight, radius, fontSize } =
664+
this._getLabelLayoutMetrics(width, cssWidth, renderContext);
624665
const fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
625-
const fontSize = 14 * dpr;
626666

627667
const layout = [];
628668
labels.forEach((label) => {
@@ -955,6 +995,8 @@ export class PMIViewsWidget {
955995
viewport,
956996
renderMode,
957997
showCenterLines,
998+
targetFrameWidthIn: Number(options?.targetFrameWidthIn) > 0 ? Number(options.targetFrameWidthIn) : null,
999+
targetFrameHeightIn: Number(options?.targetFrameHeightIn) > 0 ? Number(options.targetFrameHeightIn) : null,
9581000
dispose: () => {
9591001
try {
9601002
if (camera.parent === scene) scene.remove(camera);
@@ -1413,13 +1455,25 @@ export class PMIViewsWidget {
14131455
}
14141456
}
14151457

1416-
async captureViewImageDataUrl(view, viewIndex, { hideViewCube = true, renderMode = 'shaded', showCenterLines = false } = {}) {
1458+
async captureViewImageDataUrl(view, viewIndex, {
1459+
hideViewCube = true,
1460+
renderMode = 'shaded',
1461+
showCenterLines = false,
1462+
targetFrameWidthIn = null,
1463+
targetFrameHeightIn = null,
1464+
} = {}) {
14171465
const viewer = this.viewer;
14181466
if (!viewer) throw new Error('Viewer is not ready to export images');
14191467

14201468
const captureViewport = this._getCaptureViewportMetrics(view);
14211469
const originalWireframe = this._detectWireframe(viewer.scene);
1422-
const renderContext = this._createExportRenderContext(captureViewport, view, { renderMode, showCenterLines });
1470+
const renderContext = this._createExportRenderContext(captureViewport, view, {
1471+
renderMode,
1472+
showCenterLines,
1473+
targetFrameWidthIn,
1474+
targetFrameHeightIn,
1475+
});
1476+
renderContext.view = view || null;
14231477

14241478
let dataUrl = null;
14251479
const runCapture = async () => {
@@ -1486,6 +1540,8 @@ export class PMIViewsWidget {
14861540
.pmi-row-menu .pmi-btn { width: 100%; justify-content: flex-start; }
14871541
.pmi-row-menu .pmi-btn.danger { justify-content: center; }
14881542
.pmi-row-menu-wireframe { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #e5e7eb; }
1543+
.pmi-row-menu-setting { display: flex; flex-direction: column; gap: 6px; font-size: 12px; color: #e5e7eb; }
1544+
.pmi-row-menu-setting .pmi-input { min-width: 0; }
14891545
.pmi-row-menu hr { border: none; border-top: 1px solid #1f2937; margin: 4px 0; }
14901546
`;
14911547
document.head.appendChild(style);
@@ -1670,6 +1726,27 @@ export class PMIViewsWidget {
16701726
wireframeLabel.appendChild(wireframeText);
16711727
menu.appendChild(wireframeLabel);
16721728

1729+
const textSizeWrap = document.createElement('label');
1730+
textSizeWrap.className = 'pmi-row-menu-setting';
1731+
const textSizeText = document.createElement('span');
1732+
textSizeText.textContent = 'Text size (pt)';
1733+
const textSizeInput = document.createElement('input');
1734+
textSizeInput.type = 'number';
1735+
textSizeInput.min = '1';
1736+
textSizeInput.max = '288';
1737+
textSizeInput.step = '0.5';
1738+
textSizeInput.className = 'pmi-input';
1739+
textSizeInput.value = String(this._getViewTextSizePt(v));
1740+
textSizeInput.title = 'PMI label size on the sheet in points';
1741+
textSizeInput.addEventListener('click', (evt) => evt.stopPropagation());
1742+
textSizeInput.addEventListener('change', (evt) => {
1743+
evt.stopPropagation();
1744+
this._setViewTextSizePt(idx, textSizeInput.value);
1745+
});
1746+
textSizeWrap.appendChild(textSizeText);
1747+
textSizeWrap.appendChild(textSizeInput);
1748+
menu.appendChild(textSizeWrap);
1749+
16731750
menuBtn.addEventListener('click', (evt) => {
16741751
evt.stopPropagation();
16751752
this._toggleRowMenu(menu, menuBtn);
@@ -2174,14 +2251,9 @@ export class PMIViewsWidget {
21742251
if (!baseImage) throw new Error('Base image missing for SVG composition');
21752252
const camera = renderContext?.viewer?.camera || this.viewer?.camera;
21762253
const isMonochrome = this._isMonochromeExport(renderContext);
2177-
const safeCssWidth = Math.max(1, cssWidth || width);
2178-
const dpr = Math.max(1, width / safeCssWidth);
2179-
const paddingX = 8 * dpr;
2180-
const paddingY = 6 * dpr;
2181-
const lineHeight = 18 * dpr;
2182-
const radius = 8 * dpr;
2254+
const { paddingX, paddingY, lineHeight, radius, fontSize } =
2255+
this._getLabelLayoutMetrics(width, cssWidth, renderContext);
21832256
const fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
2184-
const fontSize = 14 * dpr;
21852257

21862258
const layout = [];
21872259
labels.forEach((label) => {
@@ -2526,6 +2598,35 @@ export class PMIViewsWidget {
25262598
}
25272599
}
25282600

2601+
_setViewTextSizePt(index, value) {
2602+
if (this._readOnly) return;
2603+
const nextTextSizePt = this._normalizeViewTextSizePt(value);
2604+
const applyValue = (entry) => {
2605+
if (!entry || typeof entry !== 'object') return entry;
2606+
if (!entry.viewSettings || typeof entry.viewSettings !== 'object') {
2607+
entry.viewSettings = {};
2608+
}
2609+
entry.viewSettings.pmiTextSizePt = nextTextSizePt;
2610+
return entry;
2611+
};
2612+
2613+
let updated = false;
2614+
const manager = this.viewer?.partHistory?.pmiViewsManager;
2615+
if (manager && typeof manager.updateView === 'function') {
2616+
const result = manager.updateView(index, (entry) => applyValue(entry));
2617+
updated = Boolean(result);
2618+
} else if (Array.isArray(this.views) && this.views[index]) {
2619+
applyValue(this.views[index]);
2620+
updated = true;
2621+
this.refreshFromHistory();
2622+
}
2623+
2624+
if (!updated) {
2625+
this.refreshFromHistory();
2626+
this._renderList();
2627+
}
2628+
}
2629+
25292630
_updateViewCamera(index) {
25302631
if (this._readOnly) return;
25312632
try {

0 commit comments

Comments
 (0)