/* * SVGExporterTool.js * * @author jespada */ import { Tool } from './Tool.js' import { Solid } from '../core/Solid.js' import { GeometryUtils } from '../utils/GeometryUtils.js' import { WebUtils } from '../utils/WebUtils.js' import { Controls } from '../ui/Controls.js' import { MessageDialog } from '../ui/MessageDialog.js' import { I18N } from '../i18n/I18N.js' import * as THREE from '../lib/three.module.js' class SVGExporterTool extends Tool { constructor(application, options) { super(application) this.name = 'svg_exporter' this.label = 'tool.svg_exporter.label' this.className = 'svg_exporter' this.decimals = 5 this.setOptions(options) this.createPanel() } createPanel() { this.panel = this.application.createPanel(this.label, 'left') this.panel.preferredHeight = 120 this.titleElem = Controls.addTextField(this.panel.bodyElem, 'svg_exporter_title', 'label.svg_exporter_title', '', 'row') this.scaleElem = Controls.addTextField(this.panel.bodyElem, 'print_scale', 'label.print_scale', '10', 'row') this.scaleElem.style.width = '60px' this.svgExportButton = Controls.addButton(this.panel.bodyElem, 'svg_exporter_button', 'button.export', () => this.exportSvg()) this.openLink = document.createElement('a') I18N.set(this.openLink, 'innerHTML', 'button.open') this.openLink.target = '_blank' this.openLink.style.display = 'none' this.panel.bodyElem.appendChild(this.openLink) } activate() { this.panel.visible = true } deactivate() { this.panel.visible = false } exportSvg() { this.svgExportButton.disabled = true this.application.progressBar.progress = undefined this.application.progressBar.visible = true this.application.progressBar.message = '' setTimeout(() => this.generateSvg(), 100) } generateSvg() { const application = this.application let scale = parseFloat(this.scaleElem.value) // assume units in meters let factor = 1 * 39.37007874 * 72 // dots per meter factor /= scale let matrix = new THREE.Matrix4() matrix.makeTranslation(297, 382, 0) matrix.multiply(new THREE.Matrix4().makeScale(factor, factor, factor)) matrix.multiply(application.camera.matrixWorldInverse) let svgExportSource = { title: this.titleElem.value || 'Bimrocket_SVG_export.svg', strOut: '', writeHiddenEdges: true, bbox: new THREE.Box2(), debug: { testIfcElement: undefined } } let writeFromRootElement = svgExportSource.debug.testIfcElement === undefined && this.application.selection.objects.length === 0 this.generateSvgObject(application.baseObject, matrix, svgExportSource, 2, writeFromRootElement) this.svgExportButton.disabled = false this.application.progressBar.visible = false const boxX = svgExportSource.bbox.min.x const boxY = -svgExportSource.bbox.max.y const boxWidth = svgExportSource.bbox.max.x - svgExportSource.bbox.min.x const boxHeight = svgExportSource.bbox.max.y - svgExportSource.bbox.min.y const content = ` \n${svgExportSource.strOut}\t\n` const encodedUri = encodeURI('data:text/svg;charset=utf-8,' + content) // override out this.openLink.setAttribute('href', encodedUri) this.openLink.setAttribute('download', svgExportSource.title + '.svg') this.openLink.click() } indent(level) { let str = '' for (let i = 0; i < level; i++) { str += '\t' } return str } writeSvgShape(object, matrix, svgExportSource, level, _writeSvgShape = true) { if (object instanceof Solid || object instanceof THREE.Mesh || object instanceof THREE.Line) { if (_writeSvgShape === false) { return true } } if (object instanceof Solid) { if (svgExportSource.writeHiddenEdges === true || object.edgesVisible === true) { let edgesGeometry = object.edgesGeometry let vertices = GeometryUtils.getBufferGeometryVertices(edgesGeometry) let p1 = new THREE.Vector3() let p2 = new THREE.Vector3() let drawnLines = new Set() // draw set of lines for (let i = 0; i < vertices.length; i += 2) { p1.copy(vertices[i]) p1.applyMatrix4(object.matrixWorld) p1.applyMatrix4(matrix) p2.copy(vertices[i + 1]) p2.applyMatrix4(object.matrixWorld) p2.applyMatrix4(matrix) let decimals = this.decimals let p1x = p1.x.toFixed(decimals) let p1y = p1.y.toFixed(decimals) let p2x = p2.x.toFixed(decimals) let p2y = p2.y.toFixed(decimals) // 1 point skip if (p1x === p2x && p1y === p2y) { continue } let lineId = `${p1x},${p1y},${p2x},${p2y}` let inverseLineId = `${p2x},${p2y},${p1x},${p1y}` // write only lines that not overlap if (!drawnLines.has(lineId) && !drawnLines.has(inverseLineId)) { svgExportSource.strOut += this.indent(level + 1) + `\n` drawnLines.add(lineId) // update 2d bounding box svgExportSource.bbox.expandByPoint(p1) svgExportSource.bbox.expandByPoint(p2) } } return true } } else if (object instanceof THREE.Mesh) { let geometry = object.geometry if (geometry instanceof THREE.BufferGeometry) { GeometryUtils.getBufferGeometryFaces( // three mesh geometry, // callback foreach vertex converted conversion (va, vb, vc) => { const vertices = GeometryUtils.getBufferGeometryVertices(geometry) const p1 = new THREE.Vector3() const p2 = new THREE.Vector3() const p3 = new THREE.Vector3() p1.copy(vertices[va]) p2.copy(vertices[vb]) p3.copy(vertices[vc]) p1.applyMatrix4(object.matrixWorld) p2.applyMatrix4(object.matrixWorld) p3.applyMatrix4(object.matrixWorld) p1.applyMatrix4(matrix) p2.applyMatrix4(matrix) p3.applyMatrix4(matrix) // update 2d bounding box svgExportSource.bbox.expandByPoint(p1) svgExportSource.bbox.expandByPoint(p2) svgExportSource.bbox.expandByPoint(p3) // draw polyline svgExportSource.strOut += this.indent(level + 1) + `\n` } ) return true } } else if (object instanceof THREE.Line) { // skip lines return true } return false } generateSvgObject(object, matrix, svgExportSource, level = 0, _writeSvgShape = true) { let uuid = THREE.MathUtils.generateUUID() let globalId = 'unknow' let ifcClassName = 'unknow' if (object.userData.IFC) { if (object.userData.IFC.GlobalId) { globalId = object.userData.IFC.GlobalId } ifcClassName = object.userData.IFC.ifcClassName } if (object.userData.IFC_type) { if (globalId === 'unknow') { if (object.userData.IFC_type.GlobalId) { globalId = object.userData.IFC_type.GlobalId } } if (ifcClassName === 'unknow') { if (object.userData.IFC_type.ifcClassName) { ifcClassName = object.userData.IFC_type.ifcClassName } } } // drawable elements // object if (svgExportSource.debug.testIfcElement) { if (globalId === svgExportSource.debug.testIfcElement) { _writeSvgShape = true } } else { // do not process not selected objects... if (this.application.selection.contains(object)) { _writeSvgShape = true } } if (_writeSvgShape === true) { const id = uuid + '_' + globalId + '_' + ifcClassName svgExportSource.strOut += this.indent(level) + `\n` } if (this.writeSvgShape(object, matrix, svgExportSource, level, _writeSvgShape) === false) { for (let child of object.children) { if (child.visible) { this.generateSvgObject(child, matrix, svgExportSource, _writeSvgShape ? level + 1 : level, _writeSvgShape) } } } if (_writeSvgShape === true) { svgExportSource.strOut += this.indent(level) + '\n' } } } export { SVGExporterTool }