markupJs
sRect 2022-2-18
import React, { useRef, useImperativeHandle, Fragment, forwardRef, memo } from 'react';
import { message } from 'antd';
import { nanoid } from 'nanoid/async';
import PropTypes from 'prop-types';
// import html2canvas from 'html2canvas';
import domtoimage from 'dom-to-image';
import { base64ToBlob } from '@/utils';
const getEditModel = MarkupCore => ({
arrow: MarkupCore.EditModeArrow,
circle: MarkupCore.EditModeCircle,
lace: MarkupCore.EditModeCloud,
line: MarkupCore.EditModeFreehand,
rect: MarkupCore.EditModeRectangle,
txt: MarkupCore.EditModeText,
});
// 获取当前viewerState(定位模型整体数据)
const getViewerState = async viewer => {
let tmpState = await viewer.getState();
tmpState = JSON.stringify(tmpState);
tmpState = tmpState.replace(/\"/g, "'"); // eslint-disable-line
return tmpState;
};
// 获取当前viewer截图
// const getViewerScreenShot = async () => await viewer.getScreenShot());
const getViewerScreenShot = viewer => {
return new Promise(resolve => {
viewer.getScreenShot(viewer.container.clientWidth, viewer.container.clientHeight, function (
base64
) {
resolve(base64);
});
});
};
/**
* 解析模型定位数据处理
* @param {String} location
*/
const resolveModelLocation = location => JSON.parse(location.replace(/'/g, '"'));
/**
* 解析涂鸦数据
* @param {String} markup
*/
const resolveModelMarkup = markup => markup.replace(/(^\s*)|(\s*$)/g, '');
const Markup = forwardRef((props, ref) => {
const { viewer, AV, modelPath } = props;
const markupExt = useRef(null); // markup extension实例
const handleEnterEditMode = () => {
if (markupExt.current) {
markupExt.current.show();
markupExt.current.enterEditMode();
}
};
const handleChangeEditMode = mode => {
if (!markupExt.current) return message.warning('涂鸦异常');
const MarkupCore = AV.Extensions.Markups.Core;
const EditeMode = getEditModel(MarkupCore)[mode];
const resultModel = new EditeMode(markupExt.current);
resultModel && markupExt.current?.changeEditMode(resultModel);
};
// 获取含 Markups 的截图
const getContainMarkUpBlob = () => {
return new Promise((resolve, reject) => {
try {
// const screenshot = new Image();
// const canvas = document.createElement('canvas');
// screenshot.src = imgPath;
// screenshot.onload = () => {
// // 如何在 Forge Viewer 里获取含 Markups 的截图
// // https://segmentfault.com/a/1190000012739224
// canvas.width = viewer?.container?.clientWidth;
// canvas.height = viewer?.container?.clientHeight;
// const ctx = canvas.getContext('2d');
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// ctx.drawImage(screenshot, 0, 0, canvas.width, canvas.height);
// // markupExt.current?.loadMarkups(markupSVG, 'layerName');
// try {
// markupExt.current?.renderToCanvas(ctx, () => {
// canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.8);
// });
// } catch (e) {
// console.log(e);
// // reject(e);
// canvas.toBlob(blob => resolve(blob), 'image/jpeg', 0.8);
// }
// // resolve(base64);
// };
// screenshot.onerror = () => reject('error');
const modelWrap = viewer?.canvasWrap?.parentNode;
if (!modelWrap) reject('error');
// // 使用html2canvas获取markup截图(无法获取涂鸦中的文字)
// // http://html2canvas.hertzen.com/configuration
// const canvasWrapBouding = viewer?.canvasWrap?.getBoundingClientRect();
// html2canvas(modelWrap, {
// // backgroundColor: null,
// foreignObjectRendering: true,
// width: viewer?.canvas?.width,
// height: viewer?.canvas?.height,
// scrollX: canvasWrapBouding.x,
// scrollY: canvasWrapBouding.y,
// ignoreElements: el => {
// if (
// (el.nodeName &&
// el.nodeName.toLowerCase() === 'div' &&
// el.parentNode &&
// el.parentNode === modelWrap &&
// el.classList &&
// !el.classList.contains('canvas-wrap')) ||
// (el.nodeName &&
// el.nodeName.toLowerCase() === 'div' &&
// el.classList.contains('modelName'))
// ) {
// return true;
// }
// return false;
// },
// }).then(function (canvasNode) {
// canvasNode.toBlob(blob => resolve(blob), 'image/jpeg', 0.8);
// });
// 使用dom-to-image获取markup截图
// https://www.npmjs.com/package/dom-to-image
domtoimage
.toBlob(modelWrap, {
quality: 0.8,
filter: node =>
!(
(node.nodeName.toLowerCase() === 'div' &&
!node.classList.contains('canvas-wrap')) ||
(node.nodeName.toLowerCase() === 'svg' && !node.hasAttribute('layer-order-id'))
),
})
.then(blob => {
resolve(blob);
})
.catch(error => {
console.log(error);
reject('error');
});
} catch (error) {
reject('error');
}
});
};
// 获取当前涂鸦数据
const getMarkState = async () => {
const randomId = await nanoid();
let markStr = await (markupExt.current?.duringEditMode && markupExt.current?.generateData());
markStr = markStr && markStr.replace(/\"/g, "'"); // eslint-disable-line
// markStr = markStr && markStr.replace(/(^\s*)|(\s*$)/g, '');
markStr = markStr && markStr.length > 0 && `${randomId}@BIM@${markStr}`;
return markStr;
};
const leaveEditMode = () => {
if (markupExt.current) {
markupExt.current.leaveEditMode();
markupExt.current.hide(); // 隐藏涂丫工具,并回复导览工具
}
};
/**
* 加载上一次涂鸦数据和视点定位
* 步骤:
* 1. 先监听视点AV.VIEWER_STATE_RESTORED_EVENT
* 2. 加载视点viewer.restoreState(location);
* 3. 监听相机完成事件AV.CAMERA_TRANSITION_COMPLETED
* 4. 相机完成后,执行markupExt.loadMarkups(markup)加载涂鸦数据
* @param {String} location 视点数据
* @param {String} markup 涂鸦数据
* @param {Number} pdfPageNum pdf模型页码
*/
const restoreMarkupState = ({ location, markup, pdfPageNum }) => {
if (!markupExt.current || !viewer) return message.warning('涂鸦加载异常');
const locationState = location && resolveModelLocation(location);
const markupState = markup && resolveModelMarkup(markup);
const markupStateArr = markupState && markupState.split('@#@');
const isPdf = viewer?.model?.isPdf() || false;
// 监听相机事件
const handleCameraTransitionCompleted = () => {
markupExt.current?.enterEditMode(); // 进入涂鸦模式
markupStateArr &&
Array.isArray(markupStateArr) &&
markupStateArr.length &&
markupStateArr.forEach(state => {
const currentMarkupState = state.split('@BIM@');
// 恢复涂鸦数据
markupExt.current?.loadMarkups(
currentMarkupState[1].replace(/'/g, '"'),
currentMarkupState[0]
);
});
// 每次加载涂鸦后,模式都改为自由线
const currentMarkupEditMode = markupExt.current?.editMode;
currentMarkupEditMode?.type !== 'line' && handleChangeEditMode('line');
viewer.removeEventListener(AV.CAMERA_TRANSITION_COMPLETED, handleCameraTransitionCompleted);
};
// 监听restore事件完成
const handleViewerStateRestored = () => {
viewer.addEventListener(AV.CAMERA_TRANSITION_COMPLETED, handleCameraTransitionCompleted);
viewer.removeEventListener(AV.VIEWER_STATE_RESTORED_EVENT, handleViewerStateRestored);
};
const handler = function () {
viewer.addEventListener(AV.VIEWER_STATE_RESTORED_EVENT, handleViewerStateRestored);
viewer.impl.setCutPlanes(); // 禁止所有切面(x,y,z),解除切面状态
markupExt.current?.unloadMarkupsAllLayers(); // 清除上一次的涂鸦数据
leaveEditMode(); // 相当于首次进入。模型可拖动等操作
viewer.setLayerVisible(null, true); // 如果是图纸,需要执行
viewer.restoreState(locationState); // 恢复视点定位数据
};
if (isPdf) {
viewer?.loadExtension('Autodesk.PDF').then(() => {
// pdf加载默认设置为第一页
viewer?.loadModel(modelPath, { page: pdfPageNum || 1 }, () => {
handler();
});
viewer?.setSwapBlackAndWhite(false); //false使图纸背景变为白色,true使图纸背景变为黑色
});
} else {
handler();
}
};
useImperativeHandle(ref, () => ({
// 获取markup extension实例
getMarkupExt: () => markupExt.current,
// 进入涂鸦模式
enterEditMode: () => handleEnterEditMode(),
// 退出涂鸦模式
leaveEditMode: () => leaveEditMode(),
// 修改涂鸦模式
changeEditMode: mode => handleChangeEditMode(mode),
// 上一步
handlePrevStep: () => markupExt.current?.rePreviousMarkupStack(),
// 下一步
handleNextStep: () => markupExt.current?.reNextMarkupStack(),
// 选择清除
handleDeleteOne: () => markupExt.current?.handleDeleteOne(),
// 全部清除
handleDeleteAll: () => markupExt.current?.handleDeleteAll(),
// 修改颜色
handleChangeColor: color => markupExt.current?.changeMarkupColor(color),
// 修改字体大小和线条粗细
handleChangeFontSizeAndLineWidth: num => {
const sliderVal = Number.parseInt(num) / 100;
markupExt.current?.changeTextFontSize(sliderVal);
markupExt.current?.changeLineTypeWidth(sliderVal);
},
// 保存
handleSave: async () => {
if (!viewer) return message.warning('保存失败,模型对象丢失');
let resultMarkUpBlob = null;
const screenShotBase64 = await getViewerScreenShot(viewer); // 获取viewer截图
// 获取包含markup的截图
const [error, markUpBlob] = await getContainMarkUpBlob(screenShotBase64)
.then(data => [null, data])
.catch(error => [error, null]);
if (screenShotBase64) {
if (error) {
resultMarkUpBlob = await base64ToBlob(screenShotBase64); // base64转blob对象
// message.warning('生成涂鸦截图失败,请重试');
} else {
resultMarkUpBlob = markUpBlob;
}
}
const screenShot = resultMarkUpBlob; // blob
const location = await getViewerState(viewer);
const markup = await getMarkState();
return {
location,
screenShot,
markup,
};
},
// 加载涂鸦数据
restoreMarkupState: (...args) => {
handleEnterEditMode();
restoreMarkupState(...args);
},
// 首次加载涂鸦
loadMarkUpExtension: (oViewer, callback) => {
const resultViewer = oViewer || viewer;
resultViewer
.loadExtension('Autodesk.Viewing.MarkupsCore')
.then(ext => {
markupExt.current = ext; // 设置markup extension实例
if (window.top.$markupGlobalData) {
window.top.$markupGlobalData.markupExt = ext;
window.top.$markupGlobalData.viewer = viewer;
}
leaveEditMode();
console.log('成功加载涂鸦');
return ext;
})
.then(ext => {
callback && callback(ext);
})
.catch(err => {
console.log(err);
message.warning('markup load fail');
});
},
// 卸载涂鸦
unloadMrkupExtension: () => {
leaveEditMode();
viewer?.unloadExtension('Autodesk.Viewing.MarkupsCore');
markupExt.current = null;
console.log('成功卸载涂鸦');
},
// 获取当前涂鸦模式
getMarkupEditMode: () => markupExt.current?.editMode,
}));
return <Fragment />;
});
Markup.propTypes = {
viewer: PropTypes.any,
AV: PropTypes.any,
modelPath: PropTypes.string,
};
export { getViewerScreenShot, getViewerState, resolveModelLocation, resolveModelMarkup };
export default memo(Markup);