SVGExporterTool.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /*
  2. * SVGExporterTool.js
  3. *
  4. * @author jespada
  5. */
  6. import { Tool } from './Tool.js'
  7. import { Solid } from '../core/Solid.js'
  8. import { GeometryUtils } from '../utils/GeometryUtils.js'
  9. import { WebUtils } from '../utils/WebUtils.js'
  10. import { Controls } from '../ui/Controls.js'
  11. import { MessageDialog } from '../ui/MessageDialog.js'
  12. import { I18N } from '../i18n/I18N.js'
  13. import * as THREE from '../lib/three.module.js'
  14. class SVGExporterTool extends Tool {
  15. constructor(application, options) {
  16. super(application)
  17. this.name = 'svg_exporter'
  18. this.label = 'tool.svg_exporter.label'
  19. this.className = 'svg_exporter'
  20. this.decimals = 5
  21. this.setOptions(options)
  22. this.createPanel()
  23. }
  24. createPanel() {
  25. this.panel = this.application.createPanel(this.label, 'left')
  26. this.panel.preferredHeight = 120
  27. this.titleElem = Controls.addTextField(this.panel.bodyElem, 'svg_exporter_title', 'label.svg_exporter_title', '', 'row')
  28. this.scaleElem = Controls.addTextField(this.panel.bodyElem, 'print_scale', 'label.print_scale', '10', 'row')
  29. this.scaleElem.style.width = '60px'
  30. this.svgExportButton = Controls.addButton(this.panel.bodyElem, 'svg_exporter_button', 'button.export', () => this.exportSvg())
  31. this.openLink = document.createElement('a')
  32. I18N.set(this.openLink, 'innerHTML', 'button.open')
  33. this.openLink.target = '_blank'
  34. this.openLink.style.display = 'none'
  35. this.panel.bodyElem.appendChild(this.openLink)
  36. }
  37. activate() {
  38. this.panel.visible = true
  39. }
  40. deactivate() {
  41. this.panel.visible = false
  42. }
  43. exportSvg() {
  44. this.svgExportButton.disabled = true
  45. this.application.progressBar.progress = undefined
  46. this.application.progressBar.visible = true
  47. this.application.progressBar.message = ''
  48. setTimeout(() => this.generateSvg(), 100)
  49. }
  50. generateSvg() {
  51. const application = this.application
  52. let scale = parseFloat(this.scaleElem.value)
  53. // assume units in meters
  54. let factor = 1 * 39.37007874 * 72 // dots per meter
  55. factor /= scale
  56. let matrix = new THREE.Matrix4()
  57. matrix.makeTranslation(297, 382, 0)
  58. matrix.multiply(new THREE.Matrix4().makeScale(factor, factor, factor))
  59. matrix.multiply(application.camera.matrixWorldInverse)
  60. let svgExportSource = {
  61. title: this.titleElem.value || 'Bimrocket_SVG_export.svg',
  62. strOut: '',
  63. writeHiddenEdges: true,
  64. bbox: new THREE.Box2(),
  65. debug: { testIfcElement: undefined }
  66. }
  67. let writeFromRootElement = svgExportSource.debug.testIfcElement === undefined && this.application.selection.objects.length === 0
  68. this.generateSvgObject(application.baseObject, matrix, svgExportSource, 2, writeFromRootElement)
  69. this.svgExportButton.disabled = false
  70. this.application.progressBar.visible = false
  71. const boxX = svgExportSource.bbox.min.x
  72. const boxY = -svgExportSource.bbox.max.y
  73. const boxWidth = svgExportSource.bbox.max.x - svgExportSource.bbox.min.x
  74. const boxHeight = svgExportSource.bbox.max.y - svgExportSource.bbox.min.y
  75. const content = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  76. <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
  77. version="1.1" viewBox="${boxX} ${boxY} ${boxWidth} ${boxHeight}">
  78. <defs>
  79. <style type="text/css"><![CDATA[
  80. path {
  81. stroke: black;
  82. fill: none;
  83. stroke-width: 2;
  84. }
  85. line {
  86. stroke: black;
  87. stroke-width: 2;
  88. }
  89. ]]>
  90. </style>
  91. </defs>
  92. <g id="root" transform="matrix(1,0,0,-1,0,0)">\n${svgExportSource.strOut}\t</g>\n</svg>`
  93. const encodedUri = encodeURI('data:text/svg;charset=utf-8,' + content)
  94. // override out
  95. this.openLink.setAttribute('href', encodedUri)
  96. this.openLink.setAttribute('download', svgExportSource.title + '.svg')
  97. this.openLink.click()
  98. }
  99. indent(level) {
  100. let str = ''
  101. for (let i = 0; i < level; i++) {
  102. str += '\t'
  103. }
  104. return str
  105. }
  106. writeSvgShape(object, matrix, svgExportSource, level, _writeSvgShape = true) {
  107. if (object instanceof Solid || object instanceof THREE.Mesh || object instanceof THREE.Line) {
  108. if (_writeSvgShape === false) {
  109. return true
  110. }
  111. }
  112. if (object instanceof Solid) {
  113. if (svgExportSource.writeHiddenEdges === true || object.edgesVisible === true) {
  114. let edgesGeometry = object.edgesGeometry
  115. let vertices = GeometryUtils.getBufferGeometryVertices(edgesGeometry)
  116. let p1 = new THREE.Vector3()
  117. let p2 = new THREE.Vector3()
  118. let drawnLines = new Set()
  119. // draw set of lines
  120. for (let i = 0; i < vertices.length; i += 2) {
  121. p1.copy(vertices[i])
  122. p1.applyMatrix4(object.matrixWorld)
  123. p1.applyMatrix4(matrix)
  124. p2.copy(vertices[i + 1])
  125. p2.applyMatrix4(object.matrixWorld)
  126. p2.applyMatrix4(matrix)
  127. let decimals = this.decimals
  128. let p1x = p1.x.toFixed(decimals)
  129. let p1y = p1.y.toFixed(decimals)
  130. let p2x = p2.x.toFixed(decimals)
  131. let p2y = p2.y.toFixed(decimals)
  132. // 1 point skip
  133. if (p1x === p2x && p1y === p2y) {
  134. continue
  135. }
  136. let lineId = `${p1x},${p1y},${p2x},${p2y}`
  137. let inverseLineId = `${p2x},${p2y},${p1x},${p1y}`
  138. // write only lines that not overlap
  139. if (!drawnLines.has(lineId) && !drawnLines.has(inverseLineId)) {
  140. svgExportSource.strOut += this.indent(level + 1) + `<line x1="${p1x}" y1="${p1y}" x2="${p2x}" y2="${p2y}" />\n`
  141. drawnLines.add(lineId)
  142. // update 2d bounding box
  143. svgExportSource.bbox.expandByPoint(p1)
  144. svgExportSource.bbox.expandByPoint(p2)
  145. }
  146. }
  147. return true
  148. }
  149. } else if (object instanceof THREE.Mesh) {
  150. let geometry = object.geometry
  151. if (geometry instanceof THREE.BufferGeometry) {
  152. GeometryUtils.getBufferGeometryFaces(
  153. // three mesh
  154. geometry,
  155. // callback foreach vertex converted conversion
  156. (va, vb, vc) => {
  157. const vertices = GeometryUtils.getBufferGeometryVertices(geometry)
  158. const p1 = new THREE.Vector3()
  159. const p2 = new THREE.Vector3()
  160. const p3 = new THREE.Vector3()
  161. p1.copy(vertices[va])
  162. p2.copy(vertices[vb])
  163. p3.copy(vertices[vc])
  164. p1.applyMatrix4(object.matrixWorld)
  165. p2.applyMatrix4(object.matrixWorld)
  166. p3.applyMatrix4(object.matrixWorld)
  167. p1.applyMatrix4(matrix)
  168. p2.applyMatrix4(matrix)
  169. p3.applyMatrix4(matrix)
  170. // update 2d bounding box
  171. svgExportSource.bbox.expandByPoint(p1)
  172. svgExportSource.bbox.expandByPoint(p2)
  173. svgExportSource.bbox.expandByPoint(p3)
  174. // draw polyline
  175. svgExportSource.strOut += this.indent(level + 1) + `<path d="M${p1.x},${p1.y} L${p2.x},${p2.y} L${p3.x},${p3.y} L${p1.x},${p1.y}" />\n`
  176. }
  177. )
  178. return true
  179. }
  180. } else if (object instanceof THREE.Line) {
  181. // skip lines
  182. return true
  183. }
  184. return false
  185. }
  186. generateSvgObject(object, matrix, svgExportSource, level = 0, _writeSvgShape = true) {
  187. let uuid = THREE.MathUtils.generateUUID()
  188. let globalId = 'unknow'
  189. let ifcClassName = 'unknow'
  190. if (object.userData.IFC) {
  191. if (object.userData.IFC.GlobalId) {
  192. globalId = object.userData.IFC.GlobalId
  193. }
  194. ifcClassName = object.userData.IFC.ifcClassName
  195. }
  196. if (object.userData.IFC_type) {
  197. if (globalId === 'unknow') {
  198. if (object.userData.IFC_type.GlobalId) {
  199. globalId = object.userData.IFC_type.GlobalId
  200. }
  201. }
  202. if (ifcClassName === 'unknow') {
  203. if (object.userData.IFC_type.ifcClassName) {
  204. ifcClassName = object.userData.IFC_type.ifcClassName
  205. }
  206. }
  207. }
  208. // drawable elements
  209. // object
  210. if (svgExportSource.debug.testIfcElement) {
  211. if (globalId === svgExportSource.debug.testIfcElement) {
  212. _writeSvgShape = true
  213. }
  214. } else {
  215. // do not process not selected objects...
  216. if (this.application.selection.contains(object)) {
  217. _writeSvgShape = true
  218. }
  219. }
  220. if (_writeSvgShape === true) {
  221. const id = uuid + '_' + globalId + '_' + ifcClassName
  222. svgExportSource.strOut += this.indent(level) + `<g id="${id}" data-guid="${globalId}" data-class-name="${ifcClassName}">\n`
  223. }
  224. if (this.writeSvgShape(object, matrix, svgExportSource, level, _writeSvgShape) === false) {
  225. for (let child of object.children) {
  226. if (child.visible) {
  227. this.generateSvgObject(child, matrix, svgExportSource, _writeSvgShape ? level + 1 : level, _writeSvgShape)
  228. }
  229. }
  230. }
  231. if (_writeSvgShape === true) {
  232. svgExportSource.strOut += this.indent(level) + '</g>\n'
  233. }
  234. }
  235. }
  236. export { SVGExporterTool }