ExtrudeTool.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /*
  2. * ExtrudeTool.js
  3. *
  4. * @author realor
  5. */
  6. import { Tool } from './Tool.js'
  7. import { Solid } from '../core/Solid.js'
  8. import { Profile } from '../core/Profile.js'
  9. import { ObjectBuilder } from '../builders/ObjectBuilder.js'
  10. import { Extruder } from '../builders/Extruder.js'
  11. import { Controls } from '../ui/Controls.js'
  12. import { I18N } from '../i18n/I18N.js'
  13. import * as THREE from '../lib/three.module.js'
  14. class ExtrudeTool extends Tool {
  15. constructor(application, options) {
  16. super(application)
  17. this.name = 'extrude'
  18. this.label = 'tool.extrude.label'
  19. this.help = 'tool.extrude.help'
  20. this.className = 'extrude'
  21. this.depthStep = 0.1
  22. this.setOptions(options)
  23. this.stage = 0
  24. this.solid = null
  25. this.depth = 0
  26. this.extrudeLine = new THREE.Line3(new THREE.Vector3(0, 0, -100), new THREE.Vector3(0, 0, 100))
  27. this.extrudeLineWorld = new THREE.Line3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 0))
  28. this.matrixWorldInverse = new THREE.Matrix4()
  29. this.line = null
  30. this._onPointerDown = this.onPointerDown.bind(this)
  31. this._onPointerUp = this.onPointerUp.bind(this)
  32. this._onPointerMove = this.onPointerMove.bind(this)
  33. this._onSelection = this.onSelection.bind(this)
  34. this._onContextMenu = this.onContextMenu.bind(this)
  35. this.extrudeMaterial = new THREE.LineBasicMaterial({
  36. color: new THREE.Color(1, 0, 0),
  37. depthTest: false,
  38. linewidth: 1.5
  39. })
  40. this.createPanel()
  41. }
  42. activate() {
  43. this.panel.visible = true
  44. const application = this.application
  45. const container = application.container
  46. container.addEventListener('contextmenu', this._onContextMenu, false)
  47. container.addEventListener('pointerdown', this._onPointerDown, false)
  48. container.addEventListener('pointerup', this._onPointerUp, false)
  49. container.addEventListener('pointermove', this._onPointerMove, false)
  50. application.addEventListener('selection', this._onSelection, false)
  51. this.prepareExtrusion()
  52. }
  53. deactivate() {
  54. this.panel.visible = false
  55. const application = this.application
  56. const container = application.container
  57. container.removeEventListener('contextmenu', this._onContextMenu, false)
  58. container.removeEventListener('pointerdown', this._onPointerDown, false)
  59. container.removeEventListener('pointerup', this._onPointerUp, false)
  60. container.removeEventListener('pointermove', this._onPointerMove, false)
  61. application.removeEventListener('selection', this._onSelection, false)
  62. application.pointSelector.deactivate()
  63. this.removeExtrusionAxis()
  64. }
  65. createPanel() {
  66. this.panel = this.application.createPanel(this.label, 'left', 'panel_extrude')
  67. this.panel.preferredHeight = 140
  68. this.helpElem = document.createElement('div')
  69. this.panel.bodyElem.appendChild(this.helpElem)
  70. this.depthInputElem = Controls.addNumberField(this.panel.bodyElem, 'extrude_depth', 'label.depth', 0)
  71. this.depthInputElem.step = this.depthStep
  72. this.depthInputElem.addEventListener(
  73. 'change',
  74. event => {
  75. this.depth = parseFloat(this.depthInputElem.value)
  76. this.updateExtrusion()
  77. },
  78. false
  79. )
  80. this.depthLabelElem = this.depthInputElem.parentElement.firstChild
  81. this.buttonsElem = document.createElement('div')
  82. this.panel.bodyElem.appendChild(this.buttonsElem)
  83. this.applyButton = Controls.addButton(this.buttonsElem, 'extrude_apply', 'button.apply', event => {
  84. this.depth = parseFloat(this.depthInputElem.value)
  85. this.updateExtrusion()
  86. })
  87. this.finishButton = Controls.addButton(this.buttonsElem, 'revolve_finish', 'button.finish', event => {
  88. this.application.selection.clear()
  89. })
  90. }
  91. onPointerDown(event) {
  92. const application = this.application
  93. const pointSelector = application.pointSelector
  94. if (!pointSelector.isPointSelectionEvent(event)) return
  95. if (this.stage === 0) {
  96. this.setStage(1)
  97. }
  98. }
  99. onPointerMove(event) {
  100. const pointSelector = this.application.pointSelector
  101. if (!pointSelector.isPointSelectionEvent(event)) return
  102. if (this.stage === 1) {
  103. const snap = pointSelector.snap
  104. if (snap) {
  105. let vector = new THREE.Vector3()
  106. vector.copy(snap.positionWorld).applyMatrix4(this.matrixWorldInverse)
  107. let height = vector.z
  108. vector.copy(this.solid.builder.direction).normalize()
  109. this.depth = height / vector.z
  110. this.updateDepthInPanel()
  111. this.updateExtrusion()
  112. }
  113. }
  114. }
  115. onPointerUp(event) {
  116. const pointSelector = this.application.pointSelector
  117. if (!pointSelector.isPointSelectionEvent(event)) return
  118. if (this.stage === 1) {
  119. this.setStage(0)
  120. }
  121. }
  122. onSelection(event) {
  123. this.prepareExtrusion()
  124. }
  125. onContextMenu(event) {
  126. const pointSelector = this.application.pointSelector
  127. if (!pointSelector.isPointSelectionEvent(event)) return
  128. event.preventDefault()
  129. }
  130. setStage(stage) {
  131. this.stage = stage
  132. const application = this.application
  133. switch (stage) {
  134. case 0: // set depth value
  135. application.pointSelector.auxiliaryLines = []
  136. application.pointSelector.deactivate()
  137. this.depthLabelElem.style.display = ''
  138. this.depthInputElem.disabled = false
  139. this.depthInputElem.style.display = ''
  140. this.applyButton.style.display = ''
  141. this.finishButton.style.display = ''
  142. I18N.set(this.helpElem, 'innerHTML', 'tool.extrude.drag_pointer')
  143. application.i18n.update(this.helpElem)
  144. this.removeExtrusionAxis()
  145. break
  146. case 1: // dynamic extrude
  147. application.pointSelector.clearAxisGuides()
  148. application.pointSelector.excludeSelection = true
  149. application.pointSelector.auxiliaryPoints = []
  150. application.pointSelector.auxiliaryLines = [this.extrudeLineWorld]
  151. application.pointSelector.activate()
  152. this.depthLabelElem.style.display = ''
  153. this.depthInputElem.disabled = true
  154. this.depthInputElem.style.display = ''
  155. this.applyButton.style.display = 'none'
  156. this.finishButton.style.display = ''
  157. I18N.set(this.helpElem, 'innerHTML', 'tool.extrude.drag_pointer')
  158. application.i18n.update(this.helpElem)
  159. this.addExtrusionAxis()
  160. break
  161. case 2: // no object selected
  162. application.pointSelector.auxiliaryLines = []
  163. application.pointSelector.deactivate()
  164. this.depthLabelElem.style.display = 'none'
  165. this.depthInputElem.style.display = 'none'
  166. this.applyButton.style.display = 'none'
  167. this.finishButton.style.display = 'none'
  168. I18N.set(this.helpElem, 'innerHTML', 'tool.extrude.select_object')
  169. application.i18n.update(this.helpElem)
  170. this.removeExtrusionAxis()
  171. break
  172. }
  173. }
  174. prepareExtrusion() {
  175. const application = this.application
  176. const object = application.selection.object
  177. let solid
  178. if (object instanceof Profile && !(object.parent instanceof Solid)) {
  179. solid = this.extrudeProfile(object)
  180. } else if (object instanceof Profile && object.parent instanceof Solid && object.parent.builder instanceof Extruder && object.parent.children.length === 3) {
  181. solid = object.parent
  182. } else if (object instanceof Solid && object.builder instanceof Extruder && object.children.length === 3 && object.children[2] instanceof Profile) {
  183. solid = object
  184. } else {
  185. solid = null
  186. }
  187. this.solid = solid
  188. if (solid) {
  189. this.depth = solid.builder.depth
  190. this.updateDepthInPanel()
  191. if (!application.selection.contains(solid)) {
  192. application.selection.set(solid)
  193. }
  194. this.setStage(0)
  195. } else {
  196. this.depth = 0
  197. this.setStage(2)
  198. }
  199. }
  200. extrudeProfile(profile) {
  201. const application = this.application
  202. const parent = profile.parent
  203. application.removeObject(profile)
  204. const solid = new Solid()
  205. solid.name = 'Extrude'
  206. solid.builder = new Extruder(0)
  207. profile.matrix.decompose(solid.position, solid.rotation, solid.scale)
  208. solid.matrix.copy(profile.matrix)
  209. profile.visible = false
  210. profile.matrix.identity()
  211. profile.matrix.decompose(profile.position, profile.rotation, profile.scale)
  212. solid.add(profile)
  213. ObjectBuilder.build(solid)
  214. application.addObject(solid, parent, false, true)
  215. return solid
  216. }
  217. updateExtrusion() {
  218. const solid = this.solid
  219. if (solid) {
  220. let extruder = solid.builder
  221. if (this.depth !== extruder.depth) {
  222. extruder.depth = this.depth
  223. solid.needsRebuild = true
  224. ObjectBuilder.build(solid)
  225. this.application.notifyObjectsChanged(solid)
  226. }
  227. }
  228. }
  229. updateDepthInPanel() {
  230. const k = 10000000
  231. this.depthInputElem.value = Math.round(this.depth * k) / k
  232. }
  233. addExtrusionAxis() {
  234. const application = this.application
  235. const solid = this.solid
  236. let vector = new THREE.Vector3()
  237. vector
  238. .copy(solid.builder.direction)
  239. .normalize()
  240. .multiplyScalar(1000)
  241. this.extrudeLine.start.copy(vector)
  242. vector.negate()
  243. this.extrudeLine.end.copy(vector)
  244. let matrixWorld = solid.matrixWorld
  245. this.extrudeLineWorld.copy(this.extrudeLine).applyMatrix4(matrixWorld)
  246. this.matrixWorldInverse.copy(matrixWorld).invert()
  247. if (this.line) {
  248. application.removeObject(this.line)
  249. }
  250. let geometryPoints = []
  251. geometryPoints.push(this.extrudeLineWorld.start)
  252. geometryPoints.push(this.extrudeLineWorld.end)
  253. let geometry = new THREE.BufferGeometry()
  254. geometry.setFromPoints(geometryPoints)
  255. this.line = new THREE.Line(geometry, this.extrudeMaterial)
  256. this.line.name = 'extrudeLine'
  257. this.line.raycast = function() {}
  258. this.line.renderOrder = 10
  259. application.overlays.add(this.line)
  260. application.repaint()
  261. }
  262. removeExtrusionAxis() {
  263. if (this.line) {
  264. this.application.removeObject(this.line)
  265. this.line = null
  266. }
  267. }
  268. }
  269. export { ExtrudeTool }