Inspector.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464
  1. /**
  2. * Inspector.js
  3. *
  4. * @author realor
  5. */
  6. import { Panel } from './Panel.js'
  7. import { Tree } from './Tree.js'
  8. import { Dialog } from './Dialog.js'
  9. import { ConfirmDialog } from './ConfirmDialog.js'
  10. import { Application } from './Application.js'
  11. import { Solid } from '../core/Solid.js'
  12. import { SolidGeometry } from '../core/SolidGeometry.js'
  13. import { Formula } from '../formula/Formula.js'
  14. import { ObjectUtils } from '../utils/ObjectUtils.js'
  15. import { FormulaDialog } from './FormulaDialog.js'
  16. import { PropertiesDialog } from './PropertiesDialog.js'
  17. import { PropertyDialog } from './PropertyDialog.js'
  18. import { BuilderDialog } from './BuilderDialog.js'
  19. import { ControllerDialog } from './ControllerDialog.js'
  20. import { Controls } from './Controls.js'
  21. import { I18N } from '../i18n/I18N.js'
  22. import * as THREE from '../lib/three.module.js'
  23. class Inspector extends Panel {
  24. static mainMaterialProperties = [
  25. 'id',
  26. 'uuid',
  27. 'type',
  28. 'name',
  29. 'color',
  30. 'specular',
  31. 'emissive',
  32. 'shininess',
  33. 'opacity',
  34. 'transparent',
  35. 'side',
  36. 'emissiveIntensity',
  37. 'fog',
  38. 'depthTest',
  39. 'depthWrite',
  40. 'polygonOffset',
  41. 'polygonOffsetFactor',
  42. 'polygonOffsetUnits',
  43. 'sizeAttenuation',
  44. 'map'
  45. ]
  46. constructor(application) {
  47. super(application)
  48. this.position = 'right'
  49. this.object = null
  50. this.objects = null
  51. this.state = {}
  52. this.objectSectionName = 'Object'
  53. this.materialSectionName = 'Material'
  54. this.builderSectionName = 'Builder'
  55. this.formulasSectionName = 'Formulas'
  56. this.geometrySectionName = 'Geometry'
  57. this.propertiesSectionName = 'Properties'
  58. this.controllersSectionName = 'Controllers'
  59. this.renderers = {}
  60. this.editors = {}
  61. this.addRenderer(StringRenderer)
  62. this.addRenderer(NumberRenderer)
  63. this.addRenderer(BooleanRenderer)
  64. this.addRenderer(VectorRenderer)
  65. this.addRenderer(EulerRenderer)
  66. this.addRenderer(FormulaRenderer)
  67. this.addRenderer(ColorRenderer)
  68. this.addRenderer(TextureRenderer)
  69. this.addRenderer(Object3DRenderer)
  70. this.addEditor(StringEditor)
  71. this.addEditor(NumberEditor)
  72. this.addEditor(BooleanEditor)
  73. this.addEditor(VectorEditor)
  74. this.addEditor(EulerEditor)
  75. this.addEditor(FormulaEditor)
  76. this.addEditor(ColorEditor)
  77. this.addEditor(TextureEditor)
  78. this.objectPanelElem = document.createElement('div')
  79. this.bodyElem.appendChild(this.objectPanelElem)
  80. this.objectPanelElem.className = 'inspector_card'
  81. this.toolBarElem = document.createElement('div')
  82. this.objectPanelElem.appendChild(this.toolBarElem)
  83. this.toolBarElem.className = 'inspector_toolbar'
  84. this.listButton = Controls.addButton(
  85. this.toolBarElem,
  86. 'list',
  87. null,
  88. event => {
  89. this.listPanelElem.style.display = ''
  90. this.objectPanelElem.style.display = 'none'
  91. },
  92. 'list'
  93. )
  94. I18N.set(this.listButton, 'title', 'button.list')
  95. I18N.set(this.listButton, 'alt', 'button.list')
  96. this.previousButton = Controls.addButton(
  97. this.toolBarElem,
  98. 'previous',
  99. null,
  100. event => {
  101. if (this.objectIndex > 0) {
  102. this.showProperties(this.objects[this.objectIndex - 1])
  103. this.centerObject()
  104. }
  105. },
  106. 'previous'
  107. )
  108. I18N.set(this.previousButton, 'title', 'button.previous')
  109. I18N.set(this.previousButton, 'alt', 'button.previous')
  110. this.indexElem = document.createElement('span')
  111. this.toolBarElem.appendChild(this.indexElem)
  112. this.nextButton = Controls.addButton(
  113. this.toolBarElem,
  114. 'next',
  115. null,
  116. event => {
  117. if (this.objectIndex < this.objects.length - 1) {
  118. this.showProperties(this.objects[this.objectIndex + 1])
  119. this.centerObject()
  120. }
  121. },
  122. 'next'
  123. )
  124. I18N.set(this.nextButton, 'title', 'button.next')
  125. I18N.set(this.nextButton, 'alt', 'button.next')
  126. this.selectButton = Controls.addButton(
  127. this.toolBarElem,
  128. 'select',
  129. null,
  130. event => {
  131. this.application.selection.set(this.object)
  132. },
  133. 'select'
  134. )
  135. I18N.set(this.selectButton, 'title', 'button.select')
  136. I18N.set(this.selectButton, 'alt', 'button.select')
  137. this.propertiesElem = document.createElement('div')
  138. this.objectPanelElem.appendChild(this.propertiesElem)
  139. this.propertiesElem.className = 'inspector_properties'
  140. this.listPanelElem = document.createElement('div')
  141. this.bodyElem.appendChild(this.listPanelElem)
  142. this.listPanelElem.className = 'inspector_card'
  143. this.objectIndex = -1
  144. this.anchoredSectionName = null
  145. this.edition = {
  146. object: null,
  147. propertyName: null,
  148. renderer: null,
  149. editor: null,
  150. propElem: null
  151. }
  152. application.addEventListener('selection', event => {
  153. this.showSelectedObjects(event.objects)
  154. if (event.objects.length === 1) {
  155. this.showProperties(event.objects[0])
  156. }
  157. })
  158. application.addEventListener('scene', event => {
  159. if (event.type === 'nodeChanged' && this.object && event.objects.includes(this.object) && event.source !== this) {
  160. if (event.properties instanceof Array) {
  161. for (let propertyName of event.properties) {
  162. this.updateProperty(this.object, propertyName)
  163. }
  164. } else {
  165. this.showProperties(this.object)
  166. }
  167. }
  168. })
  169. this.title = 'tool.inspector.label'
  170. }
  171. showSelectedObjects(objects) {
  172. this.listPanelElem.style.display = ''
  173. this.objectPanelElem.style.display = 'none'
  174. this.propertiesElem.innerHTML = ''
  175. this.listPanelElem.innerHTML = ''
  176. this.objects = objects
  177. this.objectIndex = objects.length >= 0 ? 0 : -1
  178. const infoElem = document.createElement('div')
  179. infoElem.className = 'inspector_info'
  180. I18N.set(infoElem, 'innerHTML', 'message.objects_selected', objects.length)
  181. this.application.i18n.update(infoElem)
  182. this.listPanelElem.appendChild(infoElem)
  183. const selectionTree = new Tree(this.listPanelElem)
  184. for (let i = 0; i < objects.length; i++) {
  185. let object = objects[i]
  186. let label = object.name || object.id
  187. let className = this.getObjectClassNames(object)
  188. selectionTree.addNode(
  189. label,
  190. event => {
  191. this.showProperties(object)
  192. this.centerObject()
  193. },
  194. className
  195. )
  196. }
  197. }
  198. showProperties(object) {
  199. if (this.edition.object) {
  200. // edition in progress
  201. this.stopEdition()
  202. }
  203. let objectChanged
  204. if (this.object !== object) {
  205. this.object = object
  206. objectChanged = true
  207. } else {
  208. objectChanged = false
  209. }
  210. this.objectIndex = this.objects.indexOf(object)
  211. this.updateToolBar()
  212. this.propertiesElem.innerHTML = ''
  213. this.objectPanelElem.style.display = ''
  214. this.listPanelElem.style.display = 'none'
  215. let anchoredSectionElem = null
  216. if (object) {
  217. let topListElem = document.createElement('ul')
  218. topListElem.className = 'inspector'
  219. this.propertiesElem.appendChild(topListElem)
  220. if (this.state[this.objectSectionName] === undefined) {
  221. this.state[this.objectSectionName] = 'expanded'
  222. }
  223. // object
  224. let objListElem = this.createSection(this.objectSectionName, topListElem, [this.createClearAnchorSectionAction()])
  225. this.createReadOnlyProperty(objListElem, object, 'id')
  226. for (let propertyName in object) {
  227. if (this.isSupportedProperty(propertyName)) {
  228. if (this.isReadOnlyProperty(propertyName)) {
  229. this.createReadOnlyProperty(objListElem, object, propertyName)
  230. } else {
  231. this.createWriteableProperty(objListElem, object, propertyName)
  232. }
  233. }
  234. }
  235. if (object instanceof Solid) {
  236. this.createWriteableProperty(objListElem, object, 'edgesVisible')
  237. this.createWriteableProperty(objListElem, object, 'facesVisible')
  238. this.createWriteableProperty(objListElem, object, 'castShadow')
  239. this.createWriteableProperty(objListElem, object, 'receiveShadow')
  240. }
  241. let material = object.material
  242. if (material) {
  243. // material
  244. if (this.state[this.materialSectionName] === undefined) {
  245. this.state[this.materialSectionName] = 'expanded'
  246. }
  247. let materialListElem = this.createSection(this.materialSectionName, topListElem, [this.createChangeMaterialAction(object)])
  248. for (let propertyName of Inspector.mainMaterialProperties) {
  249. if (propertyName in material) {
  250. if (this.isReadOnlyProperty(propertyName) || material === Solid.EdgeMaterial || material === Solid.FaceMaterial) {
  251. this.createReadOnlyProperty(materialListElem, material, propertyName)
  252. } else {
  253. if (propertyName === 'map') {
  254. this.createWriteableProperty(materialListElem, material, propertyName, this.renderers.TextureRenderer, this.editors.TextureEditor)
  255. } else {
  256. this.createWriteableProperty(materialListElem, material, propertyName)
  257. }
  258. }
  259. }
  260. }
  261. }
  262. // builder
  263. if (this.state[this.builderSectionName] === undefined) {
  264. this.state[this.builderSectionName] = 'expanded'
  265. }
  266. let builderListElem = this.createSection(this.builderSectionName, topListElem, [this.createSetBuilderAction(object)])
  267. let builder = object.builder
  268. if (builder) {
  269. this.createReadOnlyProperty(builderListElem, builder.constructor, 'type', builder.constructor.name)
  270. for (let propertyName in builder) {
  271. let propertyValue = builder[propertyName]
  272. if (this.isSupportedProperty(propertyName)) {
  273. if (propertyValue !== null) {
  274. this.createWriteableProperty(builderListElem, builder, propertyName)
  275. }
  276. }
  277. }
  278. }
  279. // formulas
  280. if (this.state[this.formulasSectionName] === undefined) {
  281. this.state[this.formulasSectionName] = 'expanded'
  282. }
  283. let formulasListElem = this.createSection(this.formulasSectionName, topListElem, [this.createAddFormulaAction(object)])
  284. const formulas = Formula.getAll(object)
  285. for (let path in formulas) {
  286. this.createWriteableProperty(formulasListElem, formulas, path)
  287. }
  288. // geometry
  289. let geometry = object.geometry
  290. if (geometry) {
  291. if (this.state[this.geometrySectionName] === undefined) {
  292. this.state[this.geometrySectionName] = 'expanded'
  293. }
  294. let geomListElem = this.createSection(this.geometrySectionName, topListElem)
  295. this.createReadOnlyProperty(geomListElem, geometry, 'id')
  296. this.createReadOnlyProperty(geomListElem, geometry, 'uuid')
  297. this.createReadOnlyProperty(geomListElem, geometry, 'type')
  298. if (geometry instanceof SolidGeometry) {
  299. this.createReadOnlyProperty(geomListElem, geometry, 'vertices', geometry.vertices.length)
  300. this.createReadOnlyProperty(geomListElem, geometry, 'faces', geometry.faces.length)
  301. this.createReadOnlyProperty(geomListElem, geometry, 'isManifold')
  302. this.createReadOnlyProperty(geomListElem, geometry, 'smoothAngle')
  303. } else if (geometry instanceof THREE.BufferGeometry) {
  304. for (let name in geometry.attributes) {
  305. this.createReadOnlyProperty(geomListElem, geometry.attributes[name], name, geometry.attributes[name].array.length)
  306. }
  307. }
  308. }
  309. // userData
  310. let userData = object.userData
  311. if (this.state[this.propertiesSectionName] === undefined) {
  312. this.state[this.propertiesSectionName] = 'expanded'
  313. }
  314. let propListElem = this.createSection(this.propertiesSectionName, topListElem, [this.createAddPropertyAction(object, userData), this.createEditPropertiesAction(object)])
  315. for (let propertyName in userData) {
  316. let propertyValue = userData[propertyName]
  317. if (propertyValue !== null && typeof propertyValue === 'object') {
  318. let dictName = propertyName
  319. let dictionary = propertyValue
  320. if (this.state[dictName] === undefined) {
  321. this.state[dictName] = 'expanded'
  322. }
  323. let dictListElem = this.createSection(dictName, propListElem, [this.createAddPropertyAction(object, dictionary), this.createAnchorSectionAction(object, dictName)])
  324. for (let dictPropertyName in dictionary) {
  325. this.createWriteableProperty(dictListElem, dictionary, dictPropertyName)
  326. }
  327. if (dictName === this.anchoredSectionName) {
  328. anchoredSectionElem = dictListElem.parentElement
  329. }
  330. } else {
  331. this.createWriteableProperty(propListElem, userData, propertyName)
  332. }
  333. }
  334. //controllers
  335. // let controllers = object.controllers
  336. // if (this.state[this.controllersSectionName] === undefined) {
  337. // this.state[this.controllersSectionName] = 'expanded'
  338. // }
  339. // let controllersListElem = this.createSection(this.controllersSectionName, topListElem, [this.createAddControllerAction(object)])
  340. // if (controllers) {
  341. // for (let name in controllers) {
  342. // let controller = controllers[name]
  343. // if (this.state[name] === undefined) {
  344. // this.state[name] = 'expanded'
  345. // }
  346. // let controlListElem = this.createSection(name, controllersListElem, [
  347. // this.createStartControllerAction(controller),
  348. // this.createStopControllerAction(controller),
  349. // this.createRemoveControllerAction(controller),
  350. // this.createAnchorSectionAction(object, name)
  351. // ])
  352. // this.createReadOnlyProperty(controlListElem, controller, 'type', controller.constructor.name)
  353. // this.createReadOnlyProperty(controlListElem, controller, 'started', controller.isStarted())
  354. // for (let propertyName in controller) {
  355. // if (propertyName !== 'name' && propertyName !== 'object' && !propertyName.startsWith('_')) {
  356. // this.createWriteableProperty(controlListElem, controller, propertyName)
  357. // }
  358. // }
  359. // if (name === this.anchoredSectionName) {
  360. // anchoredSectionElem = controlListElem.parentElement
  361. // }
  362. // }
  363. // }
  364. this.application.i18n.updateTree(this.objectPanelElem)
  365. if (objectChanged) {
  366. this.markAnchoredSection(anchoredSectionElem)
  367. }
  368. }
  369. }
  370. getObjectClassNames(object) {
  371. let classList = [object.type]
  372. if (object.userData.IFC && object.userData.IFC.ifcClassName) {
  373. classList.push(object.userData.IFC.ifcClassName)
  374. }
  375. if (object.builder) {
  376. classList.push(object.builder.constructor.name)
  377. }
  378. return classList.join(' ')
  379. }
  380. updateToolBar() {
  381. this.indexElem.innerHTML = this.objectIndex + 1 + ' / ' + this.objects.length
  382. const objectCount = this.objects.length
  383. if (objectCount < 2) {
  384. this.toolBarElem.style.display = 'none'
  385. this.propertiesElem.style.top = '0px'
  386. } else {
  387. this.toolBarElem.style.display = ''
  388. this.propertiesElem.style.top = ''
  389. this.previousButton.disabled = this.objectIndex <= 0
  390. this.nextButton.disabled = this.objectIndex >= objectCount - 1
  391. }
  392. }
  393. centerObject() {
  394. const application = this.application
  395. const object = this.object
  396. if (object) {
  397. const container = application.container
  398. const aspect = container.clientWidth / container.clientHeight
  399. const camera = application.camera
  400. ObjectUtils.zoomAll(camera, [object], aspect, true)
  401. application.notifyObjectsChanged(camera, this)
  402. }
  403. }
  404. createSection(name, parentElem, actions = null) {
  405. let labelListener = event => {
  406. let labelElem = event.srcElement || event.target
  407. labelElem.className = labelElem.className === 'expand' ? 'collapse' : 'expand'
  408. let listElem = labelElem.parentNode.querySelector('ul')
  409. listElem.className = listElem.className === 'expanded' ? 'collapsed' : 'expanded'
  410. let sectionName = labelElem.id.substring(8)
  411. this.state[sectionName] = listElem.className
  412. }
  413. let sectionElem = document.createElement('li')
  414. sectionElem.className = 'section'
  415. parentElem.appendChild(sectionElem)
  416. if (this.state[name] === 'expanded' && this.firstExpandedSectionElem === null) {
  417. this.firstExpandedSectionElem = sectionElem
  418. }
  419. let labelElem = document.createElement('a')
  420. labelElem.href = '#'
  421. labelElem.id = 'section-' + name
  422. labelElem.innerHTML = name
  423. sectionElem.appendChild(labelElem)
  424. labelElem.className = this.state[name] === 'collapsed' ? 'expand' : 'collapse'
  425. labelElem.addEventListener('click', labelListener)
  426. if (actions instanceof Array) {
  427. for (let action of actions) {
  428. let actionElem = document.createElement('button')
  429. actionElem.className = action.className
  430. I18N.set(actionElem, 'alt', action.label)
  431. I18N.set(actionElem, 'title', action.label)
  432. actionElem.addEventListener('click', action.listener)
  433. sectionElem.appendChild(actionElem)
  434. }
  435. }
  436. let listElem = document.createElement('ul')
  437. listElem.className = this.state[name]
  438. sectionElem.appendChild(listElem)
  439. return listElem
  440. }
  441. createReadOnlyProperty(parentElem, object, propertyName, propertyValue = object[propertyName]) {
  442. this.createProperty(parentElem, object, propertyName, propertyValue)
  443. }
  444. createWriteableProperty(parentElem, object, propertyName, renderer, editor) {
  445. let propertyValue = object[propertyName]
  446. editor = editor || this.getEditor(propertyValue)
  447. this.createProperty(parentElem, object, propertyName, propertyValue, renderer, editor)
  448. }
  449. createProperty(parentElem, object, propertyName, propertyValue, renderer = null, editor = null) {
  450. renderer = renderer || this.getRenderer(propertyValue)
  451. if (renderer) {
  452. let propElem = document.createElement('li')
  453. if (object instanceof THREE.Object3D) {
  454. propElem.id = 'inspector_' + propertyName
  455. }
  456. propElem.className = 'property ' + renderer.getClassName(propertyValue)
  457. parentElem.appendChild(propElem)
  458. let labelElem = document.createElement('a')
  459. labelElem.href = '#'
  460. labelElem.innerHTML = propertyName + ':'
  461. labelElem.className = 'label'
  462. propElem.appendChild(labelElem)
  463. this.createValueElem(propElem, object, propertyName, propertyValue, renderer, editor)
  464. if (editor) {
  465. labelElem.addEventListener(
  466. 'click',
  467. event => {
  468. event.preventDefault()
  469. this.startEdition(propElem, object, propertyName, renderer, editor)
  470. },
  471. false
  472. )
  473. propElem.className += ' editable'
  474. }
  475. }
  476. }
  477. createValueElem(propElem, object, propertyName, propertyValue, renderer, editor) {
  478. let valueElem = renderer.render(propertyValue, !Boolean(editor))
  479. if (valueElem) {
  480. propElem.appendChild(valueElem)
  481. if (editor) {
  482. valueElem.addEventListener('click', () => this.startEdition(propElem, object, propertyName, renderer, editor), false)
  483. }
  484. }
  485. }
  486. createChangeMaterialAction(object) {
  487. const listener = () => {
  488. object.material = object.material.clone()
  489. this.application.notifyObjectsChanged(this.object, '')
  490. }
  491. return {
  492. className: 'button edit',
  493. label: 'button.change_material',
  494. listener: listener
  495. }
  496. }
  497. createSetBuilderAction(object) {
  498. const listener = () => {
  499. const dialog = new BuilderDialog(this.application, object)
  500. dialog.show()
  501. }
  502. return {
  503. className: 'button edit',
  504. label: 'button.object_builder',
  505. listener: listener
  506. }
  507. }
  508. createAddPropertyAction(object, dictionary) {
  509. const listener = () => {
  510. const dialog = new PropertyDialog(this.application, object, dictionary)
  511. dialog.show()
  512. }
  513. return {
  514. className: 'button add',
  515. label: 'button.add_property',
  516. listener: listener
  517. }
  518. }
  519. createEditPropertiesAction(object) {
  520. const listener = () => {
  521. const dialog = new PropertiesDialog(this.application, object)
  522. dialog.show()
  523. }
  524. return {
  525. className: 'button edit',
  526. label: 'button.edit_properties',
  527. listener: listener
  528. }
  529. }
  530. createClearAnchorSectionAction() {
  531. const listener = event => {
  532. this.anchoredSectionName = null
  533. this.markAnchoredSection()
  534. }
  535. return {
  536. className: 'button anchor',
  537. label: 'button.anchor_section',
  538. listener: listener
  539. }
  540. }
  541. createAnchorSectionAction(object, dictName) {
  542. const listener = event => {
  543. this.anchoredSectionName = dictName
  544. const elem = event.srcElement.parentElement
  545. this.markAnchoredSection(elem)
  546. }
  547. return {
  548. className: 'button anchor',
  549. label: 'button.anchor_section',
  550. listener: listener
  551. }
  552. }
  553. createAddFormulaAction(object) {
  554. const listener = () => {
  555. const dialog = new FormulaDialog(this.application, object)
  556. dialog.show()
  557. }
  558. return {
  559. className: 'button add',
  560. label: 'button.add_formula',
  561. listener: listener
  562. }
  563. }
  564. createAddControllerAction(object) {
  565. const listener = () => {
  566. const dialog = new ControllerDialog(this.application, object)
  567. dialog.show()
  568. }
  569. return {
  570. className: 'button add',
  571. label: 'button.add_controller',
  572. listener: listener
  573. }
  574. }
  575. createRemoveControllerAction(controller) {
  576. const listener = () => {
  577. ConfirmDialog.create('title.remove_controller', 'question.remove_controller', controller.name)
  578. .setAction(() => {
  579. controller.stop()
  580. let object = controller.object
  581. delete object.controllers[controller.name]
  582. this.showProperties(object)
  583. })
  584. .setI18N(this.application.i18n)
  585. .show()
  586. }
  587. return {
  588. className: 'button remove',
  589. label: 'button.remove_controller',
  590. listener: listener
  591. }
  592. }
  593. createStartControllerAction(controller) {
  594. const listener = () => {
  595. if (!controller.isStarted()) {
  596. controller.start()
  597. let object = controller.object
  598. this.showProperties(object)
  599. }
  600. }
  601. return {
  602. className: 'button start',
  603. label: 'button.start_controller',
  604. listener: listener
  605. }
  606. }
  607. createStopControllerAction(controller) {
  608. const listener = () => {
  609. if (controller.isStarted()) {
  610. controller.stop()
  611. let object = controller.object
  612. this.showProperties(object)
  613. }
  614. }
  615. return {
  616. className: 'button stop',
  617. label: 'button.stop_controller',
  618. listener: listener
  619. }
  620. }
  621. markAnchoredSection(sectionElem) {
  622. let anchored = this.propertiesElem.getElementsByClassName('anchored')
  623. for (let anchoredElem of anchored) {
  624. anchoredElem.classList.remove('anchored')
  625. }
  626. if (sectionElem) {
  627. sectionElem.firstChild.classList.add('anchored')
  628. this.propertiesElem.scrollTo(0, sectionElem.offsetTop)
  629. } else {
  630. this.propertiesElem.scrollTo(0, 0)
  631. }
  632. }
  633. startEdition(propElem, object, propertyName, renderer, editor) {
  634. if (this.edition.object !== null) {
  635. this.stopEdition()
  636. }
  637. let propertyValue = object[propertyName]
  638. this.edition.object = object
  639. this.edition.propertyName = propertyName
  640. this.edition.renderer = renderer
  641. this.edition.editor = editor
  642. this.edition.propElem = propElem
  643. let editorElem = editor.edit(object, propertyName, propertyValue)
  644. if (editorElem) {
  645. this.application.i18n.updateTree(editorElem)
  646. propElem.removeChild(propElem.lastChild)
  647. propElem.appendChild(editorElem)
  648. if (editorElem.focus) editorElem.focus()
  649. }
  650. }
  651. endEdition() {
  652. this.stopEdition()
  653. this.application.notifyObjectsChanged(this.object, this)
  654. }
  655. stopEdition() {
  656. let propElem = this.edition.propElem
  657. if (propElem === null || this.edition.object === null) return
  658. propElem.removeChild(propElem.lastChild)
  659. let propertyValue = this.edition.object[this.edition.propertyName]
  660. this.createValueElem(propElem, this.edition.object, this.edition.propertyName, propertyValue, this.edition.renderer, this.edition.editor)
  661. propElem.firstChild.focus()
  662. this.clearEdition()
  663. }
  664. clearEdition() {
  665. this.edition.object = null
  666. this.edition.propertyName = null
  667. this.edition.renderer = null
  668. this.edition.editor = null
  669. this.edition.propElem = null
  670. }
  671. updateProperty(object, propertyName, renderer) {
  672. let propElem = document.getElementById('inspector_' + propertyName)
  673. if (propElem) {
  674. propElem.removeChild(propElem.lastChild)
  675. let propertyValue = object[propertyName]
  676. renderer = renderer || this.getRenderer(propertyValue)
  677. this.createValueElem(propElem, object, propertyName, propertyValue, renderer)
  678. }
  679. }
  680. isSupportedProperty(propertyName) {
  681. if (propertyName[0] === '_') return false
  682. if (propertyName === 'material') return false
  683. return true
  684. }
  685. isReadOnlyProperty(propertyName) {
  686. if (propertyName === 'id') return true
  687. if (propertyName === 'uuid') return true
  688. if (propertyName === 'type') return true
  689. if (propertyName === 'matrixWorldNeedsUpdate') return true
  690. if (propertyName === 'needsRebuild') return true
  691. if (propertyName === 'needsMarking') return true
  692. if (propertyName.indexOf('is') === 0) return true
  693. return false
  694. }
  695. addRenderer(rendererClass) {
  696. this.renderers[rendererClass.name] = new rendererClass(this)
  697. }
  698. addEditor(editorClass) {
  699. this.editors[editorClass.name] = new editorClass(this)
  700. }
  701. getRenderer(value) {
  702. for (let rendererName in this.renderers) {
  703. let renderer = this.renderers[rendererName]
  704. if (renderer.isSupported(value)) {
  705. return renderer
  706. }
  707. }
  708. return null
  709. }
  710. getEditor(value) {
  711. for (let editorName in this.editors) {
  712. let editor = this.editors[editorName]
  713. if (editor.isSupported(value)) {
  714. return editor
  715. }
  716. }
  717. return null
  718. }
  719. }
  720. /* PropertyRenderers */
  721. class PropertyRenderer {
  722. constructor(inspector) {
  723. this.inspector = inspector
  724. }
  725. isSupported(value, type) {
  726. return false
  727. }
  728. getClassName(value) {
  729. return ''
  730. }
  731. render(
  732. value,
  733. disabled // returns elem
  734. ) {
  735. return null
  736. }
  737. }
  738. class StringRenderer extends PropertyRenderer {
  739. constructor(inspector) {
  740. super(inspector)
  741. }
  742. isSupported(value) {
  743. return typeof value === 'string'
  744. }
  745. getClassName(value) {
  746. return 'string'
  747. }
  748. render(text, disabled) {
  749. let valueElem = document.createElement('span')
  750. valueElem.className = 'value'
  751. if (disabled) valueElem.classList.add('disabled')
  752. valueElem.innerHTML = text
  753. return valueElem
  754. }
  755. }
  756. class NumberRenderer extends PropertyRenderer {
  757. constructor(inspector) {
  758. super(inspector)
  759. }
  760. isSupported(value) {
  761. return typeof value === 'number'
  762. }
  763. getClassName(value) {
  764. return 'number'
  765. }
  766. render(number, disabled) {
  767. let valueElem = document.createElement('span')
  768. valueElem.className = 'value'
  769. if (disabled) valueElem.classList.add('disabled')
  770. valueElem.innerHTML = Math.round(number * 1000) / 1000
  771. return valueElem
  772. }
  773. }
  774. class BooleanRenderer extends PropertyRenderer {
  775. constructor(inspector) {
  776. super(inspector)
  777. }
  778. isSupported(value) {
  779. return typeof value === 'boolean'
  780. }
  781. getClassName(value) {
  782. return 'boolean'
  783. }
  784. render(value, disabled) {
  785. let valueElem = document.createElement('span')
  786. valueElem.className = 'value'
  787. let checkboxElem = document.createElement('input')
  788. checkboxElem.type = 'checkbox'
  789. checkboxElem.checked = value
  790. checkboxElem.tabIndex = -1
  791. if (disabled) checkboxElem.disabled = true
  792. valueElem.appendChild(checkboxElem)
  793. return valueElem
  794. }
  795. }
  796. class VectorRenderer extends PropertyRenderer {
  797. constructor(inspector) {
  798. super(inspector)
  799. }
  800. isSupported(value) {
  801. return value instanceof THREE.Vector3
  802. }
  803. getClassName(value) {
  804. return 'vector'
  805. }
  806. render(vector, disabled) {
  807. let valueElem = document.createElement('span')
  808. valueElem.className = 'value'
  809. if (disabled) valueElem.classList.add('disabled')
  810. let round = function(value) {
  811. var precision = 1000
  812. return Math.round(precision * value) / precision
  813. }
  814. let out = '(' + round(vector.x) + ', ' + round(vector.y) + ', ' + round(vector.z) + ')'
  815. valueElem.innerHTML = out
  816. return valueElem
  817. }
  818. }
  819. class EulerRenderer extends PropertyRenderer {
  820. constructor(inspector) {
  821. super(inspector)
  822. }
  823. isSupported(value) {
  824. return value instanceof THREE.Euler
  825. }
  826. getClassName(value) {
  827. return 'euler'
  828. }
  829. render(euler, disabled) {
  830. let valueElem = document.createElement('span')
  831. valueElem.className = 'value'
  832. if (disabled) valueElem.classList.add('disabled')
  833. let angle = function(value) {
  834. var precision = 1000
  835. return Math.round(precision * THREE.MathUtils.radToDeg(value)) / precision
  836. }
  837. let out = '(' + angle(euler.x) + 'º, ' + angle(euler.y) + 'º, ' + angle(euler.z) + 'º)'
  838. valueElem.innerHTML = out
  839. return valueElem
  840. }
  841. }
  842. class FormulaRenderer extends PropertyRenderer {
  843. constructor(inspector) {
  844. super(inspector)
  845. }
  846. isSupported(value) {
  847. return value instanceof Formula
  848. }
  849. getClassName(value) {
  850. return 'formula'
  851. }
  852. render(formula, disabled) {
  853. let valueElem = document.createElement('span')
  854. valueElem.className = 'value'
  855. if (disabled) valueElem.classList.add('disabled')
  856. valueElem.innerHTML = formula.expression
  857. return valueElem
  858. }
  859. }
  860. class ColorRenderer extends PropertyRenderer {
  861. constructor(inspector) {
  862. super(inspector)
  863. }
  864. isSupported(value) {
  865. return value instanceof THREE.Color
  866. }
  867. getClassName(value) {
  868. return 'color'
  869. }
  870. render(color, disabled) {
  871. const rgb = 'rgb(' + Math.round(255 * color.r) + ', ' + Math.round(255 * color.g) + ', ' + Math.round(255 * color.b) + ')'
  872. let valueElem = document.createElement('span')
  873. valueElem.className = 'value'
  874. if (disabled) valueElem.classList.add('disabled')
  875. let codeElem = document.createElement('span')
  876. codeElem.innerHTML = rgb
  877. valueElem.appendChild(codeElem)
  878. let colorElem = document.createElement('span')
  879. colorElem.className = 'color'
  880. colorElem.style.backgroundColor = rgb
  881. colorElem.alt = rgb
  882. colorElem.title = rgb
  883. valueElem.appendChild(colorElem)
  884. return valueElem
  885. }
  886. }
  887. class Object3DRenderer extends PropertyRenderer {
  888. constructor(inspector) {
  889. super(inspector)
  890. }
  891. isSupported(value) {
  892. return value instanceof THREE.Object3D
  893. }
  894. getClassName(value) {
  895. return 'object3D'
  896. }
  897. render(object, disabled) {
  898. let valueElem = document.createElement('a')
  899. valueElem.href = '#'
  900. valueElem.className = 'value'
  901. valueElem.innerHTML = object.name || 'object-' + object.id
  902. valueElem.addEventListener('click', () => this.inspector.application.selection.set(object))
  903. return valueElem
  904. }
  905. }
  906. class TextureRenderer extends PropertyRenderer {
  907. constructor(inspector) {
  908. super(inspector)
  909. }
  910. isSupported(value) {
  911. return value instanceof THREE.Texture
  912. }
  913. getClassName(value) {
  914. return 'texture'
  915. }
  916. render(texture, disabled) {
  917. let valueElem = document.createElement('span')
  918. valueElem.className = 'value'
  919. if (texture) {
  920. if (texture.name) {
  921. valueElem.innerHTML = texture.name
  922. } else if (texture.image) {
  923. valueElem.innerHTML = texture.image.src
  924. } else {
  925. valueElem.innerHTML = ''
  926. }
  927. }
  928. return valueElem
  929. }
  930. }
  931. /* PropertyEditors */
  932. class PropertyEditor {
  933. constructor(inspector) {
  934. this.inspector = inspector
  935. }
  936. isSupported(value) {
  937. return false
  938. }
  939. edit(
  940. object,
  941. propertyName,
  942. value // returns the editor element
  943. ) {
  944. return null
  945. }
  946. }
  947. class StringEditor extends PropertyEditor {
  948. constructor(inspector) {
  949. super(inspector)
  950. }
  951. isSupported(value) {
  952. return typeof value === 'string'
  953. }
  954. edit(object, propertyName, text) {
  955. let valueElem = document.createElement('input')
  956. valueElem.className = 'value'
  957. valueElem.value = text
  958. valueElem.addEventListener(
  959. 'keydown',
  960. event => {
  961. if (event.keyCode === 13) {
  962. object[propertyName] = valueElem.value
  963. this.inspector.endEdition()
  964. } else if (event.keyCode === 27) {
  965. this.inspector.stopEdition()
  966. }
  967. },
  968. false
  969. )
  970. return valueElem
  971. }
  972. }
  973. class NumberEditor extends PropertyEditor {
  974. constructor(inspector) {
  975. super(inspector)
  976. }
  977. isSupported(value) {
  978. return typeof value === 'number'
  979. }
  980. edit(object, propertyName, number) {
  981. let valueElem = document.createElement('input')
  982. valueElem.className = 'value'
  983. valueElem.value = '' + number
  984. valueElem.type = 'number'
  985. valueElem.addEventListener(
  986. 'keydown',
  987. event => {
  988. if (event.keyCode === 13) {
  989. number = parseFloat(valueElem.value)
  990. if (!isNaN(number)) {
  991. object[propertyName] = number
  992. this.inspector.endEdition()
  993. }
  994. } else if (event.keyCode === 27) {
  995. this.inspector.stopEdition()
  996. }
  997. },
  998. false
  999. )
  1000. return valueElem
  1001. }
  1002. }
  1003. class BooleanEditor extends PropertyEditor {
  1004. constructor(inspector) {
  1005. super(inspector)
  1006. }
  1007. isSupported(value) {
  1008. return typeof value === 'boolean'
  1009. }
  1010. edit(object, propertyName, value) {
  1011. let checked = value
  1012. object[propertyName] = !checked
  1013. this.inspector.endEdition()
  1014. return null
  1015. }
  1016. }
  1017. class DimensionEditor extends PropertyEditor {
  1018. constructor(inspector) {
  1019. super(inspector)
  1020. }
  1021. formatValue(value) {
  1022. return value
  1023. }
  1024. createInstance(x, y, z) {
  1025. return { x: x, y: y, z: z }
  1026. }
  1027. edit(object, propertyName, vector) {
  1028. let dimId = 'dim_edit_'
  1029. const parseDimension = dim => {
  1030. let valueElem = document.getElementById(dimId + dim)
  1031. let value = valueElem.value
  1032. let num = parseFloat(value)
  1033. return isNaN(num) ? vector[dim] : num
  1034. }
  1035. const endEdition = () => {
  1036. let x = parseDimension('x')
  1037. let y = parseDimension('y')
  1038. let z = parseDimension('z')
  1039. object[propertyName].copy(this.createInstance(x, y, z))
  1040. if (object instanceof THREE.Object3D) {
  1041. object.updateMatrix()
  1042. }
  1043. this.inspector.endEdition()
  1044. }
  1045. const keyListener = event => {
  1046. if (event.keyCode === 13) {
  1047. endEdition()
  1048. } else if (event.keyCode === 27) {
  1049. this.inspector.stopEdition()
  1050. }
  1051. }
  1052. const createDimensionEditor = (vector, dim) => {
  1053. let itemElem = document.createElement('li')
  1054. let labelElem = document.createElement('label')
  1055. labelElem.innerHTML = dim + ':'
  1056. labelElem.htmlFor = dimId + dim
  1057. let valueElem = document.createElement('input')
  1058. valueElem.id = dimId + dim
  1059. valueElem.type = 'number'
  1060. valueElem.className = 'value'
  1061. valueElem.value = this.formatValue(vector[dim])
  1062. valueElem.addEventListener('keydown', keyListener, false)
  1063. itemElem.appendChild(labelElem)
  1064. itemElem.appendChild(valueElem)
  1065. return itemElem
  1066. }
  1067. let listElem = document.createElement('ul')
  1068. listElem.className = 'list_3'
  1069. listElem.appendChild(createDimensionEditor(vector, 'x'))
  1070. listElem.appendChild(createDimensionEditor(vector, 'y'))
  1071. listElem.appendChild(createDimensionEditor(vector, 'z'))
  1072. listElem.focus = () => document.getElementById(dimId + 'x').focus()
  1073. return listElem
  1074. }
  1075. }
  1076. class VectorEditor extends DimensionEditor {
  1077. constructor(inspector) {
  1078. super(inspector)
  1079. }
  1080. isSupported(value) {
  1081. return value instanceof THREE.Vector3
  1082. }
  1083. createInstance(x, y, z) {
  1084. return new THREE.Vector3(x, y, z)
  1085. }
  1086. }
  1087. class EulerEditor extends DimensionEditor {
  1088. constructor(inspector) {
  1089. super(inspector)
  1090. }
  1091. isSupported(value) {
  1092. return value instanceof THREE.Euler
  1093. }
  1094. formatValue(value) {
  1095. const precision = 10000000
  1096. return Math.round(precision * THREE.MathUtils.radToDeg(value)) / precision
  1097. }
  1098. createInstance(x, y, z) {
  1099. let xrad = THREE.MathUtils.degToRad(x)
  1100. let yrad = THREE.MathUtils.degToRad(y)
  1101. let zrad = THREE.MathUtils.degToRad(z)
  1102. return new THREE.Euler(xrad, yrad, zrad, 'XYZ')
  1103. }
  1104. }
  1105. class FormulaEditor extends PropertyEditor {
  1106. constructor(inspector) {
  1107. super(inspector)
  1108. }
  1109. isSupported(value) {
  1110. return value instanceof Formula
  1111. }
  1112. edit(object, propertyName, formula) {
  1113. const inspector = this.inspector
  1114. const dialog = new FormulaDialog(inspector.application, inspector.object, formula)
  1115. dialog.show()
  1116. this.inspector.clearEdition()
  1117. return null
  1118. }
  1119. }
  1120. class ColorEditor extends PropertyEditor {
  1121. constructor(inspector) {
  1122. super(inspector)
  1123. }
  1124. isSupported(value) {
  1125. return value instanceof THREE.Color
  1126. }
  1127. edit(object, propertyName, color) {
  1128. const groupElem = document.createElement('span')
  1129. groupElem.className = 'value'
  1130. const rgb = 'rgb(' + Math.round(255 * color.r) + ', ' + Math.round(255 * color.g) + ', ' + Math.round(255 * color.b) + ')'
  1131. let codeElem = document.createElement('span')
  1132. codeElem.innerHTML = rgb
  1133. codeElem.style.color = '#6060c0'
  1134. codeElem.addEventListener('click', () => {
  1135. colorElem.focus()
  1136. colorElem.click()
  1137. })
  1138. groupElem.appendChild(codeElem)
  1139. let hexString = '#' + color.getHexString()
  1140. const sampleElem = document.createElement('label')
  1141. sampleElem.className = 'color'
  1142. sampleElem.style.backgroundColor = hexString
  1143. sampleElem.alt = rgb
  1144. sampleElem.title = rgb
  1145. sampleElem.style.borderColor = '#6060c0'
  1146. groupElem.appendChild(sampleElem)
  1147. const colorElem = document.createElement('input')
  1148. colorElem.className = 'value'
  1149. colorElem.type = 'color'
  1150. colorElem.value = '#' + color.getHexString()
  1151. colorElem.style.visibility = 'hidden'
  1152. colorElem.style.width = '0'
  1153. colorElem.style.height = '0'
  1154. sampleElem.appendChild(colorElem)
  1155. colorElem.addEventListener('change', () => {
  1156. object[propertyName].set(colorElem.value)
  1157. document.body.removeEventListener('keydown', keyDownListener)
  1158. document.body.removeEventListener('pointerdown', pointerDownListener)
  1159. this.inspector.endEdition()
  1160. })
  1161. const cancel = () => {
  1162. if (colorElem.value.toLowerCase() !== '#' + color.getHexString()) return // ignore: it is a change
  1163. document.body.removeEventListener('keydown', keyDownListener)
  1164. document.body.removeEventListener('pointerdown', pointerDownListener)
  1165. this.inspector.stopEdition()
  1166. }
  1167. const keyDownListener = event => {
  1168. if (event.keyCode === 27) cancel()
  1169. }
  1170. const pointerDownListener = event => {
  1171. if (event.srcElement.parentNode !== groupElem) cancel()
  1172. }
  1173. document.body.addEventListener('keydown', keyDownListener)
  1174. document.body.addEventListener('pointerdown', pointerDownListener)
  1175. groupElem.focus = () => {
  1176. colorElem.focus()
  1177. colorElem.click()
  1178. }
  1179. return groupElem
  1180. }
  1181. }
  1182. class TextureEditor extends PropertyEditor {
  1183. constructor(inspector) {
  1184. super(inspector)
  1185. }
  1186. isSupported(value) {
  1187. return value instanceof THREE.Texture
  1188. }
  1189. edit(material, propertyName, texture) {
  1190. let valueElem = document.createElement('input')
  1191. valueElem.className = 'value'
  1192. let value = ''
  1193. if (texture) {
  1194. if (texture.name) {
  1195. value = texture.name
  1196. } else if (texture.image) {
  1197. value = texture.image.src
  1198. }
  1199. }
  1200. valueElem.value = value
  1201. valueElem.addEventListener(
  1202. 'keydown',
  1203. event => {
  1204. if (event.keyCode === 13) {
  1205. let imagePath = valueElem.value.trim()
  1206. if (value === imagePath) {
  1207. this.inspector.stopEdition()
  1208. } else {
  1209. if (imagePath === '') {
  1210. if (material[propertyName] !== null) material.needsUpdate = true
  1211. material[propertyName] = null
  1212. this.inspector.endEdition()
  1213. } else {
  1214. const manager = this.inspector.application.loadingManager
  1215. if (material[propertyName] === null) material.needsUpdate = true
  1216. const textureLoader = new THREE.TextureLoader(manager)
  1217. const newTexture = textureLoader.load(imagePath, () => this.inspector.endEdition())
  1218. newTexture.name = imagePath
  1219. material[propertyName] = newTexture
  1220. }
  1221. if (texture) texture.dispose()
  1222. }
  1223. } else if (event.keyCode === 27) {
  1224. this.inspector.stopEdition()
  1225. }
  1226. },
  1227. false
  1228. )
  1229. return valueElem
  1230. }
  1231. }
  1232. export { Inspector }