PaintTool.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /*
  2. * PaintTool.js
  3. *
  4. * @author realor
  5. */
  6. import { Tool } from './Tool.js'
  7. import { I18N } from '../i18n/I18N.js'
  8. import { Controls } from '../ui/Controls.js'
  9. import { Solid } from '../core/Solid.js'
  10. import { InputDialog } from '../ui/InputDialog.js'
  11. import { ObjectUtils } from '../utils/ObjectUtils.js'
  12. import * as THREE from '../lib/three.module.js'
  13. class PaintTool extends Tool {
  14. constructor(application, options) {
  15. super(application)
  16. this.name = 'paint'
  17. this.label = 'tool.paint.label'
  18. this.className = 'paint'
  19. this.setOptions(options)
  20. this.createPanel()
  21. this.materials = new Map()
  22. this.sceneUuid = null
  23. }
  24. createPanel() {
  25. const application = this.application
  26. this.panel = this.application.createPanel(this.label, 'left')
  27. this.panel.minimumHeight = 160
  28. this.materialListElem = Controls.addSelectField(this.panel.bodyElem, 'materialList', 'label.material_list')
  29. this.materialListElem.style.display = 'block'
  30. this.materialListElem.style.width = '90%'
  31. this.materialListElem.style.marginLeft = 'auto'
  32. this.materialListElem.style.marginRight = 'auto'
  33. this.materialListElem.addEventListener('change', event => {
  34. this.loadMaterial()
  35. })
  36. this.newMaterialButton = Controls.addButton(this.panel.bodyElem, 'new_material', 'button.new', () => this.newMaterial())
  37. this.renameMaterialButton = Controls.addButton(this.panel.bodyElem, 'rename_material', 'button.rename', () => this.renameMaterial())
  38. this.materialUsageButton = Controls.addButton(this.panel.bodyElem, 'material_usage', 'button.material_usage', () => this.materialUsage())
  39. this.colorElem = Controls.addColorField(this.panel.bodyElem, 'material_color', 'label.color', null, 'option_block inline')
  40. this.colorElem.addEventListener(
  41. 'input',
  42. event => {
  43. let color = this.colorElem.value
  44. let materialId = this.materialListElem.value
  45. let material = this.materials.get(materialId)
  46. material.color.set(color)
  47. application.repaint()
  48. },
  49. false
  50. )
  51. this.specularElem = Controls.addColorField(this.panel.bodyElem, 'material_specular', 'label.specular', null, 'option_block inline')
  52. this.specularElem.addEventListener(
  53. 'input',
  54. event => {
  55. let color = this.specularElem.value
  56. let material = this.getSelectedMaterial()
  57. material.specular.set(color)
  58. application.repaint()
  59. },
  60. false
  61. )
  62. this.emissiveElem = Controls.addColorField(this.panel.bodyElem, 'material_specular', 'label.emissive', null, 'option_block inline')
  63. this.emissiveElem.addEventListener(
  64. 'input',
  65. event => {
  66. let color = this.emissiveElem.value
  67. let material = this.getSelectedMaterial()
  68. material.emissive.set(color)
  69. application.repaint()
  70. },
  71. false
  72. )
  73. this.opacityElem = document.createElement('div')
  74. this.opacityElem.className = 'option_block'
  75. this.panel.bodyElem.appendChild(this.opacityElem)
  76. this.opacityLabel = document.createElement('label')
  77. this.opacityLabel.htmlFor = 'material_opacity'
  78. I18N.set(this.opacityLabel, 'innerHTML', 'label.opacity', 50)
  79. this.application.i18n.update(this.opacityLabel)
  80. this.opacityElem.appendChild(this.opacityLabel)
  81. this.opacityRange = document.createElement('input')
  82. this.opacityRange.id = 'material_opacity'
  83. this.opacityRange.type = 'range'
  84. this.opacityRange.min = 0
  85. this.opacityRange.max = 100
  86. this.opacityRange.step = 1
  87. this.opacityRange.style.display = 'block'
  88. this.opacityRange.style.width = '80%'
  89. this.opacityRange.style.marginLeft = 'auto'
  90. this.opacityRange.style.marginRight = 'auto'
  91. this.opacityElem.appendChild(this.opacityRange)
  92. this.opacityRange.addEventListener(
  93. 'input',
  94. () => {
  95. let opacity = this.opacityRange.value
  96. I18N.set(this.opacityLabel, 'innerHTML', 'label.opacity', opacity)
  97. application.i18n.update(this.opacityLabel)
  98. let material = this.getSelectedMaterial()
  99. material.opacity = opacity / 100
  100. material.transparent = material.opacity < 1
  101. application.repaint()
  102. },
  103. false
  104. )
  105. this.sideSelect = Controls.addSelectField(
  106. this.panel.bodyElem,
  107. 'material_side',
  108. 'label.material_side',
  109. [
  110. [String(THREE.FrontSide), 'label.front_side'],
  111. [String(THREE.BackSide), 'label.back_side'],
  112. [String(THREE.DoubleSide), 'label.double_side']
  113. ],
  114. null,
  115. 'option_block inline'
  116. )
  117. this.sideSelect.addEventListener('change', event => {
  118. let material = this.getSelectedMaterial()
  119. material.side = parseInt(this.sideSelect.value)
  120. application.repaint()
  121. })
  122. this.depthTestCheckBox = Controls.addCheckBoxField(this.panel.bodyElem, 'depth_test', 'label.depth_test', false, 'option_block')
  123. this.depthTestCheckBox.addEventListener('change', event => {
  124. let material = this.getSelectedMaterial()
  125. material.depthTest = this.depthTestCheckBox.checked
  126. application.repaint()
  127. })
  128. this.depthWriteCheckBox = Controls.addCheckBoxField(this.panel.bodyElem, 'depth_write', 'label.depth_write', false, 'option_block')
  129. this.depthWriteCheckBox.addEventListener('change', event => {
  130. let material = this.getSelectedMaterial()
  131. material.depthWrite = this.depthWriteCheckBox.checked
  132. application.repaint()
  133. })
  134. this.applyElem = document.createElement('div')
  135. this.applyElem.className = 'option_block'
  136. this.panel.bodyElem.appendChild(this.applyElem)
  137. this.applyMessageElem = document.createElement('div')
  138. I18N.set(this.applyMessageElem, 'innerHTML', 'label.material_on_selection')
  139. this.applyElem.appendChild(this.applyMessageElem)
  140. this.previewMaterialButton = Controls.addButton(this.applyElem, 'preview_material', 'button.preview_material', () => this.applyMaterial({ meshMaterial: this.getSelectedMaterial() }))
  141. this.applyMaterialButton = Controls.addButton(this.applyElem, 'apply_material', 'button.apply_material', () =>
  142. this.applyMaterial({
  143. meshMaterial: this.getSelectedMaterial(),
  144. original: true
  145. })
  146. )
  147. this.restoreMaterialsButton = Controls.addButton(this.applyElem, 'restore_material', 'button.restore_materials', () => this.applyMaterial({ meshMaterial: null }))
  148. }
  149. activate() {
  150. this.panel.visible = true
  151. this.findMaterials()
  152. }
  153. deactivate() {
  154. this.panel.visible = false
  155. }
  156. loadMaterial() {
  157. let materialId = this.materialListElem.value
  158. let material = this.materials.get(materialId)
  159. let haveMaterial = material !== undefined
  160. this.colorElem.disabled = !haveMaterial
  161. this.specularElem.disabled = !haveMaterial
  162. this.emissiveElem.disabled = !haveMaterial
  163. this.sideSelect.disabled = !haveMaterial
  164. this.depthTestCheckBox.disabled = !haveMaterial
  165. this.depthWriteCheckBox.disabled = !haveMaterial
  166. this.opacityRange.disabled = !haveMaterial
  167. this.renameMaterialButton.disabled = !haveMaterial
  168. this.materialUsageButton.disabled = !haveMaterial
  169. this.previewMaterialButton.disabled = !haveMaterial
  170. this.applyMaterialButton.disabled = !haveMaterial
  171. if (haveMaterial) {
  172. this.colorElem.value = '#' + material.color.getHexString()
  173. this.specularElem.value = '#' + material.specular.getHexString()
  174. this.emissiveElem.value = '#' + material.emissive.getHexString()
  175. this.sideSelect.value = String(material.side)
  176. this.depthTestCheckBox.checked = material.depthTest
  177. this.depthWriteCheckBox.checked = material.depthWrite
  178. let opacity = Math.round(material.opacity * 100)
  179. I18N.set(this.opacityLabel, 'innerHTML', 'label.opacity', opacity)
  180. this.application.i18n.update(this.opacityLabel)
  181. this.opacityRange.value = opacity
  182. }
  183. }
  184. findMaterials() {
  185. const application = this.application
  186. if (this.sceneUuid !== application.scene.uuid) {
  187. // clear materials if scene is new
  188. for (let material of this.materials.values()) {
  189. material.dispose()
  190. }
  191. this.materials.clear()
  192. this.sceneUuid = application.scene.uuid
  193. }
  194. const materials = this.materials
  195. application.baseObject.traverse(object => {
  196. for (let name of ['material', ObjectUtils.ORIGINAL_MATERIAL]) {
  197. let material = object[name]
  198. if (material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial) {
  199. let materialId = String(material.id)
  200. materials.set(materialId, material)
  201. }
  202. }
  203. })
  204. let materialOptions = []
  205. for (let material of materials.values()) {
  206. let materialName = this.getMaterialLabel(material)
  207. materialOptions.push([String(material.id), materialName])
  208. }
  209. materialOptions.sort((a, b) => {
  210. if (a[1] === b[1]) return 0
  211. return a[1] < b[1] ? -1 : 1
  212. })
  213. Controls.setSelectOptions(this.materialListElem, materialOptions)
  214. this.loadMaterial()
  215. }
  216. newMaterial() {
  217. const dialog = new InputDialog(this.application, 'title.new_material', 'label.material_name', '')
  218. dialog.onAccept = name => {
  219. let material = this.createMaterial(name)
  220. let optionElem = document.createElement('option')
  221. let materialId = String(material.id)
  222. optionElem.value = materialId
  223. optionElem.innerHTML = this.getMaterialLabel(material)
  224. this.materialListElem.appendChild(optionElem)
  225. this.materialListElem.value = materialId
  226. this.materials.set(materialId, material)
  227. this.loadMaterial()
  228. dialog.hide()
  229. }
  230. dialog.show()
  231. }
  232. renameMaterial() {
  233. let material = this.getSelectedMaterial()
  234. if (material) {
  235. const dialog = new InputDialog(this.application, 'title.rename_material', 'label.material_name', material.name)
  236. dialog.onAccept = name => {
  237. material.name = name
  238. let index = this.materialListElem.selectedIndex
  239. this.materialListElem.options[index].innerHTML = this.getMaterialLabel(material)
  240. dialog.hide()
  241. }
  242. dialog.show()
  243. }
  244. }
  245. materialUsage() {
  246. let usages = []
  247. let material = this.getSelectedMaterial()
  248. if (material) {
  249. const baseObject = this.application.baseObject
  250. baseObject.traverse(object => {
  251. if (object.visible) {
  252. if (object.material === material) {
  253. usages.push(object)
  254. }
  255. }
  256. })
  257. }
  258. this.application.selection.set(...usages)
  259. }
  260. applyMaterial(appearance) {
  261. const application = this.application
  262. const roots = application.selection.roots
  263. ObjectUtils.updateAppearance(roots, appearance)
  264. application.repaint()
  265. }
  266. createMaterial(name) {
  267. let material = new THREE.MeshPhongMaterial()
  268. material.color.set('#C0C0C0')
  269. material.name = name
  270. material.needsUpdate = true
  271. material.side = THREE.DoubleSide
  272. return material
  273. }
  274. getSelectedMaterial() {
  275. let materialId = this.materialListElem.value
  276. return this.materials.get(materialId)
  277. }
  278. getMaterialLabel(material) {
  279. let name = material.name || 'Unnamed'
  280. return name + ' (m' + material.id + ')'
  281. }
  282. }
  283. export { PaintTool }