import wx
import math
from awx.Button import Button
from awx.ConstrainedFloatControl import ConstrainedFloatControl
from awx.RadioButton import RadioButton
from awx.Slider import Slider
from awx.StaticText import StaticText
from awx.TitleSeparator import TitleSeparator
from Dialogs.DialogButtonMixin import CreateStandardButtonSizer
from EventPasser import EventPasser
from Messaging import printException
from awSupportApi import Vector, Normal, project
from RunInUiThread import RunInUiThread
from SceneGraphUtilities import ComputeBoundingBox, GetNodesFromIds
from UiUtilities import InspectorWindow
import ModelIO
class GridArrangeDialog(InspectorWindow, EventPasser):
__kDialogTitle = _('Arrange Items in a Grid')
__kYZPlane = (Normal(0.0, 0.0, -1.0), Normal(0.0, 1.0, 0.0))
__kXZPlane = (Normal(0.0, 0.0, -1.0), Normal(1.0, 0.0, 0.0))
__kXYPlane = (Normal(0.0, -1.0, 0.0), Normal(1.0, 0.0, 0.0))
__kMinScale = 0.01
__kMaxScale = 10.0
__kUndoChunkId = "GridDialogUndoChunk"
def __init__(self, parent):
InspectorWindow.__init__(self, parent, title=self.__kDialogTitle,
name='ViewCubeProperties')
self.__myModels = None
self.__myMaxRows = 0
self.__myNumRows = 0
self.__myNumCols = 0
(self.__myRowAxis, self.__myColumnAxis) = self.__kYZPlane
self.__mySelectedNodes = []
self.__myUndoMessages = None
self.__myRowScaleFactor = 1.25
self.__myColumnScaleFactor = 1.25
self.CreateStandardLayout()
self.SetSizerAndFit(self.GetMainSizer())
self.sendMessage('DIALOG_INTERPRETER_ADD', (self,))
def DoCreate(self):
panel = wx.Panel(self)
self.__myDimensionsTitle = TitleSeparator(panel, label=_('Dimensions'))
self.__mySpacingTitle = TitleSeparator(panel, label=_('Grid Spacing'))
self.__myRowsLabel = StaticText(panel, label=_('Rows:'))
self.__myRowsSlider = Slider(panel, -1, 1, 0, 1.0)
self.__myRowsSlider.Bind(wx.EVT_COMMAND_SCROLL, self.__onRowsSlide)
self.__myRowsText = ConstrainedFloatControl(panel,
wx.ID_ANY, size=wx.Size(50,-1), numDecimalPlaces=0, constraints=[self.__checkDimensions])
self.__myRowsText.Bind(wx.EVT_KILL_FOCUS, self.__onRowsText)
self.__myPlaneLabel = StaticText(panel, label=_('Grid plane:'))
self.__myPlaneButtons = [
RadioButton(panel, wx.ID_ANY, label="Y-Z", style=wx.RB_GROUP),
RadioButton(panel, wx.ID_ANY, label="X-Z"),
RadioButton(panel, wx.ID_ANY, label="X-Y")
]
for button in self.__myPlaneButtons:
button.Bind(wx.EVT_RADIOBUTTON, self.__onPlaneSelect)
self.__myColsLabel = StaticText(panel, label=_('Columns:'))
self.__myColsText = StaticText(panel, label=_('0'))
self.__mySpacingButtons = [
RadioButton(panel, wx.ID_ANY, label="Automatically calculate spacing", style=wx.RB_GROUP),
RadioButton(panel, wx.ID_ANY, label="Manually enter spacing values")
]
for button in self.__mySpacingButtons:
button.Bind(wx.EVT_RADIOBUTTON, self.__onSpacingSelect)
self.__myRowScaleLabel = StaticText(panel, label=_('Row scale factor:'))
self.__myColumnScaleLabel = StaticText(panel, label=_('Column scale factor:'))
self.__myRowScaleSlider = Slider(panel, -1, 1, self.__kMinScale, self.__kMaxScale)
self.__myRowScaleSlider.Bind(wx.EVT_COMMAND_SCROLL, self.__onRowScaleSlide)
self.__myRowScaleText = ConstrainedFloatControl(panel, wx.ID_ANY,
size=wx.Size(50, -1), numDecimalPlaces=3, constraints=[lambda x: x > 0])
self.__myRowScaleText.Bind(wx.EVT_KILL_FOCUS, self.__onRowScaleText)
self.__myRowScaleText.SetValue(1.0)
self.__myColumnScaleSlider = Slider(panel, -1, 1, self.__kMinScale, self.__kMaxScale)
self.__myColumnScaleSlider.Bind(wx.EVT_COMMAND_SCROLL, self.__onColumnScaleSlide)
self.__myColumnScaleText = ConstrainedFloatControl(panel, wx.ID_ANY,
size=wx.Size(50, -1), numDecimalPlaces=3, constraints=[lambda x: x > 0])
self.__myColumnScaleText.Bind(wx.EVT_KILL_FOCUS, self.__onColumnScaleText)
self.__myColumnScaleText.SetValue(1.0)
self.__myRowSpacingLabel = StaticText(panel, label=_('Rows:'))
self.__myColumnSpacingLabel = StaticText(panel, label=_('Columns:'))
self.__mySpacingUnitsLabel = StaticText(panel, label=_('cm'))
self.__myRowSpacingText = ConstrainedFloatControl(panel, wx.ID_ANY, size=wx.Size(50, -1), numDecimalPlaces=3)
self.__myRowSpacingText.Bind(wx.EVT_KILL_FOCUS, self.__onRowSpaceText)
self.__myRowSpacingText.SetValue(10.0)
self.__myColumnSpacingText = ConstrainedFloatControl(panel, wx.ID_ANY, size=wx.Size(50, -1), numDecimalPlaces=3)
self.__myColumnSpacingText.Bind(wx.EVT_KILL_FOCUS, self.__onColumnSpaceText)
self.__myColumnSpacingText.SetValue(10.0)
self.__myObjectCountLabel = StaticText(panel, wx.ID_ANY, label=_('There are no objects selected.'))
self.__myArrangeButton = Button(panel, wx.ID_ANY, label=_('Arrange'))
self.__myArrangeButton.Bind(wx.EVT_BUTTON, self.__onArrange)
self.__myCloseButton = Button(panel, wx.ID_CLOSE, label=_('Close'))
self.__myCloseButton.Bind(wx.EVT_BUTTON, self.__onClose)
self.__updateSpacingControls()
self.__updateSelection()
return panel
def DoLayout(self):
sizer = wx.BoxSizer(wx.VERTICAL)
def rowSlider():
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.__myRowsText, flag=wx.ALL, border=5)
sizer.Add(self.__myRowsSlider, flag=wx.ALL, border=5)
return sizer
def rowColSizer():
sizer = wx.BoxSizer(wx.HORIZONTAL)
for item in (self.__myRowsLabel, rowSlider(), self.__myColsLabel, self.__myColsText):
sizer.Add(item, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL, border=5)
return sizer
def planeSelector():
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.__myPlaneLabel, flag=wx.ALL, border=5)
for item in self.__myPlaneButtons:
sizer.Add(item, flag=wx.ALL|wx.EXPAND, border=5)
return sizer
def scaleSizer():
sizer = wx.FlexGridSizer(rows=2, cols=3, hgap=5, vgap=5)
sizer.Add(self.__myRowScaleLabel, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
sizer.Add(self.__myRowScaleText)
sizer.Add(self.__myRowScaleSlider, flag=wx.ALIGN_CENTER_VERTICAL)
sizer.Add(self.__myColumnScaleLabel, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
sizer.Add(self.__myColumnScaleText)
sizer.Add(self.__myColumnScaleSlider, flag=wx.ALIGN_CENTER_VERTICAL)
return sizer
def spacingSizer():
sizer = wx.BoxSizer(wx.HORIZONTAL)
for item in (
self.__myRowSpacingLabel,
self.__myRowSpacingText,
self.__myColumnSpacingLabel,
self.__myColumnSpacingText,
self.__mySpacingUnitsLabel):
sizer.Add(item, flag=wx.ALL|wx.ALIGN_CENTER_VERTICAL, border=5)
return sizer
buttonSizer = CreateStandardButtonSizer(
(self.__myArrangeButton, self.__myCloseButton) )
sizer.Add(self.__myDimensionsTitle, flag=wx.EXPAND)
sizer.Add((-1, 5))
sizer.Add(self.__myObjectCountLabel, border=5, flag=wx.ALL)
sizer.Add(rowColSizer())
sizer.Add(planeSelector(), border=5, flag=wx.ALL|wx.EXPAND)
sizer.Add(self.__mySpacingTitle, flag=wx.EXPAND)
sizer.Add((-1, 5))
sizer.Add(self.__mySpacingButtons[0], border=5, flag=wx.ALL)
sizer.Add(scaleSizer(), border=5, flag=wx.ALL)
sizer.Add(self.__mySpacingButtons[1], border=5, flag=wx.ALL)
sizer.Add(spacingSizer(), border=5, flag=wx.ALL)
sizer.Add(buttonSizer, border=5, flag=wx.ALL)
return sizer
def __onClose(self, event):
self.Show(False)
def __onArrange(self, event):
if self.__myNumRows == 0:
return
try:
(rowVector, columnVector, gridCenter) = self.__computeGrid()
self.sendMessage( "GRID_ARRANGE",
( tuple(self.__mySelectedNodes)
, self.__myNumRows
, self.__myNumCols
, (rowVector, columnVector, gridCenter)
) )
except:
printException()
def __onPlaneSelect(self, event):
for (i, plane) in enumerate( (self.__kYZPlane, self.__kXZPlane, self.__kXYPlane) ):
if self.__myPlaneButtons[i].GetValue():
(self.__myRowAxis, self.__myColumnAxis) = plane
break
def __onSpacingSelect(self, event):
self.__updateSpacingControls()
def __onRowsText(self, event):
self.__myRowsText.constrainInput()
self.__myNumRows = int( self.__myRowsText.GetValue() )
scaledValue = float(self.__myRowsText.GetValue())/self.__myMaxRows
self.__myRowsSlider.SetValue(scaledValue)
self.__updateColumns()
def __onRowScaleText(self, event):
self.__myRowScaleText.constrainInput()
value = float(self.__myRowScaleText.GetValue())
self.__myRowScaleSlider.SetValue(value)
def __onColumnScaleText(self, event):
self.__myColumnScaleText.constrainInput()
value = float(self.__myColumnScaleText.GetValue())
self.__myColumnScaleSlider.SetValue(value)
def __onRowScaleSlide(self, event):
self.__myRowScaleText.SetValue( self.__myRowScaleSlider.GetValue() )
def __onColumnScaleSlide(self, event):
self.__myColumnScaleText.SetValue( self.__myColumnScaleSlider.GetValue() )
def __onRowSpaceText(self, event):
self.__myRowSpacingText.constrainInput()
def __onColumnSpaceText(self, event):
self.__myColumnSpacingText.constrainInput()
def __onRowsSlide(self, event):
self.__updateRowsFromSlider()
def __updateRowsFromSlider(self):
numRows = int(self.__myRowsSlider.GetValue()*self.__myMaxRows)
if numRows == 0:
numRows = 1
if self.__checkDimensions(numRows):
numCols = int(math.ceil(self.__myMaxRows / float(numRows)))
self.__myRowsText.SetValue(numRows)
self.__myNumRows = numRows
self.__updateColumns()
def __updateSpacingControls(self):
enableAutomatic = self.__mySpacingButtons[0].GetValue()
enableManual = not enableAutomatic
self.__myRowScaleSlider.Enable(enableAutomatic)
self.__myRowScaleText.Enable(enableAutomatic)
self.__myColumnScaleSlider.Enable(enableAutomatic)
self.__myColumnScaleText.Enable(enableAutomatic)
self.__myRowSpacingText.Enable(enableManual)
self.__myColumnSpacingText.Enable(enableManual)
def __updateColumns(self):
if self.__myMaxRows == 0:
self.__myNumCols = 0
else:
self.__myNumCols = int(math.ceil(self.__myMaxRows / float(self.__myNumRows)))
self.__myColsText.SetLabel(str(self.__myNumCols))
def __checkDimensions(self, numRows):
numObjects = self.__myMaxRows
if numRows < 1 or numRows > numObjects:
return False
numCols = int(math.ceil(self.__myMaxRows / float(numRows)))
if (numObjects % numCols == 0) and (numObjects % numRows == 0) \
or (numObjects % numCols != 0) and (numObjects % numRows != 0):
return True
return False
def __computeGrid(self):
"""
Computes the row vector, column vector, and grid center
for the grid we want to create. Will use either automatic
or manual spacing depending on the options selected in the UI.
"""
nodes = list(GetNodesFromIds(self.__mySelectedNodes))
bbox = ComputeBoundingBox(nodes)
defaultGrid = (Vector(0.0, 0.0, -1.0), Vector(0.0, 1.0, 0.0), Vector(0.0, 0.0, 0.0))
if not bbox.isBounded():
return defaultGrid
gridCenter = Vector(bbox.mid().p)
autoCompute = self.__mySpacingButtons[0].GetValue()
if autoCompute:
(rowSize, columnSize) = self.__autoComputeSpacing(nodes)
rowScale = float(self.__myRowScaleText.GetValue())
columnScale = float(self.__myColumnScaleText.GetValue())
rowSize *= rowScale
columnSize *= columnScale
else:
rowSize = float(self.__myRowSpacingText.GetValue())
columnSize = float(self.__myColumnSpacingText.GetValue())
rowVector = Vector(self.__myRowAxis)
rowVector *= rowSize
columnVector = Vector(self.__myColumnAxis)
columnVector *= columnSize
return (rowVector, columnVector, gridCenter)
def __autoComputeSpacing(self, nodes):
"""
Finds the minimum spacing so that objects don't overlap when
placed in a grid
"""
rowSize = 0
columnSize = 0
for node in nodes:
bbox = ComputeBoundingBox([node])
if not bbox.isBounded():
continue
boxSize = Vector(bbox.size().p)
tempRow = project(boxSize, self.__myRowAxis).length()
tempColumn = project(boxSize, self.__myColumnAxis).length()
if tempRow > rowSize:
rowSize = tempRow
if tempColumn > columnSize:
columnSize = tempColumn
return (rowSize, columnSize)
def __updateSelection(self):
if self.__myModels:
selectedNodes = self.__myModels.selected
else:
selectedNodes = []
RunInUiThread(self.__updateSelected, selectedNodes)
def __updateSelected(self, selectedNodes):
self.__mySelectedNodes = selectedNodes
self.__myMaxRows = len(self.__mySelectedNodes)
self.__myObjectCountLabel.SetLabel('There are ' + str(self.__myMaxRows) + ' objects selected.')
self.__myRowsSlider.SetValue(1.0)
self.__updateRowsFromSlider()
if self.__myMaxRows == 0:
self.__myRowsText.Enable(False)
self.__myRowsSlider.Enable(False)
self.__myArrangeButton.Enable(False)
else:
self.__myRowsText.Enable(True)
self.__myRowsSlider.Enable(True)
self.__myArrangeButton.Enable(True)
def SET_DOCUMENT(self, message):
document, = message.data
self.__myModels = document.get(ModelIO.id)
self.__updateSelection()
def SELECTION_CHANGED(self, message):
if not self.__myModels:
return
self.__updateSelection()
def APPLICATION_CLOSE_SCENE(self, message):
self.__myModels = None
RunInUiThread(self.Show, False)