def __init__(self, *args, **kwargs): super(SquarePart, self).__init__(self, *args, **kwargs) self._maxRow = kwargs.get('maxRow', app().prefs.squareRows) self._maxCol = kwargs.get('maxCol', app().prefs.squareCols) self._maxBase = int( kwargs.get('maxSteps', app().prefs.squareSteps) * self._step - 1)
def __init__(self, *args, **kwargs): super(HoneycombPart, self).__init__(self, *args, **kwargs) self._maxRow = kwargs.get('maxRow', app().prefs.honeycombRows) self._maxCol = kwargs.get('maxCol', app().prefs.honeycombCols) self._maxBase = int( kwargs.get('maxSteps', app().prefs.honeycombSteps) * self._step - 1)
def __init__(self): super(Document, self).__init__() self._undoStack = QUndoStack() self._parts = [] self._assemblies = [] self._controller = None self._selectedPart = None # the dictionary maintains what is selected self._selectionDict = {} # the added list is what was recently selected or deselected self._selectedChangedDict = {} cadnano.app().documentWasCreatedSignal.emit(self)
def staplePreDecoratorSelected(self, listNames): """ Callback function that is called from mayaSelectionContext when a PreDecorator geometry is called, notifies the Part Model of this event. XXX - [SB] In the future we should clean up this interaction. """ if (len(listNames) > 1): # If we have more than one PreDecorator Selected, deselect all but # the last one cmds.select(listNames[0:len(listNames) - 1], deselect=True) selectionList = [] for name in listNames: if name in self.decoratorToVirtualHelixItem: (virtualHelixItem, baseIdx, strand) = \ self.decoratorToVirtualHelixItem[name] selectionList.append( (virtualHelixItem.row(), virtualHelixItem.col(), baseIdx)) if len(selectionList) == 0 and \ self.selectionCountCache == 0: # a dumb cache check to prevent deselection to be broadcasted too # many times, but could cause problems return # XXX - [SB] we want to only send the signal to "active part" but # not sure how to get that for doc in app().documentControllers: if doc.win.actionModify.isChecked(): for partItem in doc.win.solidroot.partItems(): partModel = partItem.part() partModel.selectPreDecorator(selectionList) self.selectionCountCache = len(selectionList)
def __init__(self, mID, modelStrand, virtualHelixItem): """ The parent should be a VirtualHelixItem. Initialize function creates the Maya Node for the strand, and setups the lookup tables inside of mayaObjectManager (Mom) so that the Maya Node can be globally found given a strand, and the other way around. Also, sets up StrandItemController that is used to setup all the slots and signals between strand model and this strandItem. """ self._modelStrand = modelStrand self._virtualHelixItem = virtualHelixItem self._viewroot = app().activeDocument.win.solidroot mayaNodeInfo = () # print "solidview.StrandItem.__init__ %s" % mID if (modelStrand.strandSet().isScaffold()): mayaNodeInfo = self.createMayaHelixNodes( virtualHelixItem.x(), virtualHelixItem.y(), modelStrand.oligo().color(), StrandType.Scaffold, mID) else: mayaNodeInfo = self.createMayaHelixNodes( virtualHelixItem.x(), virtualHelixItem.y(), modelStrand.oligo().color(), StrandType.Staple, mID) #self.onStrandDidMove(strand) m = Mom() m.cnToMaya[modelStrand] = mayaNodeInfo m.mayaToCn[mayaNodeInfo[2]] = modelStrand m.mayaToCn[mayaNodeInfo[0]] = modelStrand self.updateSize() self._controller = StrandItemController(self, modelStrand)
def doRelease(self): # print "RELEASED" self.isMouseDown = False if(self.deltaFront != 0): app().activeDocument.document().resizeSelection(self.deltaFront) if(self.deltaBack != 0): app().activeDocument.document().resizeSelection(self.deltaBack) m = Mom() m.strandsSelected(self.helicesNames, (True, True)) cmds.hide(helixManip.transformName) self.frontDistance = 0 self.backDistance = 0 return OpenMaya.kUnknownParameter
def safeScale(self, delta): currentScaleLevel = self.transform().m11() scaleFactor = 1 + delta * \ (self._scaleDownRate if delta < 0 else self._scaleUpRate) * \ (app().prefs.zoomSpeed/100.) newScaleLevel = currentScaleLevel * scaleFactor newScaleLevel = util.clamp(currentScaleLevel * scaleFactor,\ self._scale_limit_min,\ self._scale_limit_max) scaleChange = newScaleLevel / currentScaleLevel self.scale(scaleChange, scaleChange) self.resetGL()
def _addBasesCallback(self, n): """ Given a user-chosen number of bases to add, snap it to an index where index modulo stepsize is 0 and calls resizeVirtualHelices to adjust to that size. """ part = self._modelPart self._addBasesDialog.intValueSelected.disconnect( self._addBasesCallback) del self._addBasesDialog maxDelta = int(n / part.stepSize()) * part.stepSize() part.resizeVirtualHelices(0, maxDelta) if app().isInMaya(): import maya.cmds as cmds cmds.select(clear=True)
def strandsSelected(self, listNames, value=(True, True)): if self.ignoreExternalSelectionSignal: return self.ignoreExternalSelectionSignal = True strandList = [] for nodeName in listNames: if (nodeName in self.mayaToCn): strandList.append(self.mayaToCn[nodeName]) doc = app().activeDocument # # XXX [SB] THIS IS A HACK, should not need to do this!!! # doc.win.pathroot.clearStrandSelections() doc.win.solidroot.selectedChanged(strandList, value) self.ignoreExternalSelectionSignal = False
def __init__(self, win): super(PathToolManager, self).__init__() self.window = win self._activeTool = None self._activePart = None self.selectTool = SelectTool(self) self.pencilTool = PencilTool(self) self.breakTool = BreakTool(self) self.eraseTool = EraseTool(self) self.insertionTool = InsertionTool(self) self.skipTool = SkipTool(self) self.paintTool = PaintTool( self) # (self, win.pathGraphicsView.toolbar) self.addSeqTool = AddSeqTool(self) def installTool(toolName, window): toolWidget = getattr(window, 'actionPath' + toolName) lToolName = toolName[0].lower() + toolName[1:] tool = getattr(self, lToolName + 'Tool') tool.actionName = 'actionPath' + toolName def clickHandler(self): toolWidget.setChecked(True) self.setActiveTool(tool) if hasattr(tool, 'widgetClicked'): tool.widgetClicked() selectToolMethodName = 'choose' + toolName + 'Tool' setattr(self.__class__, selectToolMethodName, clickHandler) handler = getattr(self, selectToolMethodName) toolWidget.triggered.connect(handler) return toolWidget tools = ('Select', 'Pencil', 'Break', 'Erase', 'Insertion', 'Skip', 'Paint', 'AddSeq') ag = QActionGroup(win) # Call installTool on every tool list( map((lambda toolName: ag.addAction(installTool(toolName, win))), tools)) ag.setExclusive(True) # Select the preferred Startup tool startupToolName = app().prefs.getStartupToolName() getattr(self, 'choose' + startupToolName + 'Tool')()
def _removeBasesClicked(self): """ Determines the minimum maxBase index where index modulo stepsize == 0 and is to the right of the rightmost nonempty base, and then resize each calls the resizeVirtualHelices to adjust to that size. """ part = self._modelPart stepSize = part.stepSize() # first find out the right edge of the part idx = part.indexOfRightmostNonemptyBase() # next snap to a multiple of stepsize idx = int(ceil(float(idx + 1) / stepSize)) * stepSize # finally, make sure we're a minimum of stepSize bases idx = util.clamp(idx, part.stepSize(), 10000) delta = idx - (part.maxBaseIdx() + 1) if delta < 0: part.resizeVirtualHelices(0, delta) if app().isInMaya(): import maya.cmds as cmds cmds.select(clear=True)
def mouseReleaseEvent(self, event): """docstring for mouseReleaseEvent""" part = self.part() uS = part.undoStack() strands = [] # Look at the undo stack in reverse order for i in range(uS.index()-1, 0, -1): # Check for contiguous strand additions m = _strand_re.match(uS.text(i)) if m: strands.insert(0, list(map(int, m.groups()))) else: break if len(strands) > 1: autoScafType = app().prefs.getAutoScafType() util.beginSuperMacro(part, "Auto-connect") if autoScafType == "Mid-seam": self.autoScafMidSeam(strands) elif autoScafType == "Raster": self.autoScafRaster(strands) util.endSuperMacro(part)
def decode(document, string): if cadnano.app().isGui(): # from ui.dialogs.ui_latticetype import Ui_LatticeType # util.qtWrapImport('QtGui', globals(), ['QDialog', 'QDialogButtonBox']) dialog = QDialog() dialogLT = Ui_LatticeType() # reusing this dialog, should rename dialogLT.setupUi(dialog) # try: # try to do it fast # try: # import cjson # packageObject = cjson.decode(string) # except: # fall back to if cjson not available or on decode error # packageObject = json.loads(string) # except ValueError: # dialogLT.label.setText("Error decoding JSON object.") # dialogLT.buttonBox.setStandardButtons(QDialogButtonBox.Ok) # dialog.exec() # return packageObject = json.loads(string) if packageObject.get('.format', None) != 'caDNAno2': import_legacy_dict(document, packageObject)
def __init__(self, parent=None, docCtrlr=None): super(DocumentWindow, self).__init__(parent) self.controller = docCtrlr doc = docCtrlr.document() self.setupUi(self) self.settings = QSettings() self._readSettings() # Slice setup self.slicescene = QGraphicsScene(parent=self.sliceGraphicsView) self.sliceroot = SliceRootItem(rect=self.slicescene.sceneRect(),\ parent=None,\ window=self,\ document=doc) self.sliceroot.setFlag( QGraphicsItem.GraphicsItemFlag.ItemHasNoContents) self.slicescene.addItem(self.sliceroot) self.slicescene.setItemIndexMethod( QGraphicsScene.ItemIndexMethod.NoIndex) assert self.sliceroot.scene() == self.slicescene self.sliceGraphicsView.setScene(self.slicescene) self.sliceGraphicsView.sceneRootItem = self.sliceroot self.sliceGraphicsView.setName("SliceView") self.sliceToolManager = SliceToolManager(self) # Path setup self.pathscene = QGraphicsScene(parent=self.pathGraphicsView) self.pathroot = PathRootItem(rect=self.pathscene.sceneRect(),\ parent=None,\ window=self,\ document=doc) self.pathroot.setFlag(QGraphicsItem.GraphicsItemFlag.ItemHasNoContents) self.pathscene.addItem(self.pathroot) self.pathscene.setItemIndexMethod( QGraphicsScene.ItemIndexMethod.NoIndex) assert self.pathroot.scene() == self.pathscene self.pathGraphicsView.setScene(self.pathscene) self.pathGraphicsView.sceneRootItem = self.pathroot self.pathGraphicsView.setScaleFitFactor(0.9) self.pathGraphicsView.setName("PathView") self.pathColorPanel = ColorPanel() self.pathGraphicsView.toolbar = self.pathColorPanel # HACK for customqgraphicsview self.pathscene.addItem(self.pathColorPanel) self.pathToolManager = PathToolManager(self) self.sliceToolManager.pathToolManager = self.pathToolManager self.pathToolManager.sliceToolManager = self.sliceToolManager self.tool_managers = (self.sliceToolManager, self.pathToolManager) # set the selection filter default doc.documentSelectionFilterChangedSignal.emit( ["endpoint", "scaffold", "staple", "xover"]) self.pathGraphicsView.setupGL() self.sliceGraphicsView.setupGL() if GL: pass # self.slicescene.drawBackground = self.drawBackgroundGL # self.pathscene.drawBackground = self.drawBackgroundGL if app().isInMaya(): from .solidview.solidrootitem import SolidRootItem self.splitter.setOrientation(Qt.Vertical) self.setUnifiedTitleAndToolBarOnMac(False) modState = self.actionModify.isChecked() self.solidroot = SolidRootItem(parent=None, document=doc, modState=modState) # Edit menu setup self.actionUndo = docCtrlr.undoStack().createUndoAction(self) self.actionRedo = docCtrlr.undoStack().createRedoAction(self) self.actionUndo.setText( QApplication.translate("MainWindow", "Undo", None)) self.actionUndo.setShortcut( QApplication.translate("MainWindow", "Ctrl+Z", None)) self.actionRedo.setText( QApplication.translate("MainWindow", "Redo", None)) self.actionRedo.setShortcut( QApplication.translate("MainWindow", "Ctrl+Shift+Z", None)) self.sep = QAction(self) self.sep.setSeparator(True) self.menuEdit.insertAction(self.actionModify, self.sep) self.menuEdit.insertAction(self.sep, self.actionRedo) self.menuEdit.insertAction(self.actionRedo, self.actionUndo) self.splitter.setSizes([400, 400]) # balance splitter size self.statusBar().showMessage("")
import json from .legacydecoder import import_legacy_dict from cadnano2.ui.dialogs.ui_latticetype import Ui_LatticeType import cadnano2.util as util import cadnano2.cadnano as cadnano if cadnano.app().isGui(): # headless: from cadnano2.ui.dialogs.ui_latticetype import Ui_LatticeType util.qtWrapImport('QtWidgets', globals(), ['QDialog', 'QDialogButtonBox']) def decode(document, string): if cadnano.app().isGui(): # from ui.dialogs.ui_latticetype import Ui_LatticeType # util.qtWrapImport('QtGui', globals(), ['QDialog', 'QDialogButtonBox']) dialog = QDialog() dialogLT = Ui_LatticeType() # reusing this dialog, should rename dialogLT.setupUi(dialog) # try: # try to do it fast # try: # import cjson # packageObject = cjson.decode(string) # except: # fall back to if cjson not available or on decode error # packageObject = json.loads(string) # except ValueError: # dialogLT.label.setText("Error decoding JSON object.") # dialogLT.buttonBox.setStandardButtons(QDialogButtonBox.Ok) # dialog.exec() # return packageObject = json.loads(string)
def focusInEvent(self): app().undoGroup.setActiveStack(self.controller.undoStack())
from collections import defaultdict from cadnano2.model.document import Document from cadnano2.model.enum import LatticeType, StrandType from cadnano2.model.parts.honeycombpart import HoneycombPart from cadnano2.model.parts.squarepart import SquarePart from cadnano2.model.virtualhelix import VirtualHelix from cadnano2.views import styles import cadnano2.util as util import cadnano2.cadnano as cadnano # import Qt stuff into the module namespace with PySide, PyQt4 independence util.qtWrapImport('QtGui', globals(), ['QColor']) if cadnano.app().isGui(): from cadnano2.ui.dialogs.ui_latticetype import Ui_LatticeType util.qtWrapImport('QtWidgets', globals(), ['QDialog', 'QDialogButtonBox']) NODETAG = "node" NAME = "name" OBJ_ID = "objectid" INST_ID = "instanceid" DONE = "done" CHECKED = "check" LOCKED = "locked" VHELIX = "vhelix" NUM = "num" COL = "col" ROW = "row" SCAFFOLD = "scaffold" STAPLE = "staple" INSERTION = "insertion"
def import_legacy_dict(document, obj, latticeType=LatticeType.Honeycomb): """ Parses a dictionary (obj) created from reading a json file and uses it to populate the given document with model data. """ numBases = len(obj['vstrands'][0]['scaf']) if cadnano.app().isGui(): # from ui.dialogs.ui_latticetype import Ui_LatticeType # util.qtWrapImport('QtGui', globals(), ['QDialog', 'QDialogButtonBox']) dialog = QDialog() dialogLT = Ui_LatticeType() dialogLT.setupUi(dialog) # DETERMINE LATTICE TYPE if numBases % 21 == 0 and numBases % 32 == 0: if dialog.exec() == 1: latticeType = LatticeType.Square else: latticeType = LatticeType.Honeycomb elif numBases % 32 == 0: latticeType = LatticeType.Square elif numBases % 21 == 0: latticeType = LatticeType.Honeycomb else: if dialog.exec() == 1: latticeType = LatticeType.Square else: latticeType = LatticeType.Honeycomb else: # Headless, assume the latticeType arg was meaningful pass # DETERMINE MAX ROW,COL maxRowJson = maxColJson = 0 for helix in obj['vstrands']: maxRowJson = max(maxRowJson, int(helix['row']) + 1) maxColJson = max(maxColJson, int(helix['col']) + 1) # CREATE PART ACCORDING TO LATTICE TYPE if latticeType == LatticeType.Honeycomb: steps = numBases / 21 nRows = max(30, maxRowJson, cadnano.app().prefs.honeycombRows) nCols = max(32, maxColJson, cadnano.app().prefs.honeycombCols) part = HoneycombPart(document=document, maxRow=nRows, maxCol=nCols, maxSteps=steps) elif latticeType == LatticeType.Square: isSQ100 = True # check for custom SQ100 format for helix in obj['vstrands']: if helix['col'] != 0: isSQ100 = False break if isSQ100: dialogLT.label.setText("Is this a SQ100 file?") if dialog.exec() == 1: nRows, nCols = 100, 1 else: nRows, nCols = 40, 30 else: nRows, nCols = 40, 30 steps = numBases / 32 nRows = max(30, maxRowJson, cadnano.app().prefs.squareRows) nCols = max(32, maxColJson, cadnano.app().prefs.squareCols) part = SquarePart(document=document, maxRow=nRows, maxCol=nCols, maxSteps=steps) else: raise TypeError("Lattice type not recognized") document._addPart(part, useUndoStack=False) # POPULATE VIRTUAL HELICES orderedCoordList = [] vhNumToCoord = {} for helix in obj['vstrands']: vhNum = helix['num'] row = helix['row'] col = helix['col'] scaf = helix['scaf'] coord = (row, col) vhNumToCoord[vhNum] = coord orderedCoordList.append(coord) # make sure we retain the original order for vhNum in sorted(vhNumToCoord.keys()): row, col = vhNumToCoord[vhNum] part.createVirtualHelix(row, col, useUndoStack=False) part.setImportedVHelixOrder(orderedCoordList) # INSTALL STRANDS AND COLLECT XOVER LOCATIONS numHelixes = len(obj['vstrands']) - 1 scaf_seg = defaultdict(list) scaf_xo = defaultdict(list) stap_seg = defaultdict(list) stap_xo = defaultdict(list) try: for helix in obj['vstrands']: vhNum = helix['num'] row = helix['row'] col = helix['col'] scaf = helix['scaf'] stap = helix['stap'] insertions = helix['loop'] skips = helix['skip'] vh = part.virtualHelixAtCoord((row, col)) scafStrandSet = vh.scaffoldStrandSet() stapStrandSet = vh.stapleStrandSet() assert(len(scaf)==len(stap) and len(stap)==part.maxBaseIdx()+1 and\ len(scaf)==len(insertions) and len(insertions)==len(skips)) # read scaffold segments and xovers for i in range(len(scaf)): fiveVH, fiveIdx, threeVH, threeIdx = scaf[i] if fiveVH == -1 and threeVH == -1: continue # null base if isSegmentStartOrEnd(StrandType.Scaffold, vhNum, i, fiveVH,\ fiveIdx, threeVH, threeIdx): scaf_seg[vhNum].append(i) if fiveVH != vhNum and threeVH != vhNum: # special case scaf_seg[vhNum].append( i) # end segment on a double crossover if is3primeXover(StrandType.Scaffold, vhNum, i, threeVH, threeIdx): scaf_xo[vhNum].append((i, threeVH, threeIdx)) assert (len(scaf_seg[vhNum]) % 2 == 0) # install scaffold segments for i in range(0, len(scaf_seg[vhNum]), 2): lowIdx = scaf_seg[vhNum][i] highIdx = scaf_seg[vhNum][i + 1] scafStrandSet.createStrand(lowIdx, highIdx, useUndoStack=False) # read staple segments and xovers for i in range(len(stap)): fiveVH, fiveIdx, threeVH, threeIdx = stap[i] if fiveVH == -1 and threeVH == -1: continue # null base if isSegmentStartOrEnd(StrandType.Staple, vhNum, i, fiveVH,\ fiveIdx, threeVH, threeIdx): stap_seg[vhNum].append(i) if fiveVH != vhNum and threeVH != vhNum: # special case stap_seg[vhNum].append( i) # end segment on a double crossover if is3primeXover(StrandType.Staple, vhNum, i, threeVH, threeIdx): stap_xo[vhNum].append((i, threeVH, threeIdx)) assert (len(stap_seg[vhNum]) % 2 == 0) # install staple segments for i in range(0, len(stap_seg[vhNum]), 2): lowIdx = stap_seg[vhNum][i] highIdx = stap_seg[vhNum][i + 1] stapStrandSet.createStrand(lowIdx, highIdx, useUndoStack=False) except AssertionError: if not cadnano.app().isGui(): print("Unrecognized file format.") else: dialogLT.label.setText("Unrecognized file format.") dialogLT.buttonBox.setStandardButtons(QDialogButtonBox.Ok) dialog.exec() # INSTALL XOVERS for helix in obj['vstrands']: vhNum = helix['num'] row = helix['row'] col = helix['col'] scaf = helix['scaf'] stap = helix['stap'] insertions = helix['loop'] skips = helix['skip'] fromVh = part.virtualHelixAtCoord((row, col)) scafStrandSet = fromVh.scaffoldStrandSet() stapStrandSet = fromVh.stapleStrandSet() # install scaffold xovers for (idx5p, toVhNum, idx3p) in scaf_xo[vhNum]: # idx3p is 3' end of strand5p, idx5p is 5' end of strand3p strand5p = scafStrandSet.getStrand(idx5p) toVh = part.virtualHelixAtCoord(vhNumToCoord[toVhNum]) strand3p = toVh.scaffoldStrandSet().getStrand(idx3p) part.createXover(strand5p, idx5p, strand3p, idx3p, useUndoStack=False) # install staple xovers for (idx5p, toVhNum, idx3p) in stap_xo[vhNum]: # idx3p is 3' end of strand5p, idx5p is 5' end of strand3p strand5p = stapStrandSet.getStrand(idx5p) toVh = part.virtualHelixAtCoord(vhNumToCoord[toVhNum]) strand3p = toVh.stapleStrandSet().getStrand(idx3p) part.createXover(strand5p, idx5p, strand3p, idx3p, useUndoStack=False) # SET DEFAULT COLOR for oligo in part.oligos(): if oligo.isStaple(): defaultColor = styles.DEFAULT_STAP_COLOR else: defaultColor = styles.DEFAULT_SCAF_COLOR oligo.applyColor(defaultColor, useUndoStack=False) # COLORS, INSERTIONS, SKIPS for helix in obj['vstrands']: vhNum = helix['num'] row = helix['row'] col = helix['col'] scaf = helix['scaf'] stap = helix['stap'] insertions = helix['loop'] skips = helix['skip'] vh = part.virtualHelixAtCoord((row, col)) scafStrandSet = vh.scaffoldStrandSet() stapStrandSet = vh.stapleStrandSet() # install insertions and skips for baseIdx in range(len(stap)): sumOfInsertSkip = insertions[baseIdx] + skips[baseIdx] if sumOfInsertSkip != 0: scaf_strand = scafStrandSet.getStrand(baseIdx) stap_strand = stapStrandSet.getStrand(baseIdx) if scaf_strand: scaf_strand.addInsertion(baseIdx, sumOfInsertSkip, useUndoStack=False) elif stap_strand: stap_strand.addInsertion(baseIdx, sumOfInsertSkip, useUndoStack=False) # end for # populate colors for baseIdx, colorNumber in helix['stap_colors']: color = QColor((colorNumber >> 16) & 0xFF, (colorNumber >> 8) & 0xFF, colorNumber & 0xFF).name() strand = stapStrandSet.getStrand(baseIdx) strand.oligo().applyColor(color, useUndoStack=False)
pos += minStapleLegLen if not isTerminalStrand: maxIdx -= minStapleLegLen while idx <= maxIdx: isTerminalNode = isTerminalStrand and idx == maxIdx nodes.append((pos, strand, idx, isTerminalNode)) idx += 1 pos += 1 pos += minStapleLegLen - 1 else: minIdx, idx = strand.lowIdx(), strand.highIdx() + 1 if strand != firstStrand: idx -= minStapleLegLen pos += minStapleLegLen if not isTerminalStrand: minIdx += minStapleLegLen while idx >= minIdx: isTerminalNode = isTerminalStrand and idx == minIdx nodes.append((pos, strand, idx, isTerminalNode)) idx -= 1 pos += 1 pos += minStapleLegLen - 1 strand = nextStrand if isTerminalStrand: break # if nodes: # dump the node array to stdout # print ' '.join(str(n[0])+':'+str(n[2]) for n in nodes) + (' :: %i'%oligo.length()) + repr(nodes[-1]) return nodes cadnano.app().breakStaples = breakStaples
def updateSelectionBoxes(self): selectedItems = cmds.ls(self.helixTransformName + "*", selection=True) if selectedItems: bbox = cmds.exactWorldBoundingBox(selectedItems) cmds.setAttr(self.selectionBox + ".scale", bbox[3] - bbox[0], bbox[4] - bbox[1], bbox[5] - bbox[2], type="double3") cmds.setAttr(self.selectionBox + ".translate", (bbox[0] + bbox[3]) / 2, (bbox[1] + bbox[4]) / 2, (bbox[2] + bbox[5]) / 2, type="double3") cmds.showHidden(self.selectionBox) selectionDict = app().activeDocument.document().selectionDict() frontEp = 0 backEp = 0 for strandSetDict in selectionDict.values(): for strand, value in strandSetDict.items(): frontEp += int(value[0]) backEp += int(value[1]) if (frontEp == 0) ^ (backEp == 0): epBoundBox = None for strandSetDict in selectionDict.values(): for strand, value in strandSetDict.items(): # XXX The following line is a work around for broken # path selection in model, remove when fixed if not strand in self.cnToMaya: continue helixNode = self.cnToMaya[strand] boundBox = cmds.exactWorldBoundingBox(helixNode[1]) # might be better to get the rise this way... #n = OpenMaya.MFnDependencyNode(helixNode) #risePlug = n.findPlug("rise") #rise = risePlug.asDouble() # but this works too idxL, idxH = strand.idxs() rise = (boundBox[5] - boundBox[2]) / (idxH - idxL + 1) if frontEp == 0: boundBox[5] = boundBox[2] + rise elif backEp == 0: boundBox[2] = boundBox[5] - rise if epBoundBox == None: epBoundBox = boundBox else: # union the boxes epBoundBox[0] = min(epBoundBox[0], boundBox[0]) epBoundBox[1] = min(epBoundBox[1], boundBox[1]) epBoundBox[2] = min(epBoundBox[2], boundBox[2]) epBoundBox[3] = max(epBoundBox[3], boundBox[3]) epBoundBox[4] = max(epBoundBox[4], boundBox[4]) epBoundBox[5] = max(epBoundBox[5], boundBox[5]) # XXX The following line is a work around for broken # path selection in model, remove when fixed if not epBoundBox == None: cmds.showHidden(self.epSelectionBox) cmds.setAttr(self.epSelectionBox + ".scale", epBoundBox[3] - epBoundBox[0], epBoundBox[4] - epBoundBox[1], epBoundBox[5] - epBoundBox[2], type="double3") cmds.setAttr(self.epSelectionBox + ".translate", (epBoundBox[0] + epBoundBox[3]) / 2, (epBoundBox[1] + epBoundBox[4]) / 2, (epBoundBox[2] + epBoundBox[5]) / 2, type="double3") else: cmds.hide(self.epSelectionBox) else: cmds.hide(self.selectionBox) cmds.hide(self.epSelectionBox)