ObjectPropertyUI.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. """
  2. UI for object property control
  3. """
  4. import wx
  5. import os
  6. from wx.lib.scrolledpanel import ScrolledPanel
  7. from wx.lib.agw.cubecolourdialog import *
  8. from direct.wxwidgets.WxSlider import *
  9. from pandac.PandaModules import *
  10. import ObjectGlobals as OG
  11. class AnimFileDrop(wx.FileDropTarget):
  12. def __init__(self, editor):
  13. wx.FileDropTarget.__init__(self)
  14. self.editor = editor
  15. def OnDropFiles(self, x, y, filenames):
  16. obj = self.editor.objectMgr.findObjectByNodePath(base.direct.selected.last)
  17. if obj is None:
  18. return
  19. objDef = obj[OG.OBJ_DEF]
  20. if not objDef.actor:
  21. return
  22. objNP = obj[OG.OBJ_NP]
  23. for filename in filenames:
  24. name = os.path.basename(filename)
  25. animName = Filename.fromOsSpecific(filename).getFullpath()
  26. if name.endswith('.mb') or\
  27. name.endswith('.ma'):
  28. self.editor.convertMaya(animName, self.editor.ui.protoPaletteUI.addNewItem, obj, isAnim=True)
  29. return
  30. if animName not in objDef.anims:
  31. objDef.anims.append(animName)
  32. objNP.loadAnims({name:animName})
  33. objNP.loop(name)
  34. obj[OG.OBJ_ANIM] = animName
  35. self.editor.ui.objectPropertyUI.updateProps(obj)
  36. class ObjectPropUI(wx.Panel):
  37. """
  38. Base class for ObjectPropUIs,
  39. It consists of label area and ui area.
  40. """
  41. def __init__(self, parent, label):
  42. wx.Panel.__init__(self, parent)
  43. self.parent = parent
  44. self.labelPane = wx.Panel(self)
  45. self.label = wx.StaticText(self.labelPane, label=label)
  46. self.labelSizer = wx.BoxSizer(wx.HORIZONTAL)
  47. self.labelSizer.Add(self.label)
  48. self.labelPane.SetSizer(self.labelSizer)
  49. self.uiPane = wx.Panel(self)
  50. sizer = wx.BoxSizer(wx.VERTICAL)
  51. sizer.Add(self.labelPane)
  52. sizer.Add(self.uiPane, 1, wx.EXPAND, 0)
  53. self.SetSizer(sizer)
  54. def setValue(self, value):
  55. self.ui.SetValue(value)
  56. def getValue(self):
  57. return self.ui.GetValue()
  58. def bindFunc(self, inFunc, outFunc, valFunc = None):
  59. self.ui.Bind(wx.EVT_ENTER_WINDOW, inFunc)
  60. self.ui.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
  61. if valFunc:
  62. self.ui.Bind(self.eventType, valFunc)
  63. class ObjectPropUIEntry(ObjectPropUI):
  64. """ UI for string value properties """
  65. def __init__(self, parent, label):
  66. ObjectPropUI.__init__(self, parent, label)
  67. self.ui = wx.TextCtrl(self.uiPane, -1)
  68. self.eventType = wx.EVT_TEXT_ENTER
  69. self.Layout()
  70. def setValue(self, value):
  71. self.ui.SetValue(str(value))
  72. class ObjectPropUISlider(ObjectPropUI):
  73. """ UI for float value properties """
  74. def __init__(self, parent, label, value, minValue, maxValue):
  75. ObjectPropUI.__init__(self, parent, label)
  76. self.ui = WxSlider(self.uiPane, -1, value, minValue, maxValue,
  77. pos = (0,0), size=(140, -1),
  78. style=wx.SL_HORIZONTAL | wx.SL_LABELS)
  79. self.ui.Enable()
  80. self.Layout()
  81. def bindFunc(self, inFunc, outFunc, valFunc = None):
  82. self.ui.Bind(wx.EVT_ENTER_WINDOW, inFunc)
  83. self.ui.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
  84. self.ui.textValue.Bind(wx.EVT_ENTER_WINDOW, inFunc)
  85. self.ui.textValue.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
  86. if valFunc:
  87. self.ui.bindFunc(valFunc)
  88. class ObjectPropUISpinner(ObjectPropUI):
  89. """ UI for int value properties """
  90. def __init__(self, parent, label, value, minValue, maxValue):
  91. ObjectPropUI.__init__(self, parent, label)
  92. self.ui = wx.SpinCtrl(self.uiPane, -1, "", min=minValue, max=maxValue, initial=value)
  93. self.eventType = wx.EVT_SPIN
  94. self.Layout()
  95. class ObjectPropUICheck(ObjectPropUI):
  96. def __init__(self, parent, label, value):
  97. ObjectPropUI.__init__(self, parent, label)
  98. self.ui = wx.CheckBox(self.uiPane, -1, "", size=(50, 30))
  99. self.setValue(value)
  100. self.eventType = wx.EVT_CHECKBOX
  101. self.Layout()
  102. class ObjectPropUIRadio(ObjectPropUI):
  103. def __init__(self, parent, label, value, valueList):
  104. ObjectPropUI.__init__(self, parent, label)
  105. self.ui = wx.RadioBox(self.uiPane, -1, "", choices=valueList, majorDimension=1, style=wx.RA_SPECIFY_COLS)
  106. self.setValue(value)
  107. self.eventType = wx.EVT_RADIOBOX
  108. self.Layout()
  109. def setValue(self, value):
  110. self.ui.SetStringSelection(value)
  111. def getValue(self):
  112. return self.ui.GetStringSelection()
  113. class ObjectPropUICombo(ObjectPropUI):
  114. def __init__(self, parent, label, value, valueList, obj=None, callBack=None):
  115. ObjectPropUI.__init__(self, parent, label)
  116. self.ui = wx.Choice(self.uiPane, -1, choices=valueList)
  117. if callBack is not None:
  118. button = wx.Button(self.labelPane, -1, 'Update', size = (100, 18))
  119. button.Bind(wx.EVT_BUTTON, lambda p0=None, p1=obj, p2=self: callBack(p0, p1, p2))
  120. self.labelSizer.Add(button)
  121. self.setValue(value)
  122. self.eventType = wx.EVT_CHOICE
  123. self.Layout()
  124. def setValue(self, value):
  125. self.ui.SetStringSelection(value)
  126. def getValue(self):
  127. return self.ui.GetStringSelection()
  128. def setItems(self, valueList):
  129. self.ui.SetItems(valueList)
  130. class ColorPicker(CubeColourDialog):
  131. def __init__(self, parent, colourData=None, style=CCD_SHOW_ALPHA, alpha = 255, updateCB=None, exitCB=None):
  132. self.updateCB=updateCB
  133. CubeColourDialog.__init__(self, parent, colourData, style)
  134. self.okButton.Hide()
  135. self.cancelButton.Hide()
  136. self._colour.alpha = alpha
  137. self.alphaSpin.SetValue(self._colour.alpha)
  138. self.DrawAlpha()
  139. if exitCB:
  140. self.Bind(wx.EVT_CLOSE, exitCB)
  141. def SetPanelColours(self):
  142. self.oldColourPanel.RefreshColour(self._oldColour)
  143. self.newColourPanel.RefreshColour(self._colour)
  144. if self.updateCB:
  145. self.updateCB(self._colour.r, self._colour.g, self._colour.b, self._colour.alpha)
  146. class ObjectPropertyUI(ScrolledPanel):
  147. def __init__(self, parent, editor):
  148. self.editor = editor
  149. self.colorPicker = None
  150. self.lastColorPickerPos = None
  151. self.lastPropTab = None
  152. ScrolledPanel.__init__(self, parent)
  153. parentSizer = wx.BoxSizer(wx.VERTICAL)
  154. parentSizer.Add(self, 1, wx.EXPAND, 0)
  155. parent.SetSizer(parentSizer); parent.Layout()
  156. self.SetDropTarget(AnimFileDrop(self.editor))
  157. def clearPropUI(self):
  158. sizer = self.GetSizer()
  159. if sizer is not None:
  160. self.lastPropTab = self.nb.GetCurrentPage().GetName()
  161. sizer.Remove(self.propPane)
  162. self.propPane.Destroy()
  163. self.SetSizer(None)
  164. self.Layout()
  165. self.SetupScrolling(self, scroll_y = True, rate_y = 20)
  166. def colorPickerExitCB(self, evt=None):
  167. self.lastColorPickerPos = self.colorPicker.GetPosition()
  168. self.colorPicker.Destroy()
  169. self.colorPicker = None
  170. def colorPickerUpdateCB(self, rr, gg, bb, aa):
  171. r = rr / 255.0
  172. g = gg / 255.0
  173. b = bb / 255.0
  174. a = aa / 255.0
  175. self.propCR.setValue(r)
  176. self.propCG.setValue(g)
  177. self.propCB.setValue(b)
  178. self.propCA.setValue(a)
  179. self.editor.objectMgr.updateObjectColor(r, g, b, a)
  180. def onColorSlider(self, evt):
  181. r = float(self.editor.ui.objectPropertyUI.propCR.getValue())
  182. g = float(self.editor.ui.objectPropertyUI.propCG.getValue())
  183. b = float(self.editor.ui.objectPropertyUI.propCB.getValue())
  184. a = float(self.editor.ui.objectPropertyUI.propCA.getValue())
  185. if self.colorPicker:
  186. evtObj = evt.GetEventObject()
  187. if evtObj == self.propCR.ui or\
  188. evtObj == self.propCR.ui.textValue:
  189. self.colorPicker.redSpin.SetValue(r * 255)
  190. self.colorPicker.AssignColourValue('r', r * 255, 255, 0)
  191. elif evtObj == self.propCG.ui or\
  192. evtObj == self.propCG.ui.textValue:
  193. self.colorPicker.greenSpin.SetValue(g * 255)
  194. self.colorPicker.AssignColourValue('g', g * 255, 255, 0)
  195. elif evtObj == self.propCB.ui or\
  196. evtObj == self.propCB.ui.textValue:
  197. self.colorPicker.blueSpin.SetValue(b * 255)
  198. self.colorPicker.AssignColourValue('b', b * 255, 255, 0)
  199. else:
  200. self.colorPicker._colour.alpha = a * 255
  201. self.colorPicker.alphaSpin.SetValue(self.colorPicker._colour.alpha)
  202. self.colorPicker.DrawAlpha()
  203. self.editor.objectMgr.updateObjectColor(r, g, b, a)
  204. def openColorPicker(self, evt, colourData, alpha):
  205. if self.colorPicker:
  206. self.lastColorPickerPos = self.colorPicker.GetPosition()
  207. self.colorPicker.Destroy()
  208. self.colorPicker = ColorPicker(self, colourData, alpha=alpha, updateCB=self.colorPickerUpdateCB, exitCB=self.colorPickerExitCB)
  209. self.colorPicker.GetColourData().SetChooseFull(True)
  210. self.colorPicker.Show()
  211. if self.lastColorPickerPos:
  212. self.colorPicker.SetPosition(self.lastColorPickerPos)
  213. def updateProps(self, obj, movable=True):
  214. self.clearPropUI()
  215. self.propPane = wx.Panel(self)
  216. mainSizer = wx.BoxSizer(wx.VERTICAL)
  217. mainSizer.Add(self.propPane, 1, wx.EXPAND, 0)
  218. self.SetSizer(mainSizer)
  219. self.nb = wx.Notebook(self.propPane, style=wx.NB_BOTTOM)
  220. sizer = wx.BoxSizer(wx.VERTICAL)
  221. sizer.Add(self.nb, 1, wx.EXPAND)
  222. self.propPane.SetSizer(sizer)
  223. self.transformPane = wx.Panel(self.nb, -1, name='Transform')
  224. self.nb.AddPage(self.transformPane, 'Transform')
  225. self.propX = ObjectPropUIEntry(self.transformPane, 'X')
  226. self.propY = ObjectPropUIEntry(self.transformPane, 'Y')
  227. self.propZ = ObjectPropUIEntry(self.transformPane, 'Z')
  228. self.propH = ObjectPropUISlider(self.transformPane, 'H', 0, 0, 360)
  229. self.propP = ObjectPropUISlider(self.transformPane, 'P', 0, 0, 360)
  230. self.propR = ObjectPropUISlider(self.transformPane, 'R', 0, 0, 360)
  231. self.propSX = ObjectPropUIEntry(self.transformPane, 'SX')
  232. self.propSY = ObjectPropUIEntry(self.transformPane, 'SY')
  233. self.propSZ = ObjectPropUIEntry(self.transformPane, 'SZ')
  234. transformProps = [self.propX, self.propY, self.propZ, self.propH, self.propP, self.propR,
  235. self.propSX, self.propSY, self.propSZ]
  236. sizer = wx.BoxSizer(wx.VERTICAL)
  237. sizer.AddMany(transformProps)
  238. self.transformPane.SetSizer(sizer)
  239. for transformProp in transformProps:
  240. transformProp.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
  241. self.editor.objectMgr.onLeaveObjectPropUI,
  242. self.editor.objectMgr.updateObjectTransform)
  243. if not movable:
  244. for transformProp in transformProps:
  245. transformProp.ui.Disable()
  246. self.lookPane = wx.Panel(self.nb, -1, name='Look')
  247. self.nb.AddPage(self.lookPane, 'Look')
  248. objNP = obj[OG.OBJ_NP]
  249. objRGBA = obj[OG.OBJ_RGBA]
  250. self.propCR = ObjectPropUISlider(self.lookPane, 'CR', objRGBA[0], 0, 1)
  251. self.propCG = ObjectPropUISlider(self.lookPane, 'CG', objRGBA[1], 0, 1)
  252. self.propCB = ObjectPropUISlider(self.lookPane, 'CB', objRGBA[2], 0, 1)
  253. self.propCA = ObjectPropUISlider(self.lookPane, 'CA', objRGBA[3], 0, 1)
  254. colorProps = [self.propCR, self.propCG, self.propCB, self.propCA]
  255. for colorProp in colorProps:
  256. colorProp.ui.bindFunc(self.onColorSlider)
  257. sizer = wx.BoxSizer(wx.VERTICAL)
  258. sizer.AddMany(colorProps)
  259. button = wx.Button(self.lookPane, -1, 'Color Picker', (0,0), (140, 20))
  260. _colourData = wx.ColourData()
  261. _colourData.SetColour(wx.Colour(objRGBA[0] * 255, objRGBA[1] * 255, objRGBA[2] * 255))
  262. button.Bind(wx.EVT_BUTTON, lambda p0=None, p1=_colourData, p2=objRGBA[3] * 255: self.openColorPicker(p0, p1, p2))
  263. sizer.Add(button)
  264. if self.colorPicker:
  265. self.openColorPicker(None, _colourData, objRGBA[3] * 255)
  266. objDef = obj[OG.OBJ_DEF]
  267. if objDef.updateModelFunction is not None or (objDef.model is not None and len(objDef.models) > 0):
  268. defaultModel = obj[OG.OBJ_MODEL]
  269. if defaultModel is None:
  270. defaultModel = ''
  271. if len(objDef.models) == 0:
  272. modelList = ''
  273. else:
  274. modelList = objDef.models
  275. propUI = ObjectPropUICombo(self.lookPane, 'model', defaultModel, modelList, obj, callBack=objDef.updateModelFunction)
  276. sizer.Add(propUI)
  277. propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
  278. self.editor.objectMgr.onLeaveObjectPropUI,
  279. lambda p0=None, p1=obj: self.editor.objectMgr.updateObjectModelFromUI(p0, p1))
  280. animList = objDef.animDict.get(obj[OG.OBJ_MODEL])
  281. if len(objDef.anims) > 0 or animList:
  282. if animList is None:
  283. animList = objDef.anims
  284. propUI = ObjectPropUICombo(self.lookPane, 'anim', obj[OG.OBJ_ANIM], animList)
  285. sizer.Add(propUI)
  286. propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
  287. self.editor.objectMgr.onLeaveObjectPropUI,
  288. lambda p0=None, p1=obj: self.editor.objectMgr.updateObjectAnimFromUI(p0, p1))
  289. self.lookPane.SetSizer(sizer)
  290. self.propsPane = wx.Panel(self.nb, -1, name='Properties')
  291. self.nb.AddPage(self.propsPane, 'Properties')
  292. sizer = wx.BoxSizer(wx.VERTICAL)
  293. propNames = objDef.orderedProperties[:]
  294. for key in objDef.properties.keys():
  295. if key not in propNames:
  296. propNames.append(key)
  297. for key in propNames:
  298. # handling properties mask
  299. propMask = BitMask32()
  300. for modeKey in objDef.propertiesMask.keys():
  301. if key in objDef.propertiesMask[modeKey]:
  302. propMask |= modeKey
  303. if not propMask.isZero():
  304. if (self.editor.mode & propMask).isZero():
  305. continue
  306. propDef = objDef.properties[key]
  307. propType = propDef[OG.PROP_TYPE]
  308. propDataType = propDef[OG.PROP_DATATYPE]
  309. value = obj[OG.OBJ_PROP].get(key)
  310. if propType == OG.PROP_UI_ENTRY:
  311. propUI = ObjectPropUIEntry(self.propsPane, key)
  312. propUI.setValue(value)
  313. sizer.Add(propUI)
  314. elif propType == OG.PROP_UI_SLIDE:
  315. if len(propDef) <= OG.PROP_RANGE:
  316. continue
  317. propRange = propDef[OG.PROP_RANGE]
  318. if value is None:
  319. continue
  320. if propDataType != OG.PROP_FLOAT:
  321. value = float(value)
  322. propUI = ObjectPropUISlider(self.propsPane, key, value, propRange[OG.RANGE_MIN], propRange[OG.RANGE_MAX])
  323. sizer.Add(propUI)
  324. elif propType == OG.PROP_UI_SPIN:
  325. if len(propDef) <= OG.PROP_RANGE:
  326. continue
  327. propRange = propDef[OG.PROP_RANGE]
  328. if value is None:
  329. continue
  330. propUI = ObjectPropUISpinner(self.propsPane, key, value, propRange[OG.RANGE_MIN], propRange[OG.RANGE_MAX])
  331. sizer.Add(propUI)
  332. elif propType == OG.PROP_UI_CHECK:
  333. if value is None:
  334. continue
  335. propUI = ObjectPropUICheck(self.propsPane, key, value)
  336. sizer.Add(propUI)
  337. elif propType == OG.PROP_UI_RADIO:
  338. if len(propDef) <= OG.PROP_RANGE:
  339. continue
  340. propRange = propDef[OG.PROP_RANGE]
  341. if value is None:
  342. continue
  343. if propDataType != OG.PROP_STR:
  344. for i in range(len(propRange)):
  345. propRange[i] = str(propRange[i])
  346. value = str(value)
  347. propUI = ObjectPropUIRadio(self.propsPane, key, value, propRange)
  348. sizer.Add(propUI)
  349. elif propType == OG.PROP_UI_COMBO:
  350. if len(propDef) <= OG.PROP_RANGE:
  351. continue
  352. propRange = propDef[OG.PROP_RANGE]
  353. if value is None:
  354. continue
  355. if propDataType != OG.PROP_STR:
  356. for i in range(len(propRange)):
  357. propRange[i] = str(propRange[i])
  358. value = str(value)
  359. propUI = ObjectPropUICombo(self.propsPane, key, value, propRange)
  360. sizer.Add(propUI)
  361. elif propType == OG.PROP_UI_COMBO_DYNAMIC:
  362. if len(propDef) <= OG.PROP_DYNAMIC_KEY:
  363. continue
  364. propDynamicKey = propDef[OG.PROP_DYNAMIC_KEY]
  365. if propDynamicKey == OG.PROP_MODEL:
  366. dynamicRangeKey = obj[OG.OBJ_MODEL]
  367. else:
  368. dynamicRangeKey = obj[OG.OBJ_PROP].get(propDynamicKey)
  369. if dynamicRangeKey is None:
  370. self.editor.objectMgr.updateObjectPropValue(obj, key, propDef[OG.PROP_DEFAULT], fUndo=False)
  371. continue
  372. propRange = propDef[OG.PROP_RANGE].get(dynamicRangeKey)
  373. if propRange is None:
  374. self.editor.objectMgr.updateObjectPropValue(obj, key, propDef[OG.PROP_DEFAULT], fUndo=False)
  375. continue
  376. if value is None:
  377. continue
  378. if propDataType != OG.PROP_STR:
  379. for i in range(len(propRange)):
  380. propRange[i] = str(propRange[i])
  381. value = str(value)
  382. if value not in propRange:
  383. value = propRange[0]
  384. self.editor.objectMgr.updateObjectPropValue(obj, key, value, fUndo=False)
  385. propUI = ObjectPropUICombo(self.propsPane, key, value, propRange)
  386. sizer.Add(propUI)
  387. else:
  388. # unspported property type
  389. continue
  390. propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
  391. self.editor.objectMgr.onLeaveObjectPropUI,
  392. lambda p0=None, p1=obj, p2=key: self.editor.objectMgr.updateObjectProperty(p0, p1, p2))
  393. self.propsPane.SetSizer(sizer);
  394. self.Layout()
  395. self.SetupScrolling(self, scroll_y = True, rate_y = 20)
  396. if self.lastPropTab == 'Transform':
  397. self.nb.SetSelection(0)
  398. elif self.lastPropTab == 'Look':
  399. self.nb.SetSelection(1)
  400. elif self.lastPropTab == 'Properties':
  401. self.nb.SetSelection(2)