#!BPY # vim:fileencoding=utf-8 """ Name: 'Demolition' Blender: 242 Group: 'Mesh' Tooltip: 'Simulation for realistic demolition effects.' """ ################################################################################ # Demolition Script, Copyright (C) 2007 Mathias Pantzenböck ################################################################################ # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. (www.gnu.org) ################################################################################ __author__ = 'Mathias Panzenböck' __version__ = '1.0.9 Beta' __url__ = 'http://sharesource.org/project/blenderdemolition' __bpydoc__ = """\ TODO """ print "You are running Demolition GUI version %s now..." % __version__ import demolition import Blender from math import sqrt from Blender.Mathutils import Vector from Blender.Draw import * from Blender.Draw import Label as BlenderLabel from Blender.Window import MButs, Qual, GetMouseButtons, GetKeyQualifiers, GetAreaSize from Blender import Scene, Text, Get, Set, Object, Group try: import weakref except ImportError: print "Warning: You do not have the 'weakref' python module installed." print " A workaround will be used but this will leak memory." print " It is highly recommended to install a full python version." print " See: http://www.python.org/" print # very ugly memory leaking workaround if there is no weakref module: class weakref(object): __slots__ = () class ref(object): __slots__ = ('__weakref__','_obj') def __init__(self,obj): self._obj = obj def __call__(self): return self._obj tooltips = { 'gr1name': '''Name of first crash group.''', 'gr2name': '''Name of second crash group.''', 'suffix': '''Suffix of simulation Objects.''', 'vmblur': '''\ Tell me if you're doing a final render with vector motion blur. Vector Blur needs some special treatment, because the script is called three times per frame then. Don't forget to activate it or you will get completely different results for the same settings. Warning: Do not use motion blur together with animated mode. It's not compatible yet.''', 'sleep': '''Do no calculations beside speed vectors until (and including) this frame number.''', 'breaks': '''Meshes breakable.''', 'limit': '''Edge break limit (*100 % edge length).''', 'limitTrans': '''Edge break limit for tranparent faces (slower), 0 = disabled.''', 'delEdges': '''\ Delete faceless (invisible) edges (slower). Use this if you don't want invisible connections between pieces. Recommended for glass pieces of a shattering window for instance.''', 'iteration': '''Iteration depth of main spring algo (must be a multiple of 3 should be a multiple of 3 when using vmblur).''', 'maxfA': '''Absolute maximum speed per vertex.''', 'springDamp': '''\ Damping of all springs, 1 = no damping, < 1 = more damping. Use this to prevent noise patterns or chaotic flipping bumps in the surface due to unstable high stresses, but try to keep it as high as possible to save iteration rate.''', 'stiff': '''\ !Obsolete, use elast instead! Create rigidity edges (very slow on first frame), > 0 = count of edges, -1 = all possible edges.''', 'relDamp': '''\ Vertex speed damping, relative to objects or pieces origin, 1 = no damping, < 1 = more damping. Use this to reduce wrapping around effect on collisions, the mesh appears more rigid and less bendable on lower values. But you can see this also as rotation damping, any centrifugal force is going to slow down when relDamp < 1.''', 'selfcoll': '''Self collision (slow).''', 'ground': '''Ground collision.''', 'grndFrict': '''Ground friction (speed = speed / grndFrict).''', 'grndBounce': '''Ground rebounce factor for single vertices, 0 = disabled (vertices stopping immediately), > 0 = rebounce factor.''', 'rigidBounce': '''Rigid objects rebounce factor for single vertices, 0 = disabled (vertices stopping immediately), > 0 = rebounce factor.''', 'grav': '''Gravity vector (BU per frame).''', 'wind': '''Wind vector (BU per frame)''', 'airRes': '''Air resistance (speed = speed / airRes).''', 'shock': '''Shock wave, 0 = off, > 0 factor of strength.''', 'shockOrig': '''Shock wave origin, the wave begins here at starting frame.''', 'shockSpd': '''Shock wave speed.''', 'shockChaos': '''Shock chaotic forces strength.''', 'gr1rigid': '''Make group 1 undeformable, 0 = deformable, 1 = completely rigid (indestructible).''', 'gr2rigid': '''Make group 2 undeformable, 0 = deformable, 1 = completely rigid (indestructible).''', 'fix': '''Make collided vertices stick together (use it with gr#rigid to have better deforming results).''', 'collLimit': '''Number of collisions per vertex before falling to sleep.''', 'animated': '''\ Animated mode until (and including) this frame number, 0 = off. The system is trying to hold the shape, position and rotation of the original objects until this frame number.''', 'selection': '''\ Selected verts in original mesh will be set to free from goal if animated or set to be fixed if free. This is a nice option to give a collapsing building the initial impulse by setting some verts to loose (= weak).''', 'tolerance': '''\ Maximum allowed distance between original vertex and deformed vertex for animated mode (in BUs). When distance gets higher than this value then vertex is detached from goal and free. Only active when animated > 0.''', 'goalForce': '''\ Factor for springs pulling verts to original verts (goal), 0 = no force, 1 = verts will be set immediately to original position after deformation. Only active when animated > 0.''', 'elast': '''\ Elasticity, objects rebounces, > 0 = multiplicator of rebounce energy. Only active when animated > 0.''', 'elastVec': '''\ Rebounce direction, (1,1,1) = backward, (0,0,1) = only z-axis is taken into account. This is useful to force the colliding object into a special rebounce direction or axis. Only active when animated > 0.''', 'elastPieces': '''\ Elast speed correction of free pieces (very slow). Use this only when elast > 0. This prevent loose parts within the same object also to rebounce when another part is colliding. Only active when animated > 0.''', 'gr1spd': '''\ Initial speed vector for crash group 1. Only active when animated > 0.''', 'gr2spd': '''\ Initial speed vector for crash group 2. Only active when animated > 0.''', 'gr1rot': '''Initial rotation impulse for crash group 1 (degree per frame).''', 'gr1rotAxis': '''Initial rotation axis for crash group 1, 'X' 'Y' or 'Z'.''', 'gr2rot': '''Initial rotation impulse for crash group 2 (degree per frame).''', 'gr2rotAxis': '''Initial rotation axis for crash group 2, 'X' 'Y' or 'Z'.''' } SLIDER_SPAN = 1000 LINE_WIDTH = 160 LINE_HEIGHT = 20 LINE_SPACE = 2 MARGINX = 5 MARGINY = 5 SCALING = 1.2 INV_SCALING = 1. / SCALING MIN_SCALE = 0.003 MAX_SCALE = 25. VECTOR_HEIGHT = LINE_WIDTH + (LINE_SPACE + LINE_HEIGHT) * 5 GROUP_SPACE = LINE_HEIGHT * .5 + LINE_SPACE VSCROLL = LINE_WIDTH / 2 HSCROLL = VSCROLL try: guiProperties = Scene.GetCurrent().properties['DemolitionGUI'] except KeyError: guiProperties = {} Scene.GetCurrent().properties['DemolitionGUI'] = guiProperties class Widget(object): __slots__ = ( '__weakref__','_name','_tooltip','_visible','_action_listeners','_resize_listeners', '_move_listeners','_x','_y','_width','_height','_keeper' ) __maxbevent = 0 __bevents = {} __widgets = [] __x = MARGINX __y = MARGINY __scale = 1 mousex = 0 mousey = 0 @staticmethod def newEvent(widget=None): # if multithreaded UIs are possible, this should be synchronized: SimpleInput.__maxbevent = evt = SimpleInput.__maxbevent + 1 if widget is not None: Widget.__bevents[evt] = widget return evt @staticmethod def movex(xoff): widthpx = GetAreaSize()[0] scale = Widget.__scale xpx = Widget.__x * scale + xoff mpx = MARGINX * scale wpx = LINE_WIDTH * scale x = Widget.__x + (xoff / scale) if wpx > widthpx: if xpx > mpx: x = MARGINX elif xpx + wpx + mpx < widthpx: x = (widthpx / scale) - LINE_WIDTH - MARGINX else: if xpx < mpx: x = MARGINX elif xpx + wpx + mpx > widthpx: x = (widthpx / scale) - LINE_WIDTH - MARGINX Widget.__x = x @staticmethod def movey(yoff): MAX_Y = MAX_Y_WIDGET.y + MAX_Y_WIDGET.height heightpx = GetAreaSize()[1] scale = Widget.__scale ypx = Widget.__y * scale + yoff mpx = MARGINY * scale hpx = MAX_Y * scale y = Widget.__y + (yoff / scale) if hpx > heightpx: if ypx > mpx: y = MARGINY elif ypx + hpx + mpx < heightpx: y = (heightpx / scale) - MAX_Y - MARGINY else: if ypx < mpx: y = MARGINY elif ypx + hpx + mpx > heightpx: y = (heightpx / scale) - MAX_Y - MARGINY Widget.__y = y @staticmethod def move(xoff,yoff): Widget.movex(xoff) Widget.movey(yoff) @staticmethod def __event(evt, val): # print "event:",evt,val if evt == MOUSEX: if GetMouseButtons() & MButs.M: Widget.movex(val - Widget.mousex) Widget.mousex = val elif evt == MOUSEY: if GetMouseButtons() & MButs.M: Widget.movey(val - Widget.mousey) Widget.mousey = val elif evt == WHEELDOWNMOUSE: qual = GetKeyQualifiers() if qual & Qual.CTRL: Widget.scale(INV_SCALING) elif qual & Qual.SHIFT: Widget.movex(HSCROLL * Widget.__scale) else: Widget.movey(VSCROLL * Widget.__scale) elif evt == WHEELUPMOUSE: qual = GetKeyQualifiers() if qual & Qual.CTRL: Widget.scale(SCALING) elif qual & Qual.SHIFT: Widget.movex(-HSCROLL * Widget.__scale) else: Widget.movey(-VSCROLL * Widget.__scale) elif evt == ESCKEY: Exit() return Redraw(1) @staticmethod def scale(scaling): scale = Widget.__scale * scaling if scale < MIN_SCALE or scale > MAX_SCALE: return Widget.__scale = scale width, height = GetAreaSize() wh = width * .5 hh = height * .5 xoff = wh - wh * scaling yoff = hh - hh * scaling Widget.movex(xoff) Widget.movey(yoff) guiProperties['guiscale'] = scale @staticmethod def __button_event(evt): # print "button event:",evt try: widget = Widget.__bevents[evt] except KeyError: pass else: widget._action(evt, widget.getActionValue()) Redraw(1) @staticmethod def gui(): try: Widget.__scale = guiProperties['guiscale'] except KeyError: guiProperties['guiscale'] = Widget.__scale Register(Widget.__draw, Widget.__event, Widget.__button_event) @staticmethod def uiBlock(): UIBlock(Widget.__draw) @staticmethod def __draw(): x = Widget.__x y = Widget.__y scale = Widget.__scale # print '__draw: x=%r, y=%r, scale=%r' % (x,y,scale) for widget in Widget.__widgets: if widget._visible: try: widget.draw(x,y,scale) except: import traceback traceback.print_exc() # TODO: Not all widgets have tooltips! def __init__(self,name,x,y,width,height,tooltip='',visible=True): object.__init__(self) self._name = name self._tooltip = tooltip self._visible = visible self._action_listeners = set() self._resize_listeners = set() self._move_listeners = set() self._x = x self._y = y self._width = width self._height = height self._keeper = None Widget.__widgets.append(self) self.move(x,y) self.resize(width,height) def draw(self,x,y,scale): pass def getValue(self): return None def setValue(self,val): pass def _action(self,event,val): # print "%s.action(%r,%r)" % (self.name,event,val) for listener in self._action_listeners: listener(val) def addActionListener(self,listener): self._action_listeners.add(listener) def removeActionListener(self,listener): self._action_listeners.remove(listener) def iterActionListeners(self): return iter(self._action_listeners) def resize(self,width,height): self._width = width self._height = height self._fireResize(width,height) Redraw(1) def _fireResize(self,width,height): for listener in self._resize_listeners: listener(width,height) def addResizeListener(self,listener): self._resize_listeners.add(listener) def removeResizeListener(self,listener): self._resize_listeners.remove(listener) def iterResizeListeners(self): return iter(self._resize_listeners) def move(self,x,y): self._x = x self._y = y self._fireMove(x,y) Redraw(1) def _fireMove(self,x,y): for listener in self._move_listeners: listener(x,y) def addMoveListener(self,listener): self._move_listeners.add(listener) def removeMoveListener(self,listener): self._move_listeners.remove(listener) def iterMoveListeners(self): return iter(self._move_listeners) def _setX(self,x): x = int(x) self._x = x self._fireMove(x,self._y) Redraw(1) def _setY(self,y): y = int(y) self._y = y self._fireMove(self._x,y) Redraw(1) def _setWidth(self,width): width = int(width) self._width = width self._fireResize(width,self._height) Redraw(1) def _setHeight(self,height): height = int(height) self._height = height self._fireResize(self._width,height) Redraw(1) def _setName(self,name): self._name = str(name) Redraw(1) def _setVisible(self,visible): self._visible = bool(visible) Redraw(1) def _setTooltip(self,tooltip): self._tooltip = str(tooltip) Redraw(1) def clearKeep(self): if self._keeper is not None: self._keeper.clear() self._keeper = None def keepLeft(self,relative,offset=LINE_SPACE): self.clearKeep() self._keeper = LeftKeeper(self,relative,offset) def keepRight(self,relative,offset=LINE_SPACE): self.clearKeep() self._keeper = RightKeeper(self,relative,offset) def keepBelow(self,relative,offset=LINE_SPACE): self.clearKeep() self._keeper = BelowKeeper(self,relative,offset) def keepAbove(self,relative,offset=LINE_SPACE): self.clearKeep() self._keeper = AboveKeeper(self,relative,offset) x = property(lambda self:self._x, _setX) y = property(lambda self:self._y, _setY) width = property(lambda self:self._width, _setWidth) height = property(lambda self:self._height, _setHeight) name = property(lambda self:self._name, _setName) visible = property(lambda self:self._visible,_setVisible) tooltip = property(lambda self:self._tooltip,_setTooltip) class Keeper(object): __slots__ = ('__weakref__', '_widget','_relative','offset') def __init__(self,widget,relative,offset=LINE_SPACE): self._widget = weakref.ref(widget) self._relative = weakref.ref(relative) self.offset = offset relative.addMoveListener(self) def getWidget(self): if self._widget is None: return None else: return self._widget() def getRelative(self): if self._relative is None: return None else: return self._relative() def clear(self): relative = self.getRelative() if relative is not None: try: relative.removeMoveListener(self) except KeyError: pass self.widget = None self.relative = None def __call__(self,dummy1,dummy2): widget = self.getWidget() relative = self.getRelative() if widget is not None and relative is not None: self.keep(widget,relative) def keep(self,widget,relative): raise NotImplementedError("The keep method has to be overloaded by a subclass.") class LeftKeeper(Keeper): __slots__ = () def __init__(self,widget,relative,offset=LINE_SPACE): Keeper.__init__(self,widget,relative,offset) widget.addResizeListener(self) def clear(self): widget = self.getWidget() if widget is not None: try: widget.removeResizeListener(self) except KeyError: pass Keeper.clear(self) def keep(self,widget,relative): widget.move(relative.x - widget.width - self.offset, relative.y) class RightKeeper(Keeper): __slots__ = () def __init__(self,widget,relative,offset=LINE_SPACE): Keeper.__init__(self,widget,relative,offset) relative.addResizeListener(self) def clear(self): widget = self.getWidget() if widget is not None: try: relative.removeResizeListener(self) except KeyError: pass Keeper.clear(self) def keep(self,widget,relative): widget.move(relative.x + relative.width + self.offset, relative.y) class AboveKeeper(Keeper): __slots__ = () def __init__(self,widget,relative,offset=LINE_SPACE): Keeper.__init__(self,widget,relative,offset) relative.addResizeListener(self) def clear(self): relative = self.getRelative() if relative is not None: try: relative.removeMoveListener(self) except KeyError: pass Keeper.clear(self) def keep(self,widget,relative): widget.move(relative.x, relative.y + relative.height + self.offset) class BelowKeeper(Keeper): __slots__ = () def __init__(self,widget,relative,offset=LINE_SPACE): Keeper.__init__(self,widget,relative,offset) widget.addResizeListener(self) def clear(self): widget = self.getWidget() if widget is not None: try: widget.removeResizeListener(self) except KeyError: pass Keeper.clear(self) def keep(self,widget,relative): widget.move(relative.x, relative.y - widget.height - self.offset) class Dummy(object): __slots__ = ('__weakref__', 'val') def __init__(self,val): self.val = val class Label(Widget): __slots__ = () def __init__(self,name,x,y,width,height=LINE_HEIGHT): Widget.__init__(self,name,x,y,width,height) def draw(self,x,y,scale): BlenderLabel(self.name, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale)) class SimpleInput(Widget): __slots__ = ('type','event','_widget') def __init__(self,name,value,tp,x,y,width,height=LINE_HEIGHT,register_bevent=False,tooltip=''): Widget.__init__(self,name,x,y,width,height,tooltip) self.type = tp # XXX: Create(value) seems to be broken. self._widget = Dummy(tp(value)) if register_bevent: self.event = Widget.newEvent(self) else: self.event = Widget.newEvent() def getValue(self): return self.type(self._widget.val) def setValue(self,val): self._widget.val = self.type(val) Redraw(1) class IntInput(SimpleInput): __slots__ = ('min','max','slider','_label') def __init__(self,name,value,x,y,width,height=LINE_HEIGHT,min=-1000,max=1000,slider=False,tooltip=''): SimpleInput.__init__(self,name,value,int,x,y,width,height,tooltip=tooltip) self.min = int(min) self.max = int(max) self.slider = slider self._label = name + ': ' def draw(self,x,y,scale): if self.slider: self._widget = Slider(self._label, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self._widget.val, self.min, self.max, 0, self.tooltip, self._action) else: self._widget = Number(self._label, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self._widget.val, self.min, self.max, self.tooltip, self._action) class FloatInput(SimpleInput): __slots__ = ('min','max','slider','_label') def __init__(self,name,value,x,y,width,height=LINE_HEIGHT,min=-1.,max=1.,slider=True,tooltip=''): SimpleInput.__init__(self,name,value,float,x,y,width,height,tooltip=tooltip) self.min = float(min) self.max = float(max) self.slider = slider self._label = name + ': ' def draw(self,x,y,scale): if self.slider: self._widget = Slider(self._label, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self._widget.val, self.min, self.max, 0, self.tooltip, self._action) else: self._widget = Number(self._label, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self._widget.val, self.min, self.max, self.tooltip, self._action) class BoolInput(SimpleInput): __slots__ = () def __init__(self,name,value,x,y,width,height=LINE_HEIGHT,tooltip=''): SimpleInput.__init__(self,name,value,bool,x,y,width,height,tooltip=tooltip) def draw(self,x,y,scale): self._widget = Toggle(self.name, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self._widget.val, self.tooltip, self._action) class StrInput(SimpleInput): __slots__ = ('length','_label') # TODO: wrap length into property def __init__(self,name,value,x,y,width,height=LINE_HEIGHT,length=399,tooltip=''): SimpleInput.__init__(self,name,value,str,x,y,width,height,tooltip=tooltip) self.length = int(length) self._label = name + ': ' def draw(self,x,y,scale): self._widget = String(self._label, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self._widget.val, self.length, self.tooltip, self._action) class GroupInput(Widget): __slots__ = ('_strgroup','_btnassign','_btnremove') def __init__(self,name,value,x,y,width,height=LINE_HEIGHT*2+LINE_SPACE,length=399,tooltip=''): Widget.__init__(self,name,x,y,width,height,tooltip) self._strgroup = StrInput(name,value,x,y + LINE_HEIGHT + LINE_SPACE,width,LINE_HEIGHT,length,tooltip) width = (width - LINE_SPACE) * .5 self._btnassign = Button('assign',x,y,width,LINE_HEIGHT,'assign selection to group') self._btnremove = Button('remove',x,y + width + LINE_SPACE,width,LINE_HEIGHT,'remove selection from group') self._strgroup.addActionListener(WeakListener(self._straction)) self._btnassign.addActionListener(WeakListener(self._assign)) self._btnremove.addActionListener(WeakListener(self._remove)) self.addMoveListener(WeakListener(self._onMove)) self.addResizeListener(WeakListener(self._onResize)) def _setVisible(self,val): Widget._setVisible(self,val) self._strgroup.visible = val self._btnassign.visible = val self._btnremove.visible = val visible = property(lambda self:self._visible,_setVisible) def _straction(self,val): self._action(self._strgroup._event,val) def _onMove(self,x,y): self._strgroup.x = x self._btnassign.x = x self._btnremove.x = x + self._btnassign.width + LINE_SPACE self._strgroup.y = y + LINE_HEIGHT + LINE_SPACE self._btnassign.y = y self._btnremove.y = y def _onResize(self,width,height): self._strgroup.width = width width = (width - LINE_SPACE) * .5 self._btnassign.width = width self._btnremove.x = self.x + width + LINE_SPACE self._btnremove.width = width def _assign(self,val): try: grp = Group.Get(self._strgroup.getValue()) except: grp = Group.New(self._strgroup.getValue()) for obj in Object.GetSelected(): grp.objects.link(obj) def _remove(self,val): try: grp = Group.Get(self._strgroup.getValue()) except: return for obj in Object.GetSelected(): grp.objects.unlink(obj) def getValue(self): return self._strgroup.getValue() def setValue(self,value): self._strgroup.setValue(value) def norm(vec): return sqrt(vec[0]**2 + vec[1]**2 + vec[2]**2 ) def xnormalize(vec): scale = norm(vec) if scale == 0.0: return scale, (0.,0.,0.) else: return scale, (vec[0] / scale, vec[1] / scale, vec[2] / scale) class VectorInput(SimpleInput): __slots__ = ( '_label','_obj','_min','_max','_flz','_fly','_flx','_btnnorm','_btnobj', '_btnrot','_btnloc','_strobj','_fls','_full_height','_normal_height', '_normal_y','_label_y' ) def __init__(self,name,value,x,y,width,height=VECTOR_HEIGHT,min=(-1.0,-1.0,-1.0),max=(1.0,1.0,1.0),tooltip=''): SimpleInput.__init__(self,name,(0.,0.,0.),tuple,x,y,width,height,True,tooltip) self._label = name + ':' self._obj = None self._min = min self._max = max setattr(Blender,"DemolitionGui%sCallback" % name,self._callback) span = abs(max[2] - min[2]) slider = span <= SLIDER_SPAN self._flz = FloatInput('z',0.,x,y,width,LINE_HEIGHT,min=min[2],max=max[2],slider=slider,tooltip=tooltip) span = abs(max[2] - min[2]) slider = span <= SLIDER_SPAN self._fly = FloatInput('y',0.,x,y,width,LINE_HEIGHT,min=min[1],max=max[1],slider=slider,tooltip=tooltip) span = abs(max[2] - min[2]) slider = span <= SLIDER_SPAN self._flx = FloatInput('x',0.,x,y,width,LINE_HEIGHT,min=min[0],max=max[0],slider=slider,tooltip=tooltip) self._btnnorm = BoolInput('N',False,x,y,LINE_HEIGHT,tooltip='show normal input') self._btnobj = BoolInput('from obj',False,x,y,LINE_HEIGHT,tooltip='get vector from an object (experimental)') self._btnrot = BoolInput('rotation',True,x,y,LINE_HEIGHT,tooltip='use objects rotation and size') self._btnloc = BoolInput('location',False,x,y,LINE_HEIGHT,tooltip='use objects location') self._strobj = StrInput('object','',x,y,LINE_HEIGHT,tooltip='name of the object') self._btnrot.visible = False self._btnloc.visible = False self._strobj.visible = False minnorm = norm(min) maxnorm = norm(max) if minnorm > maxnorm: maxnorm = minnorm slider = maxnorm <= SLIDER_SPAN self._fls = FloatInput('scale',0,x,y,width,LINE_HEIGHT,min=0.,max=maxnorm,slider=slider,tooltip=tooltip) self._flx.addActionListener(WeakListener(self._xaction)) self._fly.addActionListener(WeakListener(self._yaction)) self._flz.addActionListener(WeakListener(self._zaction)) self._fls.addActionListener(WeakListener(self._saction)) self._btnobj.addActionListener(WeakListener(self._objaction)) self._strobj.addActionListener(WeakListener(self._setObjectByName)) self._btnrot.addActionListener(WeakListener(self._rotaction)) self._btnloc.addActionListener(WeakListener(self._locaction)) self._btnnorm.addActionListener(WeakListener(self._naction)) self.setValue(value) self._full_height = height self.addResizeListener(WeakListener(self._recalc_dim)) self.addMoveListener(WeakListener(self._recalc_dim)) self._recalc_dim() def _xaction(self,val): vec = (val, self._fly.getValue(), self._flz.getValue()) scale, nvec = xnormalize(vec) SimpleInput.setValue(self, nvec) self._fls.setValue(scale) self._inform_updated(vec) def _yaction(self,val): vec = (self._flx.getValue(), val, self._flz.getValue()) scale, nvec = xnormalize(vec) SimpleInput.setValue(self, nvec) self._fls.setValue(scale) self._inform_updated(vec) def _zaction(self,val): vec = (self._flx.getValue(), self._fly.getValue(), val) scale, nvec = xnormalize(vec) SimpleInput.setValue(self, nvec) self._fls.setValue(scale) self._inform_updated(vec) def _saction(self,val): if val == 0.0: vec = (0.,0.,0.) SimpleInput.setValue(self, vec) else: x, y, z = self.getNormalized() vec = (x * val, y * val, z * val) self._flx.setValue(vec[0]) self._fly.setValue(vec[1]) self._flz.setValue(vec[2]) self._inform_updated(vec) def _rotaction(self,val): self._btnloc.setValue(not val) self._inform_updated() def _locaction(self,val): self._btnrot.setValue(not val) self._inform_updated() def _objaction(self,val): # self._btnobj.setValue(val) # print "objaction: val=%r, btn=%r" % (val, self._btnobj.getValue()) if not val: normal = self._btnnorm.getValue() else: self._btnnorm.setValue(False) normal = False self.__setObjectSource(val,normal) # self._btnobj.setValue(val) # self._inform_updated(self.getValue()) # self.__updateDisplay(val) def _naction(self,val): self._btnnorm.setValue(val) if val: self._btnobj.setValue(False) self.__setObjectSource(False,val) def _setVisible(self,visible): SimpleInput._setVisible(self,visible) if visible: object = self._btnobj.getValue() normal = self._btnnorm.getValue() notobject = not object else: object = False normal = False notobject = False self._btnobj.visible = visible self._btnnorm.visible = visible self._strobj.visible = object self._btnrot.visible = object self._btnloc.visible = object self._fls.visible = normal self._flx.visible = notobject self._fly.visible = notobject self._flz.visible = notobject visible = property(lambda self:self._visible,_setVisible) def _recalc_dim(self,dummy1=None,dummy2=None): if self._btnnorm.getValue(): self._full_height = self.height x = self.x width = self.width self._fls.x = x self._flx.x = x self._fly.x = x self._flz.x = x self._strobj.x = x self._btnrot.x = x self._fls.width = width self._flx.width = width self._fly.width = width self._flz.width = width self._strobj.width = width w = (width - LINE_SPACE) * 0.5 self._btnrot.width = w self._btnloc.width = w wobj = w - LINE_HEIGHT self._btnobj.width = wobj x += w + LINE_SPACE self._btnobj.x = x self._btnloc.x = x self._btnnorm.x = x + wobj y = self.y self._flx.y = y + 2 * (LINE_HEIGHT + LINE_SPACE) self._fly.y = y + LINE_HEIGHT + LINE_SPACE self._flz.y = y y = self._y + 3 * (LINE_HEIGHT + LINE_SPACE) self._normal_height = self._height - 5 * (LINE_HEIGHT + LINE_SPACE) self._normal_y = y y = y + self._normal_height + 2 * LINE_SPACE + LINE_HEIGHT self._label_y = y self._btnobj.y = y self._btnnorm.y = y y -= LINE_SPACE + LINE_HEIGHT self._fls.y = y self._btnrot.y = y self._btnloc.y = y y -= LINE_SPACE + LINE_HEIGHT self._strobj.y = y def trim(self,x,y,z): min = self._min max = self._max if x < min[0]: x = min[0] elif x > max[0]: x = max[0] if y < min[1]: y = min[1] elif y > max[1]: y = max[1] if z < min[2]: z = min[2] elif z > max[2]: z = max[2] return (x, y, z) def _action(self,event,value): scale = self._fls.getValue() vec = self.trim(value[0] * scale, value[1] * scale, value[2] * scale) # print "_action",scale,vec self._flx.setValue(vec[0]) self._fly.setValue(vec[1]) self._flz.setValue(vec[2]) SimpleInput._action(self,event,vec) def getActionValue(self): return self.getNormalized() def getNorm(self): return self._fls.getValue() def getNormalized(self): return SimpleInput.getValue(self) def getSource(self): if self._btnobj.getValue(): if self._btnrot.getValue(): return 'rotation' else: return 'location' elif self._btnnorm.getValue(): return 'normal' else: return 'xyz' def getObjectName(self): return self._strobj.getValue() def __setObjectSource(self,object,normal): if object: self._setObjectByName(self._strobj.getValue()) else: self.__clearScriptLink() self._inform_updated() self.__updateDisplay(object,normal) def __updateDisplay(self,object,normal=False): if object: self.height = LINE_HEIGHT * 3 + LINE_SPACE * 2 elif normal: self.height = self._full_height else: self.height = LINE_HEIGHT * 4 + LINE_SPACE * 3 self._strobj.visible = object self._btnrot.visible = object self._btnloc.visible = object self._fls.visible = normal self._flx.visible = not object self._fly.visible = not object self._flz.visible = not object self._recalc_dim() def setSource(self,source): if source == 'normal': self._btnobj.setValue(False) self._btnnorm.setValue(True) self.__setObjectSource(False,True) elif source == 'xyz': self._btnobj.setValue(False) self._btnnorm.setValue(False) self.__setObjectSource(False,False) else: if source == 'rotation': self._btnrot.setValue(True) self._btnloc.setValue(False) elif source == 'location': self._btnrot.setValue(False) self._btnloc.setValue(True) else: raise ValueError('illegal value %r' % source) self._btnobj.setValue(True) self.__setObjectSource(True,False) self._inform_updated() def setObjectName(self,name): self._strobj.setValue(name) self._setObjectByName(name) def getScriptLinkName(self): return (self.name + '-scriptlink')[:21] def __addScriptLink(self): self.__clearScriptLink() lnkname = self.getScriptLinkName() try: text = Text.Get(lnkname) text.clear() except NameError: text = Text.New(lnkname) text.write('''\ # DO NOT EDIT THIS TEXT! # This text is autogenerated by the Demolition Script GUI. try: from Blender import DemolitionGui%sCallback as callback except: def makeCallback(): import demolition from Blender import Object, Scene from math import sqrt import Blender def norm(vec): return sqrt(vec[0]**2 + vec[1]**2 + vec[2]**2 ) def xnormalize(vec): scale = norm(vec) if scale == 0.0: return scale, (0.,0.,0.) else: return scale, (vec[0] / scale, vec[1] / scale, vec[2] / scale) settings = Scene.GetCurrent().properties['DemolitionGUI'][%r] source = settings['source'] object = Object.Get(settings['object']) propertyType = demolition.propertiesDef[%r] _min = propertyType.min _max = propertyType.max def trim(x,y,z): min = _min max = _max if x < min[0]: x = min[0] elif x > max[0]: x = max[0] if y < min[1]: y = min[1] elif y > max[1]: y = max[1] if z < min[2]: z = min[2] elif z > max[2]: z = max[2] return (x, y, z) if source == 'rotation': def callback(): nvec = object.rot scale = sum(object.size) / 3 demolition.setProperty(%r, trim(nvec[0] * scale, nvec[1] * scale, nvec[2] * scale)) elif source == 'location': def callback(): scale, nvec = xnormalize(object.loc) demolition.setProperty(%r, trim(nvec[0] * scale, nvec[1] * scale, nvec[2] * scale)) else: raise Exception('configuration error: no object as vector source configuration for ' %r ' found') Blender.DemolitionGui%sCallback = callback return callback callback = makeCallback() callback()''' % ((self.name,) * 7)) self._obj.addScriptLink(lnkname,"Redraw") def _callback(self): self._inform_updated(self.getValueFromObject()) def __clearScriptLink(self): lnkname = self.getScriptLinkName() if self._obj is not None: self._obj.clearScriptLinks([lnkname]) try: text = Text.Get(lnkname) except NameError: pass else: if text.users == 0: Text.unlink(text) def _setObjectByName(self,name): if not name: self._obj = None return try: self._obj = Object.Get(name) except ValueError, e: PupMenu("Error%%t|%s" % str(e)) if self._obj is None: self._strobj.setValue('') else: self._strobj.setValue(self._obj.name) else: self.__addScriptLink() self._inform_updated(self.getValueFromObject()) def _inform_updated(self,value=None): if value is None: value = self.getValue() for listener in self.iterActionListeners(): listener(value) def getValueFromObject(self): # print # print "get from object" if self._btnrot.getValue(): nvec = self._obj.rot scale = sum(self._obj.size) / 3 else: scale, nvec = xnormalize(self._obj.loc) vec = self.trim(nvec[0] * scale, nvec[1] * scale, nvec[2] * scale) # print scale, nvec, vec self._fls.setValue(scale) self._flx.setValue(vec[0]) self._fly.setValue(vec[1]) self._flz.setValue(vec[2]) SimpleInput.setValue(self, nvec) # print self._flx.getValue() return vec def getValueFromNormal(self): # print # print "get from normal" nvec = self.getNormalized() scale = self._fls.getValue() # print scale, nvec return (nvec[0] * scale, nvec[1] * scale, nvec[2] * scale) def getValue(self): # print "obj:",self._obj if self._btnobj.getValue() and self._obj is not None: return self.getValueFromObject() else: return self.getValueFromNormal() def setValue(self,val): scale, nvec = xnormalize(val) SimpleInput.setValue(self, nvec) self._flx.setValue(val[0]) self._fly.setValue(val[1]) self._flz.setValue(val[2]) self._fls.setValue(scale) def draw(self,x,y,scale): self_x = int((self.x + x) * scale) BlenderLabel(self._label, self_x, int((self._label_y + y) * scale), int(self.width * scale), int(LINE_HEIGHT * scale)) if self._btnnorm.getValue(): self._widget = Normal(self.event, self_x, int((self._normal_y + y) * scale), int(self.width * scale), int(self._normal_height * scale), self._widget.val, self.tooltip) # , self.action) # <- this doesn't work for some reason def scriptlink_action(val): if val: ScriptLinkButton.enable() else: ScriptLinkButton.disable() class ScriptLinkButton(BoolInput): __slots__ = () def __init__(self,x,y,width,height=LINE_HEIGHT): BoolInput.__init__(self,"enable script link", ScriptLinkButton.isEnabled(),x,y,width,height, tooltip='Link the demolition script to the current scene.') self.addActionListener(scriptlink_action) def draw(self,x,y,scale): self._widget.val = ScriptLinkButton.isEnabled() BoolInput.draw(self,x,y,scale) def setValue(self,value): BoolInput.setValue(self,value) if value: ScriptLinkButton.enable() else: ScriptLinkButton.disable() @staticmethod def isEnabled(): scene = Scene.GetCurrent() return "demolition-scriptlink" in scene.getScriptLinks("FrameChanged") @staticmethod def enable(): try: text = Text.Get("demolition-scriptlink") text.clear() except NameError: text = Text.New("demolition-scriptlink") text.write('''\ # DO NOT EDIT THIS TEXT! # This text is autogenerated by the Demolition Script GUI. from demolition import main main()''') scene = Scene.GetCurrent() scene.addScriptLink("demolition-scriptlink","FrameChanged") @staticmethod def disable(): scene = Scene.GetCurrent() scene.clearScriptLinks(["demolition-scriptlink"]) try: text = Text.Get("demolition-scriptlink") except NameError: pass else: if text.users == 0: Text.unlink(text) class Button(Widget): __slots__ = ('event') def __init__(self,name,x,y,width,height=LINE_HEIGHT,tooltip=''): Widget.__init__(self,name,x,y,width,height,tooltip=tooltip) self.event = Widget.newEvent() def draw(self,x,y,scale): PushButton(self.name, self.event, int((self.x + x) * scale), int((self.y + y) * scale), int(self.width * scale), int(self.height * scale), self.tooltip, self._action) def ok_action(val): updateProperties() curframe = Get('curframe') if curframe != 1: Set('curframe',1) if not ScriptLinkButton.isEnabled(): demolition.main() Set('curframe',curframe) else: demolition.main() class OkButton(Button): __slots__ = () def __init__(self,x,y,width,height=LINE_HEIGHT): Button.__init__(self,"Apply",x,y,width,height, tooltip='Apply the settings and recalculate the start frame.') self.addActionListener(ok_action) def cancel_action(val): Exit() class CancelButton(Button): __slots__ = () def __init__(self,x,y,width,height=LINE_HEIGHT): Button.__init__(self,"Exit",x,y,width,height,tooltip='Discard changes and exit script.') self.addActionListener(cancel_action) class MenuInput(SimpleInput): __slots__ = ('_values','_label','_menu') # TODO: could be implemented better. def __init__(self,name,value,x,y,width,height=LINE_HEIGHT,tooltip='',values={}): self._values = list(values) SimpleInput.__init__(self,name,self._values.index(value),int,x,y,width,height,tooltip=tooltip) items = [(values[key], self._values.index(key)) for key in values] items.sort() self._label = name + ':' self._menu = '%s %%t|%s' % (name, '|'.join('%s %%x%d' % (disply, n) for disply, n in items)) def _action(self,event,value): SimpleInput._action(self,event,self._values[self._widget.val]) def getValue(self): return self._values[self._widget.val] def setValue(self,value): SimpleInput.setValue(self, self._values.index(value)) def draw(self,x,y,scale): w = int(self.width * scale * .5) h = int(self.height * scale) self_x = int((self.x + x) * scale) self_y = int((self.y + y) * scale) BlenderLabel(self._label, self_x, self_y, w, h) self._widget = Menu(self._menu, self.event, self_x + w, self_y, w, h, self._widget.val, self.tooltip, self._action) class GroupWidget(Widget): __slots__ = ( '_childs','_child_listeners','_exp_listeners','_expanded_height', '_label','_btnexp' ) def __init__(self,name,expanded,childs,x,y,width,tooltip='',visible=True): Widget.__init__(self,name,x,y,width,LINE_HEIGHT + GROUP_SPACE,tooltip,visible) self._childs = [] self._child_listeners = [] self._exp_listeners = set() self._expanded_height = LINE_HEIGHT + GROUP_SPACE self._label = Label(name + ':',x,y,width-LINE_HEIGHT,LINE_HEIGHT) self._btnexp = BoolInput(' ',expanded,x+width-LINE_HEIGHT,y,LINE_HEIGHT,LINE_HEIGHT,tooltip='expand group') self.addChilds(childs) if expanded: self.height = self._expanded_height self.expanded = expanded self.addMoveListener(WeakListener(self._onMove)) self._btnexp.addActionListener(WeakListener(self._expaction)) self._onMove(self.x,self.y) def _expaction(self,val): self.__updateDisplay(val) for listener in self._exp_listeners: listener(val) def addExpansionListener(self,listener): self._exp_listeners.add(listener) def removeExpansionListener(self,listener): self._exp_listeners.add(listener) def clearExpansionListener(self): self._exp_listeners.clear() def _setVisible(self,visible): SimpleInput._setVisible(self,visible) self._label.visible = visible self._btnexp.visible = visible if self.expanded: for child in self._childs: child.visible = visible visible = property(lambda self:self._visible,_setVisible) def _onResize(self,width,height): w = width - LINE_HEIGHT self._label.width = w self._btnexp.x = w + self.x for child in self._childs: child.width = width if self.expanded: height = self._expanded_height else: height = LINE_HEIGHT + GROUP_SPACE self._height = height def _onChildResize(self): self._expanded_height = sum(child.height + LINE_SPACE for child in self._childs) + LINE_HEIGHT + GROUP_SPACE if self.expanded: self.height = self._expanded_height self._onMove(self.x,self.y) def _onMove(self,x,y): self._label.x = x self._btnexp.x = x + self.width - LINE_HEIGHT y += self.height - GROUP_SPACE - LINE_HEIGHT self._label.y = y self._btnexp.y = y if self.expanded: y -= LINE_SPACE for child in self._childs: y -= child.height child.x = x child.y = y y -= LINE_SPACE def clearChilds(self): for listener in self._child_listeners: listener.clear() self._child_listeners = [] self._childs = [] self._expanded_height = LINE_HEIGHT + GROUP_SPACE self.height = self._expanded_height def addChilds(self,childs): for child in childs: self.addChild(child) def addChild(self,child): self._childs.append(child) self._expanded_height += child.height + LINE_SPACE if self.expanded: self.height = self._expanded_height child.x = self.x child.width = self.width self._child_listeners.append(ChildResizeListener(self,child)) y = self.y + self.height - GROUP_SPACE - LINE_HEIGHT self._label.y = y self._btnexp.y = y self._onMove(self.x,self.y) def __len__(self): return len(self._childs) def __iter__(self): return iter(self._childs) def _setExpanded(self,val): val = bool(val) self._btnexp.setValue(val) self.__updateDisplay(val) def __updateDisplay(self,expanded): for child in self._childs: child.visible = expanded if expanded: self.height = self._expanded_height else: self.height = LINE_HEIGHT + GROUP_SPACE self._onMove(self.x,self.y) expanded = property(lambda self:self._btnexp.getValue(),_setExpanded) class WeakListener(object): __slots__ = ('__weakref__', '_func','_obj') def __init__(self,listener): self._func = listener.im_func self._obj = weakref.ref(listener.im_self) def __call__(self,*args,**kwargs): obj = self._obj() if obj is None: print "*** lost reference to %s's self object ***" % self._func.func_name else: self._func(obj,*args,**kwargs) class ChildResizeListener(object): __slots__ = ('__weakref__', '_widget','_child') def __init__(self,widget,child): self._widget = weakref.ref(widget) self._child = weakref.ref(child) child.addResizeListener(self) def getWidget(self): if self._widget is not None: return self._widget() else: return None def getChild(self): if self._child is not None: return self._child() else: return None def clear(self): widget = self.getWidget() child = self.getChild() if widget is not None and child is not None: child.removeResizeListener(self) self._widget = None def __call__(self,width,height): widget = self.getWidget() if widget is not None: widget._onChildResize() def getVectorSettings(name): try: settings = guiProperties[name] except KeyError: settings = {'source':'xyz','object':''} guiProperties[name] = settings return settings def setVectorSettings(name,source,object): """ source has to be 'xyz', 'normal', 'rotation' or 'location'. object is an objects name. """ try: settings = guiProperties[name] except KeyError: settings = {} guiProperties[name] = settings settings['source'] = source settings['object'] = object def getGroupExpanded(groupName): key = 'group-%s-expanded' % groupName try: expanded = guiProperties[key] except KeyError: expanded = False guiProperties[key] = expanded return expanded def setGroupExpanded(groupName,expanded): key = 'group-%s-expanded' % groupName guiProperties[key] = bool(expanded) def getWidgetByType(propertyType,*args,**kwargs): tp = type(propertyType) if tp is demolition.IntProperty: span = abs(propertyType.max - propertyType.min) slider = span <= SLIDER_SPAN widget = IntInput(min=propertyType.min,max=propertyType.max,slider=slider,*args,**kwargs) elif tp is demolition.FloatProperty: span = abs(propertyType.max - propertyType.min) slider = span <= SLIDER_SPAN widget = FloatInput(min=propertyType.min,max=propertyType.max,slider=slider,*args,**kwargs) elif tp is demolition.GroupProperty: widget = GroupInput(length=propertyType.length,*args,**kwargs) elif tp is demolition.StrProperty: widget = StrInput(length=propertyType.length,*args,**kwargs) elif tp is demolition.BoolProperty: widget = BoolInput(*args,**kwargs) elif tp is demolition.VectorProperty: widget = VectorInput(min=propertyType.min,max=propertyType.max,*args,**kwargs) settings = getVectorSettings(widget.name) widget.setObjectName(settings['object']) widget.setSource(settings['source']) def vaction(val): setVectorSettings(widget.name, widget.getSource(), widget.getObjectName()) widget.addActionListener(vaction) elif tp is demolition.SetProperty: widget = MenuInput(values=propertyType.values,*args,**kwargs) else: raise TypeError("unknown PropertyType: " + tp.__name__) def action(val): changedProperty(widget.name,val) widget.addActionListener(action) return widget def changedProperty(name,value): print 'changed: %s = %r' % (name, value) # updateProperties() demolition.setProperty(name,value) def updateProperties(): properties = {} for input in inputs: properties[input.name] = input.getValue() demolition.setProperties(**properties) # print properties inputs = [] MAX_Y_WIDGET = None btnCancel = None btnOk = None btnLink = None def buildGUI(): global MAX_Y_WIDGET, btnCancel, btnOk, btnLink, inputs groups = {} for name, propertyType in demolition.propertiesDef.iteritems(): groupName = propertyType.group try: group = groups[groupName] except KeyError: group = [] groups[groupName] = group group.append(name) groupNames = groups.keys() groupNames.sort(reverse=True) x = 0 y = 0 BUTTON_WIDTH = (LINE_WIDTH - LINE_SPACE) * .5 btnCancel = CancelButton(x, y, BUTTON_WIDTH) btnOk = OkButton(x + BUTTON_WIDTH + LINE_SPACE, y, BUTTON_WIDTH) y += LINE_SPACE + LINE_HEIGHT btnLink = ScriptLinkButton(x, y, LINE_WIDTH) y += LINE_SPACE + LINE_HEIGHT + GROUP_SPACE prev = None for groupName in groupNames: group = groups[groupName] group.sort() childs = [] for name in group: tp = demolition.propertiesDef[name] if not tp.deprecated: value = demolition.getProperty(name) input = getWidgetByType(tp,name,value,x,y,LINE_WIDTH,tooltip=tooltips.get(name,'')) inputs.append(input) childs.append(input) groupWidget = GroupWidget(groupName,getGroupExpanded(groupName),childs,x,y,LINE_WIDTH) groupWidget.addExpansionListener(ExpansionListener(groupName)) if prev is not None: groupWidget.keepAbove(prev) prev = groupWidget y += groupWidget.height + LINE_SPACE MAX_Y_WIDGET = prev class ExpansionListener(object): __slots__ = ('__weakref__', 'groupName',) def __init__(self,groupName): self.groupName = groupName def __call__(self,val): setGroupExpanded(self.groupName,val) buildGUI() try: import psyco except ImportError: pass else: psyco.full() if __name__ == '__main__': Widget.gui() # vim:noexpandtab:sw=4:ts=4: