class VirtualHelixItem(QGraphicsEllipseItem): """ The VirtualHelixItem is an individual circle that gets drawn in the SliceView as a child of the PartItem. Taken as a group, many SliceHelix instances make up the crossection of the DNAPart. Clicking on a SliceHelix adds a VirtualHelix to the DNAPart. The SliceHelix then changes appearence and paints its corresponding VirtualHelix number. """ # set up default, hover, and active drawing styles _useBrush = QBrush(styles.orangefill) _usePen = QPen(styles.orangestroke, styles.SLICE_HELIX_STROKE_WIDTH) _radius = styles.SLICE_HELIX_RADIUS _outOfSlicePen = QPen(styles.lightorangestroke,\ styles.SLICE_HELIX_STROKE_WIDTH) _outOfSliceBrush = QBrush(styles.lightorangefill) _rect = QRectF(0, 0, 2 * _radius, 2 * _radius) _font = styles.SLICE_NUM_FONT _ZVALUE = styles.ZSLICEHELIX+3 def __init__(self, modelVirtualHelix, emptyHelixItem): """ emptyHelixItem is a EmptyHelixItem that will act as a QGraphicsItem parent """ super(VirtualHelixItem, self).__init__(parent=emptyHelixItem) self._virtualHelix = modelVirtualHelix self._emptyHelixItem = emptyHelixItem self.hide() # drawing related self.isHovered = False self.setAcceptsHoverEvents(True) # self.setFlag(QGraphicsItem.ItemIsSelectable) self.setZValue(self._ZVALUE) self.lastMousePressAddedBases = False self.setBrush(self._outOfSliceBrush) self.setPen(self._outOfSlicePen) self.setRect(self._rect) # handle the label specific stuff self._label = self.createLabel() self.setNumber() self.createArrow() self._controller = VirtualHelixItemController(self, modelVirtualHelix) self.show() # end def ### SIGNALS ### ### SLOTS ### def virtualHelixNumberChangedSlot(self, virtualHelix): """ receives a signal containing a virtualHelix and the oldNumber as a safety check """ self.setNumber() # end def def virtualHelixRemovedSlot(self, virtualHelix): self._controller.disconnectSignals() self._controller = None self._emptyHelixItem.setNotHovered() self._virtualHelix = None self._emptyHelixItem = None self.scene().removeItem(self._label) self._label = None self.scene().removeItem(self) # end def def strandAddedSlot(self, sender, strand): pass # end def ### def createLabel(self): label = QGraphicsSimpleTextItem("%d" % self._virtualHelix.number()) label.setFont(self._font) label.setZValue(self._ZVALUE) label.setParentItem(self) return label # end def def createArrow(self): rad = self._radius pen = QPen() pen.setWidth(3) color = QColor(Qt.blue) color.setAlphaF(0.25) pen.setBrush(color) if self._virtualHelix.isEvenParity(): arrow = QGraphicsLineItem(rad, rad, 2*rad, rad, self) else: arrow = QGraphicsLineItem(0, rad, rad, rad, self) arrow.setTransformOriginPoint(rad, rad) arrow.setZValue(400) arrow.setPen(pen) self.arrow = arrow self.arrow.hide() # end def def updateArrow(self, idx): part = self.part() tpb = part._twistPerBase angle = idx*tpb self.arrow.setRotation(angle + part._twistOffset) # end def def setNumber(self): """docstring for setNumber""" vh = self._virtualHelix num = vh.number() label = self._label radius = self._radius if num != None: label.setText("%d" % num) else: return y_val = radius / 3 if num < 10: label.setPos(radius / 1.5, y_val) elif num < 100: label.setPos(radius / 3, y_val) else: # _number >= 100 label.setPos(0, y_val) bRect = label.boundingRect() posx = bRect.width()/2 posy = bRect.height()/2 label.setPos(radius-posx, radius-posy) # end def def part(self): return self._emptyHelixItem.part() def virtualHelix(self): return self._virtualHelix # end def def number(self): return self.virtualHelix().number() def setActiveSliceView(self, isActiveNow, idx): if isActiveNow: self.setPen(self._usePen) self.setBrush(self._useBrush) self.updateArrow(idx) self.arrow.show() else: self.setPen(self._outOfSlicePen) self.setBrush(self._outOfSliceBrush) self.arrow.hide() # end def ############################ User Interaction ############################ def sceneEvent(self, event): """Included for unit testing in order to grab events that are sent via QGraphicsScene.sendEvent().""" # if self._parent.sliceController.testRecorder: # coord = (self._row, self._col) # self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord) if event.type() == QEvent.MouseButtonPress: self.mousePressEvent(event) return True elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return True elif event.type() == QEvent.MouseMove: self.mouseMoveEvent(event) return True QGraphicsItem.sceneEvent(self, event) return False def hoverEnterEvent(self, event): """ If the selection is configured to always select everything, we don't draw a focus ring around everything, instead we only draw a focus ring around the hovered obj. """ # if self.selectAllBehavior(): # self.setSelected(True) # forward the event to the emptyHelixItem as well self._emptyHelixItem.hoverEnterEvent(event) # end def def hoverLeaveEvent(self, event): # if self.selectAllBehavior(): # self.setSelected(False) self._emptyHelixItem.hoverEnterEvent(event)
class VirtualHelixItem(QGraphicsEllipseItem): """ The VirtualHelixItem is an individual circle that gets drawn in the SliceView as a child of the PartItem. Taken as a group, many SliceHelix instances make up the crossection of the DNAPart. Clicking on a SliceHelix adds a VirtualHelix to the DNAPart. The SliceHelix then changes appearence and paints its corresponding VirtualHelix number. """ # set up default, hover, and active drawing styles _useBrush = QBrush(styles.orangefill) _usePen = QPen(styles.orangestroke, styles.SLICE_HELIX_STROKE_WIDTH) _radius = styles.SLICE_HELIX_RADIUS _outOfSlicePen = QPen(styles.lightorangestroke,\ styles.SLICE_HELIX_STROKE_WIDTH) _outOfSliceBrush = QBrush(styles.lightorangefill) _rect = QRectF(0, 0, 2 * _radius, 2 * _radius) _font = styles.SLICE_NUM_FONT _ZVALUE = styles.ZSLICEHELIX + 3 def __init__(self, modelVirtualHelix, emptyHelixItem): """ emptyHelixItem is a EmptyHelixItem that will act as a QGraphicsItem parent """ super(VirtualHelixItem, self).__init__(parent=emptyHelixItem) self._virtualHelix = modelVirtualHelix self._emptyHelixItem = emptyHelixItem self.hide() # drawing related self.isHovered = False self.setAcceptsHoverEvents(True) # self.setFlag(QGraphicsItem.ItemIsSelectable) self.setZValue(self._ZVALUE) self.lastMousePressAddedBases = False self.setBrush(self._outOfSliceBrush) self.setPen(self._outOfSlicePen) self.setRect(self._rect) # handle the label specific stuff self._label = self.createLabel() self.setNumber() self._controller = VirtualHelixItemController(self, modelVirtualHelix) self.show() # end def ### SIGNALS ### ### SLOTS ### def virtualHelixNumberChangedSlot(self, virtualHelix): """ receives a signal containing a virtualHelix and the oldNumber as a safety check """ self.setNumber() # end def def virtualHelixRemovedSlot(self, virtualHelix): self._controller.disconnectSignals() self._controller = None self._emptyHelixItem.setNotHovered() self._virtualHelix = None self._emptyHelixItem = None self.scene().removeItem(self._label) self._label = None self.scene().removeItem(self) # end def def strandAddedSlot(self, sender, strand): pass # end def ### def createLabel(self): label = QGraphicsSimpleTextItem("%d" % self._virtualHelix.number()) label.setFont(self._font) label.setZValue(self._ZVALUE) label.setParentItem(self) return label # end def def setNumber(self): """docstring for setNumber""" vh = self._virtualHelix num = vh.number() label = self._label radius = self._radius if num != None: label.setText("%d" % num) else: return y_val = radius / 3 if num < 10: label.setPos(radius / 1.5, y_val) elif num < 100: label.setPos(radius / 3, y_val) else: # _number >= 100 label.setPos(0, y_val) bRect = label.boundingRect() posx = bRect.width() / 2 posy = bRect.height() / 2 label.setPos(radius - posx, radius - posy) # end def def part(self): return self._emptyHelixItem.part() def virtualHelix(self): return self._virtualHelix # end def def number(self): return self.virtualHelix().number() def setActiveSliceView(self, isActiveNow): if isActiveNow: self.setPen(self._usePen) self.setBrush(self._useBrush) else: self.setPen(self._outOfSlicePen) self.setBrush(self._outOfSliceBrush) # end def ############################ User Interaction ############################ def sceneEvent(self, event): """Included for unit testing in order to grab events that are sent via QGraphicsScene.sendEvent().""" # if self._parent.sliceController.testRecorder: # coord = (self._row, self._col) # self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord) if event.type() == QEvent.MouseButtonPress: self.mousePressEvent(event) return True elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return True elif event.type() == QEvent.MouseMove: self.mouseMoveEvent(event) return True QGraphicsItem.sceneEvent(self, event) return False def hoverEnterEvent(self, event): """ If the selection is configured to always select everything, we don't draw a focus ring around everything, instead we only draw a focus ring around the hovered obj. """ # if self.selectAllBehavior(): # self.setSelected(True) # forward the event to the emptyHelixItem as well self._emptyHelixItem.hoverEnterEvent(event) # end def def hoverLeaveEvent(self, event): # if self.selectAllBehavior(): # self.setSelected(False) self._emptyHelixItem.hoverEnterEvent(event)
class VirtualHelixItem(QGraphicsEllipseItem): """ The VirtualHelixItem is an individual circle that gets drawn in the SliceView as a child of the PartItem. Taken as a group, many SliceHelix instances make up the crossection of the DNAPart. Clicking on a SliceHelix adds a VirtualHelix to the DNAPart. The SliceHelix then changes appearence and paints its corresponding VirtualHelix number. """ # set up default, hover, and active drawing styles _useBrush = QBrush(styles.orangefill) _usePen = QPen(styles.orangestroke, styles.SLICE_HELIX_STROKE_WIDTH) _radius = styles.SLICE_HELIX_RADIUS _outOfSlicePen = QPen(styles.lightorangestroke,\ styles.SLICE_HELIX_STROKE_WIDTH) _outOfSliceBrush = QBrush(styles.lightorangefill) _rect = QRectF(0, 0, 2 * _radius, 2 * _radius) _font = styles.SLICE_NUM_FONT _ZVALUE = styles.ZSLICEHELIX+3 def __init__(self, modelVirtualHelix, emptyHelixItem): """ emptyHelixItem is a EmptyHelixItem that will act as a QGraphicsItem parent """ super(VirtualHelixItem, self).__init__(parent=emptyHelixItem) self._virtualHelix = modelVirtualHelix self._emptyHelixItem = emptyHelixItem self.hide() # drawing related self.isHovered = False self.setAcceptsHoverEvents(True) # self.setFlag(QGraphicsItem.ItemIsSelectable) self.setZValue(self._ZVALUE) self.lastMousePressAddedBases = False self.setBrush(self._outOfSliceBrush) self.setPen(self._outOfSlicePen) self.setRect(self._rect) # handle the label specific stuff self._label = self.createLabel() self.setNumber() self._controller = VirtualHelixItemController(self, modelVirtualHelix) self.show() # end def ### SIGNALS ### ### SLOTS ### def virtualHelixNumberChangedSlot(self, virtualHelix): """ receives a signal containing a virtualHelix and the oldNumber as a safety check """ self.setNumber() # end def def virtualHelixRemovedSlot(self, virtualHelix): self._controller.disconnectSignals() self._controller = None self._emptyHelixItem.setNotHovered() self._virtualHelix = None self._emptyHelixItem = None self.scene().removeItem(self._label) self._label = None self.scene().removeItem(self) # end def def strandAddedSlot(self, sender, strand): pass # end def ### def createLabel(self): label = QGraphicsSimpleTextItem("%d" % self._virtualHelix.number()) label.setFont(self._font) label.setZValue(self._ZVALUE) label.setParentItem(self) return label # end def def setNumber(self): """docstring for setNumber""" vh = self._virtualHelix num = vh.number() label = self._label radius = self._radius if num != None: label.setText("%d" % num) else: return y_val = radius / 3 if num < 10: label.setPos(radius / 1.5, y_val) elif num < 100: label.setPos(radius / 3, y_val) else: # _number >= 100 label.setPos(0, y_val) bRect = label.boundingRect() posx = bRect.width()/2 posy = bRect.height()/2 label.setPos(radius-posx, radius-posy) # end def def part(self): return self._emptyHelixItem.part() def virtualHelix(self): return self._virtualHelix # end def def number(self): return self.virtualHelix().number() def setActiveSliceView(self, isActiveNow): if isActiveNow: self.setPen(self._usePen) self.setBrush(self._useBrush) else: self.setPen(self._outOfSlicePen) self.setBrush(self._outOfSliceBrush) # end def ############################ User Interaction ############################ def sceneEvent(self, event): """Included for unit testing in order to grab events that are sent via QGraphicsScene.sendEvent().""" # if self._parent.sliceController.testRecorder: # coord = (self._row, self._col) # self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord) if event.type() == QEvent.MouseButtonPress: self.mousePressEvent(event) return True elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return True elif event.type() == QEvent.MouseMove: self.mouseMoveEvent(event) return True QGraphicsItem.sceneEvent(self, event) return False def hoverEnterEvent(self, event): """ If the selection is configured to always select everything, we don't draw a focus ring around everything, instead we only draw a focus ring around the hovered obj. """ # if self.selectAllBehavior(): # self.setSelected(True) # forward the event to the emptyHelixItem as well self._emptyHelixItem.hoverEnterEvent(event) # end def def hoverLeaveEvent(self, event): # if self.selectAllBehavior(): # self.setSelected(False) self._emptyHelixItem.hoverEnterEvent(event) # end def # def mousePressEvent(self, event): # action = self.decideAction(event.modifiers()) # action(self) # self.dragSessionAction = action # # def mouseMoveEvent(self, event): # parent = self._helixItem # posInParent = parent.mapFromItem(self, QPointF(event.pos())) # # Qt doesn't have any way to ask for graphicsitem(s) at a # # particular position but it *can* do intersections, so we # # just use those instead # parent.probe.setPos(posInParent) # for ci in parent.probe.collidingItems(): # if isinstance(ci, SliceHelix): # self.dragSessionAction(ci) # # end def # def mouseReleaseEvent(self, event): # self.part().needsFittingToView.emit() # def decideAction(self, modifiers): # """ On mouse press, an action (add scaffold at the active slice, add # segment at the active slice, or create virtualhelix if missing) is # decided upon and will be applied to all other slices happened across by # mouseMoveEvent. The action is returned from this method in the form of a # callable function.""" # vh = self.virtualHelix() # if vh == None: return SliceHelix.addVHIfMissing # idx = self.part().activeSlice() # if modifiers & Qt.ShiftModifier: # if vh.stap().get(idx) == None: # return SliceHelix.addStapAtActiveSliceIfMissing # else: # return SliceHelix.nop # if vh.scaf().get(idx) == None: # return SliceHelix.addScafAtActiveSliceIfMissing # return SliceHelix.nop # # def nop(self): # pass
class VirtualHelixItem(object): """ VirtualHelixItem is the container for StrandItems for is the Maya 3D View, it does not get visualized in any way right now. """ baseWidth = styles.PATH_BASE_WIDTH def __init__(self, partItem, modelVirtualHelix, x, y): """ Initialize member variables. Setup VirtualHelixItemController, that takes care of all the slots and signals between VirtualHelix model and this VirtualHelixItem. """ self._partItem = partItem self._modelVirtualHelix = modelVirtualHelix self._x = x self._y = y coords = modelVirtualHelix.coord() self._row = coords[0] self._col = coords[1] self.strandIDs = [] self._modState = False self._strandItems = {} self.stapleIndicatorCount = 0 self.stapleModIndicatorIDs = [] self._controller = VirtualHelixItemController(self, modelVirtualHelix) # end def def partItem(self): """PartItem Accessor - parent Item""" return self._partItem def virtualHelix(self): """VirtualHelix Model Accessor""" return self._modelVirtualHelix def number(self): """VirtualHelix Model Index Number Accessor""" return self._modelVirtualHelix.number() def row(self): """Row Accessor""" return self.coord()[0] def col(self): """Col Accessor""" return self.coord()[1] def x(self): """Actual x position in the the Maya 3D View""" return self._x # end def def y(self): """Actual y position in the the Maya 3D View""" return self._y # end def def coord(self): """Returns a tuple (row, column) of the VitualHelix model""" return self._modelVirtualHelix.coord() # end def def isEvenParity(self): """Returns parity of the VitualHelix model""" return self._modelVirtualHelix.isEvenParity() def StrandIDs(self): """ Returns a list of Strand IDs within this VH, the IDs are suffixes of Maya Nodes, and are used to for Maya<->Cadnano communication in mayaObjectManager """ return self.strandIDs def setModifyState(self, val): """Update Modify state for this VirtualHelixItem""" self._modState = val ### SLOTS ### def strandAddedSlot(self, sender, strand): """ Instantiates a StrandItem upon notification that the model has a new Strand. The StrandItem is responsible for creating its own controller for communication with the model, and for adding itself to its parent (which is *this* VirtualHelixItem, i.e. 'self'). """ # print "solidview.VirtualHelixItem.strandAddedSlot" m = Mom() mID = m.strandMayaID(strand) self.strandIDs.append(mID) sI = StrandItem(mID, strand, self) self._strandItems[sI] = True self.updateDecorators() # print "solidview.VirtualHelixItem.strandAddedSlot done %s" % mID # end def @pyqtSlot(object) def decoratorAddedSlot(self, decorator): """decoratorAddedSlot - empty""" pass @pyqtSlot(object) def virtualHelixNumberChangedSlot(self, virtualHelix): """virtualHelixNumberChangedSlot - empty""" pass # end def @pyqtSlot(object) def virtualHelixRemovedSlot(self, virtualHelix): """ Clears out private variables and disconnects signals """ # print "solidview.VirtualHelixItem.virtualHelixRemovedSlot" self._partItem.removeVirtualHelixItem(self) self._partItem = None self._modelVirtualHelix = None self._controller.disconnectSignals() self._controller = None # end def def updateDecorators(self): """ Clears out Pre-Decorators, and re-populates them if they should be visible """ self.clearDecorators() if self._modState: m = Mom() for mID in self.strandIDs: mayaNodeInfo = "%s%s" % (m.helixMeshName, mID) # print "mayaNodeInfo: %s" % mayaNodeInfo strand = m.mayaToCn[mayaNodeInfo] if strand.strandSet().isStaple(): self.createDecorators(strand) def cadnanoVBaseToMayaCoords(self, base, strand): """ Given a Strand and a Base, returns a 3D location of that base """ m = Mom() mID = m.strandMayaID(strand) cylinderName = "%s%s" % (m.helixNodeName, mID) if cmds.objExists(cylinderName): rise = cmds.getAttr("%s.rise" % cylinderName) startBase = cmds.getAttr("%s.startBase" % cylinderName) startPos = cmds.getAttr("%s.startPos" % cylinderName) base0Pos = startPos[0][1] + (startBase * rise) ourPos = base0Pos - (base * rise) zComp = ourPos rotation = cmds.getAttr("%s.rotation" % cylinderName) radius = cmds.getAttr("%s.radius" % cylinderName) parity = cmds.getAttr("%s.parity" % cylinderName) strandType = cmds.getAttr("%s.strandType" % cylinderName) rotationOffset = cmds.getAttr("%s.rotationOffset" % cylinderName) decoratorRotOffset = cmds.getAttr("%s.decoratorRotOffset" % cylinderName) # not clear why decoratorRotOffset is not in radians but # rotationOffset is decoratorRotOffset = decoratorRotOffset * math.pi / 180 starting_rotation = (math.pi * (not parity)) + rotationOffset + decoratorRotOffset + (math.pi * strandType) fullrotation = -rotation * base * math.pi / 180 # print full rotation xComp = self._x + radius * math.cos(starting_rotation + fullrotation) yComp = self._y + radius * math.sin(starting_rotation + fullrotation) # print "%f %f %f" % (xComp, yComp, zComp) return (xComp, yComp, zComp) else: raise IndexError def clearDecorators(self): """Remove all the Pre-Decortators""" m = Mom() for mID in self.stapleModIndicatorIDs: transformName = "%s%s" % (m.decoratorTransformName, mID) # print "delete %s" % transformName m = Mom() m.removeDecoratorMapping(mID) if cmds.objExists(transformName): cmds.delete(transformName) self.stapleModIndicatorIDs = [] self.stapleIndicatorCount = 0 def createDecorators(self, strand): """Create a set of new Pre-Decortators for a given strand""" m = Mom() strandId = m.strandMayaID(strand) totalNumBases = self._modelVirtualHelix.part().maxBaseIdx() preDecoratorIdxList = strand.getPreDecoratorIdxList() for baseIdx in preDecoratorIdxList: # XXX [SB+AT] NOT THREAD SAFE while cmds.objExists("%s%s_%s" % (m.decoratorNodeName, strandId, self.stapleIndicatorCount)): self.stapleIndicatorCount += 1 stapleId = "%s_%s" % (strandId, self.stapleIndicatorCount) coords = self.cadnanoVBaseToMayaCoords(baseIdx, strand) stapleModNodeInfo = self.createDecoratorNodes(coords, stapleId) self.stapleModIndicatorIDs.append(stapleId) m = Mom() m.decoratorToVirtualHelixItem[stapleModNodeInfo[2]] = (self, baseIdx, strand) m.decoratorToVirtualHelixItem[stapleModNodeInfo[1]] = (self, baseIdx, strand) def createDecoratorNodes(self, coords, mID): """Create a actual Maya Nodes for a new Pre-Decortators""" m = Mom() stapleModIndicatorName = "%s%s" % (m.decoratorNodeName, mID) transformName = "%s%s" % (m.decoratorTransformName, mID) meshName = "%s%s" % (m.decoratorMeshName, mID) shaderName = "%s" % m.decoratorShaderName cmds.createNode("transform", name=transformName, skipSelect=True) cmds.setAttr("%s.rotateX" % transformName, 90) cmds.setAttr("%s.translateX" % transformName, coords[0]) cmds.setAttr("%s.translateY" % transformName, coords[1]) cmds.setAttr("%s.translateZ" % transformName, coords[2]) cmds.createNode("mesh", name=meshName, parent=transformName, skipSelect=True) # cmds.createNode("spPreDecoratorNode", name=stapleModIndicatorName) cmds.createNode("polySphere", name=stapleModIndicatorName, skipSelect=True) cmds.setAttr("%s.radius" % stapleModIndicatorName, 0.25) cmds.setAttr("%s.subdivisionsAxis" % stapleModIndicatorName, 4) cmds.setAttr("%s.subdivisionsHeight" % stapleModIndicatorName, 4) # cmds.connectAttr("%s.outputMesh" % stapleModIndicatorName, # "%s.inMesh" % meshName) cmds.connectAttr("%s.output" % stapleModIndicatorName, "%s.inMesh" % meshName) if not cmds.objExists(shaderName): # Shader does not exist create one cmds.shadingNode("lambert", asShader=True, name=shaderName) cmds.sets(n="%sSG" % shaderName, r=True, nss=True, em=True) cmds.connectAttr("%s.outColor" % shaderName, "%sSG.surfaceShader" % shaderName) cmds.setAttr("%s.color" % shaderName, 0.0, 0.0, 0.0, type="double3") cmds.sets(meshName, forceElement="%sSG" % shaderName) else: # shader exist connect cmds.sets(meshName, forceElement="%sSG" % shaderName) return (stapleModIndicatorName, transformName, meshName, shaderName) # end def def removeStrandItem(self, strandItem): """Remove a StrandItem from the local list of StrandItems""" del self._strandItems[strandItem]
class VirtualHelixItem(QGraphicsPathItem): """VirtualHelixItem for PathView""" findChild = util.findChild # for debug def __init__(self, partItem, modelVirtualHelix, viewroot, activeTool): super(VirtualHelixItem, self).__init__(partItem.proxy()) self._partItem = partItem self._modelVirtualHelix = modelVirtualHelix self._viewroot = viewroot self._activeTool = activeTool self._controller = VirtualHelixItemController(self, modelVirtualHelix) self._handle = VirtualHelixHandleItem(modelVirtualHelix, partItem, viewroot) self._lastStrandSet = None self._lastIdx = None self._scaffoldBackground = None self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) self.setBrush(QBrush(Qt.NoBrush)) view = viewroot.scene().views()[0] view.levelOfDetailChangedSignal.connect(self.levelOfDetailChangedSlot) shouldShowDetails = view.shouldShowDetails() pen = QPen(styles.minorgridstroke, styles.MINOR_GRID_STROKE_WIDTH) pen.setCosmetic(shouldShowDetails) self.setPen(pen) self.refreshPath() self.setAcceptHoverEvents(True) # for pathtools self.setZValue(styles.ZPATHHELIX) # end def ### SIGNALS ### ### SLOTS ### def levelOfDetailChangedSlot(self, boolval): """Not connected to the model, only the QGraphicsView""" pen = self.pen() pen.setCosmetic(boolval) self.setPen(pen) # end def def strandAddedSlot(self, sender, strand): """ Instantiates a StrandItem upon notification that the model has a new Strand. The StrandItem is responsible for creating its own controller for communication with the model, and for adding itself to its parent (which is *this* VirtualHelixItem, i.e. 'self'). """ StrandItem(strand, self, self._viewroot) # end def def decoratorAddedSlot(self, decorator): """ Instantiates a DecoratorItem upon notification that the model has a new Decorator. The Decorator is responsible for creating its own controller for communication with the model, and for adding itself to its parent (which is *this* VirtualHelixItem, i.e. 'self'). """ pass def virtualHelixNumberChangedSlot(self, virtualHelix, number): self._handle.setNumber() # end def def virtualHelixRemovedSlot(self, virtualHelix): self._controller.disconnectSignals() self._controller = None scene = self.scene() self._handle.remove() scene.removeItem(self) self._partItem.removeVirtualHelixItem(self) self._partItem = None self._modelVirtualHelix = None self._activeTool = None self._handle = None # end def ### ACCESSORS ### def activeTool(self): return self._activeTool def coord(self): return self._modelVirtualHelix.coord() # end def def viewroot(self): return self._viewroot # end def def handle(self): return self._handle # end def def part(self): return self._partItem.part() # end def def partItem(self): return self._partItem # end def def number(self): return self._modelVirtualHelix.number() # end def def virtualHelix(self): return self._modelVirtualHelix # end def def window(self): return self._partItem.window() # end def ### DRAWING METHODS ### def isStrandOnTop(self, strand): sS = strand.strandSet() isEvenParity = self._modelVirtualHelix.isEvenParity() return isEvenParity and sS.isScaffold() or\ not isEvenParity and sS.isStaple() # end def def isStrandTypeOnTop(self, strandType): isEvenParity = self._modelVirtualHelix.isEvenParity() return isEvenParity and strandType == StrandType.Scaffold or \ not isEvenParity and strandType == StrandType.Staple # end def def upperLeftCornerOfBase(self, idx, strand): x = idx * _baseWidth y = 0 if self.isStrandOnTop(strand) else _baseWidth return x, y # end def def upperLeftCornerOfBaseType(self, idx, strandType): x = idx * _baseWidth y = 0 if self.isStrandTypeOnTop(strandType) else _baseWidth return x, y # end def def refreshPath(self): """ Returns a QPainterPath object for the minor grid lines. The path also includes a border outline and a midline for dividing scaffold and staple bases. """ bw = _baseWidth bw2 = 2 * bw part = self.part() path = QPainterPath() subStepSize = part.subStepSize() canvasSize = part.maxBaseIdx()+1 # border path.addRect(0, 0, bw * canvasSize, 2 * bw) # minor tick marks for i in range(canvasSize): x = round(bw * i) + .5 if i % subStepSize == 0: path.moveTo(x-.5, 0) path.lineTo(x-.5, bw2) path.lineTo(x-.25, bw2) path.lineTo(x-.25, 0) path.lineTo(x, 0) path.lineTo(x, bw2) path.lineTo(x+.25, bw2) path.lineTo(x+.25, 0) path.lineTo(x+.5, 0) path.lineTo(x+.5, bw2) # path.moveTo(x-.5, 0) # path.lineTo(x-.5, 2 * bw) # path.lineTo(x+.5, 2 * bw) # path.lineTo(x+.5, 0) else: path.moveTo(x, 0) path.lineTo(x, 2 * bw) # staple-scaffold divider path.moveTo(0, bw) path.lineTo(bw * canvasSize, bw) self.setPath(path) if self._modelVirtualHelix.scaffoldIsOnTop(): scaffoldY = 0 else: scaffoldY = bw # if self._scaffoldBackground == None: # highlightr = QGraphicsRectItem(0, scaffoldY, bw * canvasSize, bw, self) # highlightr.setBrush(QBrush(styles.scaffold_bkg_fill)) # highlightr.setPen(QPen(Qt.NoPen)) # highlightr.setFlag(QGraphicsItem.ItemStacksBehindParent) # self._scaffoldBackground = highlightr # else: # self._scaffoldBackground.setRect(0, scaffoldY, bw * canvasSize, bw) # end def def resize(self): """Called by part on resize.""" self.refreshPath() ### PUBLIC SUPPORT METHODS ### def setActive(self, idx): """Makes active the virtual helix associated with this item.""" self.part().setActiveVirtualHelix(self._modelVirtualHelix, idx) # end def ### EVENT HANDLERS ### def mousePressEvent(self, event): """ Parses a mousePressEvent to extract strandSet and base index, forwarding them to approproate tool method as necessary. """ self.scene().views()[0].addToPressList(self) strandSet, idx = self.baseAtPoint(event.pos()) self.setActive(idx) toolMethodName = str(self._activeTool()) + "MousePress" ### uncomment for debugging modifier selection # strandSet, idx = self.baseAtPoint(event.pos()) # row, col = strandSet.virtualHelix().coord() # self._partItem.part().selectPreDecorator([(row,col,idx)]) if hasattr(self, toolMethodName): self._lastStrandSet, self._lastIdx = strandSet, idx getattr(self, toolMethodName)(strandSet, idx) else: event.setAccepted(False) # end def def mouseMoveEvent(self, event): """ Parses a mouseMoveEvent to extract strandSet and base index, forwarding them to approproate tool method as necessary. """ toolMethodName = str(self._activeTool()) + "MouseMove" if hasattr(self, toolMethodName): strandSet, idx = self.baseAtPoint(event.pos()) if self._lastStrandSet != strandSet or self._lastIdx != idx: self._lastStrandSet, self._lastIdx = strandSet, idx getattr(self, toolMethodName)(strandSet, idx) else: event.setAccepted(False) # end def def customMouseRelease(self, event): """ Parses a mouseReleaseEvent to extract strandSet and base index, forwarding them to approproate tool method as necessary. """ toolMethodName = str(self._activeTool()) + "MouseRelease" if hasattr(self, toolMethodName): getattr(self, toolMethodName)(self._lastStrandSet, self._lastIdx) else: event.setAccepted(False) # end def ### COORDINATE UTILITIES ### def baseAtPoint(self, pos): """ Returns the (strandType, index) under the location x,y or None. It shouldn't be possible to click outside a pathhelix and still call this function. However, this sometimes happens if you click exactly on the top or bottom edge, resulting in a negative y value. """ x, y = pos.x(), pos.y() mVH = self._modelVirtualHelix baseIdx = int(floor(x / _baseWidth)) minBase, maxBase = 0, mVH.part().maxBaseIdx() if baseIdx < minBase or baseIdx >= maxBase: baseIdx = util.clamp(baseIdx, minBase, maxBase) if y < 0: y = 0 # HACK: zero out y due to erroneous click strandIdx = floor(y * 1. / _baseWidth) if strandIdx < 0 or strandIdx > 1: strandIdx = int(util.clamp(strandIdx, 0, 1)) strandSet = mVH.getStrandSetByIdx(strandIdx) return (strandSet, baseIdx) # end def def keyPanDeltaX(self): """How far a single press of the left or right arrow key should move the scene (in scene space)""" dx = self._partItem.part().stepSize() * _baseWidth return self.mapToScene(QRectF(0, 0, dx, 1)).boundingRect().width() # end def def hoverLeaveEvent(self, event): self._partItem.updateStatusBar("") # end def def hoverMoveEvent(self, event): """ Parses a mouseMoveEvent to extract strandSet and base index, forwarding them to approproate tool method as necessary. """ baseIdx = int(floor(event.pos().x() / _baseWidth)) loc = "%d[%d]" % (self.number(), baseIdx) self._partItem.updateStatusBar(loc) activeTool = self._activeTool() toolMethodName = str(activeTool) + "HoverMove" if hasattr(self, toolMethodName): strandType, idxX, idxY = activeTool.baseAtPoint(self, event.pos()) getattr(self, toolMethodName)(strandType, idxX, idxY) # end def ### TOOL METHODS ### def pencilToolMousePress(self, strandSet, idx): """strand.getDragBounds""" # print "%s: %s[%s]" % (util.methodName(), strandSet, idx) activeTool = self._activeTool() if not activeTool.isDrawingStrand(): activeTool.initStrandItemFromVHI(self, strandSet, idx) activeTool.setIsDrawingStrand(True) # end def def pencilToolMouseMove(self, strandSet, idx): """strand.getDragBounds""" # print "%s: %s[%s]" % (util.methodName(), strandSet, idx) activeTool = self._activeTool() if activeTool.isDrawingStrand(): activeTool.updateStrandItemFromVHI(self, strandSet, idx) # end def def pencilToolMouseRelease(self, strandSet, idx): """strand.getDragBounds""" # print "%s: %s[%s]" % (util.methodName(), strandSet, idx) activeTool = self._activeTool() if activeTool.isDrawingStrand(): activeTool.setIsDrawingStrand(False) activeTool.attemptToCreateStrand(self, strandSet, idx) # end def def pencilToolHoverMove(self, strandType, idxX, idxY): """Pencil the strand is possible.""" partItem = self.partItem() activeTool = self._activeTool() if not activeTool.isFloatingXoverBegin(): tempXover = activeTool.floatingXover() tempXover.updateFloatingFromVHI(self, strandType, idxX, idxY)
class VirtualHelixItem(QGraphicsEllipseItem): """ The VirtualHelixItem is an individual circle that gets drawn in the SliceView as a child of the PartItem. Taken as a group, many SliceHelix instances make up the crossection of the DNAPart. Clicking on a SliceHelix adds a VirtualHelix to the DNAPart. The SliceHelix then changes appearence and paints its corresponding VirtualHelix number. """ # set up default, hover, and active drawing styles _useBrush = QBrush(styles.orangefill) _usePen = QPen(styles.orangestroke, styles.SLICE_HELIX_STROKE_WIDTH) _radius = styles.SLICE_HELIX_RADIUS _outOfSlicePen = QPen(styles.lightorangestroke, styles.SLICE_HELIX_STROKE_WIDTH) _outOfSliceBrush = QBrush(styles.lightorangefill) _rect = QRectF(0, 0, 2 * _radius, 2 * _radius) _font = styles.SLICE_NUM_FONT _ZVALUE = styles.ZSLICEHELIX + 3 def __init__(self, modelVirtualHelix, emptyHelixItem): """ emptyHelixItem is a EmptyHelixItem that will act as a QGraphicsItem parent """ super(VirtualHelixItem, self).__init__(parent=emptyHelixItem) self._virtualHelix = modelVirtualHelix self._emptyHelixItem = emptyHelixItem self.hide() # drawing related self.isHovered = False self.setAcceptHoverEvents(True) # self.setFlag(QGraphicsItem.ItemIsSelectable) self.setZValue(self._ZVALUE) self.lastMousePressAddedBases = False self.setBrush(self._outOfSliceBrush) self.setPen(self._outOfSlicePen) self.setRect(self._rect) # handle the label specific stuff self._label = self.createLabel() self.setNumber() self.arrows = list() self.createArrow() self._controller = VirtualHelixItemController(self, modelVirtualHelix) self.show() # end def ### SIGNALS ### ### SLOTS ### def virtualHelixNumberChangedSlot(self, virtualHelix): """ receives a signal containing a virtualHelix and the oldNumber as a safety check """ self.setNumber() # end def def virtualHelixRemovedSlot(self, virtualHelix): self._controller.disconnectSignals() self._controller = None self._emptyHelixItem.setNotHovered() self._virtualHelix = None self._emptyHelixItem = None self.scene().removeItem(self._label) self._label = None self.scene().removeItem(self) # end def def strandAddedSlot(self, sender, strand): pass # end def ### def createLabel(self): """ Adds a label with the helix number in the center of the helix item. """ label = QGraphicsSimpleTextItem("%d" % self._virtualHelix.number()) label.setFont(self._font) label.setZValue(self._ZVALUE + 10) label.setParentItem(self) return label # end def def genericArrow(self, color=None, alpha=None, width=2, rad=None, zvalue=3, hidden=True, rotation=0): """ Creates a generic arrow/marker/hand to indicate backbone twist location. """ rad = rad or self._radius pen = QPen() pen.setWidth(width) color = QColor(color or Qt.blue) color.setAlphaF(alpha or 0.25) pen.setBrush(color) # If we create the arrow depending on even/odd parity, then we can use the # same code for updatearrow for arrows on all helices. # (However, it does feel like a hack...) if self._virtualHelix.isEvenParity(): # If line width is 3, then the arrow marker looks better if it only has length of 0.9*rad. arrow = QGraphicsLineItem(rad * 1.1, rad, 0.95 * 2 * rad, rad, self) else: # was: 0, rad, rad, rad arrow = QGraphicsLineItem(0.1 * rad, rad, 0.9 * rad, rad, self) arrow.setTransformOriginPoint(rad, rad) arrow.setZValue(self._ZVALUE + zvalue) arrow.setPen(pen) if hidden: arrow.hide() if rotation: arrow.setRotation(rotation) return arrow def createArrow(self): """ Creates a marker/arrow/hand to indicate backbone twist location. Update: Adjusted scaffold marker and added marker for staple strand backbone. Question: What part of the nucleotide should be used for the marker? * The 5' phosphate? No, that is not really representative of the nucleotide. * The ribose C5'? * The ribose (average/center)? Or the ribose C1'? * The ribose C3'? Yes, this is actually a pretty good point. The "angle" of the minor groove depends a lot on this. Structurally, it is probably the C5' which is the most relevant, since the 5' phosphate should be completely flexible. However, when making a normal 'holliday' crossover between anti-parallel strands and you want to see which bases are opposite, then you have connection between C5' to C3' (not C5' to C5'). This would argue for using the ribose center. For consistency, I guess you could add both? Note that the phosphate between C3 and C5 accounts for as much angular rotation as the ribose. A further note: The minor/major groove will, of cause, depend on how you look at the helix. In cadnano, the slice view is viewed from left to right relative to the path view. This means that helices with even parity looks different in the sliceview than helices with odd parity. """ # For even parity, scafC5 is above scafC3, and vice versa for stap. # For even parity, it should be: scafC5 > scafC3 = stapC3 > stapC5. direction = 1 if self._virtualHelix.isEvenParity() else -1 arrowspecs = [ #dict(color=Qt.black, alpha=0.5, width=0.5, rad=self._radius, zvalue=0), # marker dict(color=Qt.blue, alpha=0.8, width=3, rad=self._radius, zvalue=direction * 4), # scafC3 dict(color=Qt.darkGray, alpha=0.8, width=3, rad=self._radius, zvalue=direction * 6), # scafC5 dict(color=Qt.red, alpha=0.8, width=3, rad=self._radius, zvalue=direction * 4, rotation=190), # stapC3 dict(color=Qt.darkGray, alpha=0.8, width=3, rad=self._radius, zvalue=direction * 2, rotation=175), # stapC5 ] arrowattrnames = [ 'arrow_scafC3', 'arrow_scafC5', 'arrow_stapC3', 'arrow_stapC5' ] for arrowname, arrowspec in zip(arrowattrnames, arrowspecs): arrow = self.genericArrow(**arrowspec) self.arrows.append(arrow) setattr(self, arrowname, arrow) # How much each arrow should be offset compared to the "global" virtualhelix angle at bp index: # Global angle seems to be: "scaffold, if scaffold were opposite staple". Yes. # For even parity stapC5' is more CW than stapC3' # For a part._twistOffset = -12, (190, 175) seems good for stapC3/C5. self.arrow_angle_offsets = [-40, -25, 190, 175] # [scafC3, scafC5, stapC3, stapC5] # angle C3-C3 is 180-10-40=130 degrees, C5-C5 is 180+5-25=160, average is 145. # (used to be 125, 155 and 140 avg, but that seems a bit much...) # end def def updateArrow(self, idx): """ Update: Adjusted scaffold marker and added marker for staple strand backbone. Note that currently, I have also adjusted part._twistOffset to make it right. Not sure if this is the right way to tweak it? """ part = self.part() tpb = part._twistPerBase # degrees angle = idx * tpb isevenparity = self._virtualHelix.isEvenParity() # the overall rotation of a helix is usually specified by the staple strand # (which usually makes the majority of crossovers) # The angle between the phosphate of a base pair between antiparallel strands is # approx 140 degrees (minor groove). It might be less, but let's assume 140 for now. # The "overall cadnano basepair angle" seems to be specified by # "where the scaffold backbone would be, if it was right opposite (180 degree) from the staple strand." direction = 1 if isevenparity else -1 for arrow, arrow_offset in zip(self.arrows, self.arrow_angle_offsets): arrow.setRotation(angle + part._twistOffset - arrow_offset * direction) # end def def setNumber(self): """docstring for setNumber""" vh = self._virtualHelix num = vh.number() label = self._label radius = self._radius if num != None: label.setText("%d" % num) else: return y_val = radius / 3 if num < 10: label.setPos(radius / 1.5, y_val) elif num < 100: label.setPos(radius / 3, y_val) else: # _number >= 100 label.setPos(0, y_val) bRect = label.boundingRect() posx = bRect.width() / 2 posy = bRect.height() / 2 label.setPos(radius - posx, radius - posy) # end def def part(self): return self._emptyHelixItem.part() def virtualHelix(self): return self._virtualHelix # end def def number(self): return self.virtualHelix().number() def setActiveSliceView(self, isActiveNow, idx): if isActiveNow: self.setPen(self._usePen) self.setBrush(self._useBrush) self.updateArrow(idx) for arrow in self.arrows: arrow.show() else: self.setPen(self._outOfSlicePen) self.setBrush(self._outOfSliceBrush) for arrow in self.arrows: arrow.hide() # end def ############################ User Interaction ############################ def sceneEvent(self, event): """Included for unit testing in order to grab events that are sent via QGraphicsScene.sendEvent().""" # if self._parent.sliceController.testRecorder: # coord = (self._row, self._col) # self._parent.sliceController.testRecorder.sliceSceneEvent(event, coord) if event.type() == QEvent.MouseButtonPress: self.mousePressEvent(event) return True elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return True elif event.type() == QEvent.MouseMove: self.mouseMoveEvent(event) return True QGraphicsItem.sceneEvent(self, event) return False def hoverEnterEvent(self, event): """ If the selection is configured to always select everything, we don't draw a focus ring around everything, instead we only draw a focus ring around the hovered obj. """ # if self.selectAllBehavior(): # self.setSelected(True) # forward the event to the emptyHelixItem as well self._emptyHelixItem.hoverEnterEvent(event) # end def def hoverLeaveEvent(self, event): # if self.selectAllBehavior(): # self.setSelected(False) self._emptyHelixItem.hoverEnterEvent(event)
class VirtualHelixItem(object): """ VirtualHelixItem is the container for StrandItems for is the Maya 3D View, it does not get visualized in any way right now. """ baseWidth = styles.PATH_BASE_WIDTH def __init__(self, partItem, modelVirtualHelix, x, y): """ Initialize member variables. Setup VirtualHelixItemController, that takes care of all the slots and signals between VirtualHelix model and this VirtualHelixItem. """ self._partItem = partItem self._modelVirtualHelix = modelVirtualHelix self._x = x self._y = y coords = modelVirtualHelix.coord() self._row = coords[0] self._col = coords[1] self.strandIDs = [] self._modState = False self._strandItems = {} self.stapleIndicatorCount = 0 self.stapleModIndicatorIDs = [] self._controller = VirtualHelixItemController(self, modelVirtualHelix) # end def def partItem(self): """PartItem Accessor - parent Item""" return self._partItem def virtualHelix(self): """VirtualHelix Model Accessor""" return self._modelVirtualHelix def number(self): """VirtualHelix Model Index Number Accessor""" return self._modelVirtualHelix.number() def row(self): """Row Accessor""" return self.coord()[0] def col(self): """Col Accessor""" return self.coord()[1] def x(self): """Actual x position in the the Maya 3D View""" return self._x # end def def y(self): """Actual y position in the the Maya 3D View""" return self._y # end def def coord(self): """Returns a tuple (row, column) of the VitualHelix model""" return self._modelVirtualHelix.coord() # end def def isEvenParity(self): """Returns parity of the VitualHelix model""" return self._modelVirtualHelix.isEvenParity() def StrandIDs(self): """ Returns a list of Strand IDs within this VH, the IDs are suffixes of Maya Nodes, and are used to for Maya<->Cadnano communication in mayaObjectManager """ return self.strandIDs def setModifyState(self, val): """Update Modify state for this VirtualHelixItem""" self._modState = val ### SLOTS ### def strandAddedSlot(self, sender, strand): """ Instantiates a StrandItem upon notification that the model has a new Strand. The StrandItem is responsible for creating its own controller for communication with the model, and for adding itself to its parent (which is *this* VirtualHelixItem, i.e. 'self'). """ #print "solidview.VirtualHelixItem.strandAddedSlot" m = Mom() mID = m.strandMayaID(strand) self.strandIDs.append(mID) sI = StrandItem(mID, strand, self) self._strandItems[sI] = True self.updateDecorators() #print "solidview.VirtualHelixItem.strandAddedSlot done %s" % mID # end def @pyqtSlot(object) def decoratorAddedSlot(self, decorator): """decoratorAddedSlot - empty""" pass @pyqtSlot(object) def virtualHelixNumberChangedSlot(self, virtualHelix): """virtualHelixNumberChangedSlot - empty""" pass # end def @pyqtSlot(object) def virtualHelixRemovedSlot(self, virtualHelix): """ Clears out private variables and disconnects signals """ #print "solidview.VirtualHelixItem.virtualHelixRemovedSlot" self._partItem.removeVirtualHelixItem(self) self._partItem = None self._modelVirtualHelix = None self._controller.disconnectSignals() self._controller = None # end def def updateDecorators(self): """ Clears out Pre-Decorators, and re-populates them if they should be visible """ self.clearDecorators() if self._modState: m = Mom() for mID in self.strandIDs: mayaNodeInfo = "%s%s" % (m.helixMeshName, mID) #print "mayaNodeInfo: %s" % mayaNodeInfo strand = m.mayaToCn[mayaNodeInfo] if(strand.strandSet().isStaple()): self.createDecorators(strand) def cadnanoVBaseToMayaCoords(self, base, strand): """ Given a Strand and a Base, returns a 3D location of that base """ m = Mom() mID = m.strandMayaID(strand) cylinderName = "%s%s" % (m.helixNodeName, mID) if cmds.objExists(cylinderName): rise = cmds.getAttr("%s.rise" % cylinderName) startBase = cmds.getAttr("%s.startBase" % cylinderName) startPos = cmds.getAttr("%s.startPos" % cylinderName) base0Pos = startPos[0][1] + (startBase * rise) ourPos = base0Pos - (base * rise) zComp = ourPos rotation = cmds.getAttr("%s.rotation" % cylinderName) radius = cmds.getAttr("%s.radius" % cylinderName) parity = cmds.getAttr("%s.parity" % cylinderName) strandType = cmds.getAttr("%s.strandType" % cylinderName) rotationOffset = cmds.getAttr("%s.rotationOffset" % cylinderName) decoratorRotOffset = cmds.getAttr("%s.decoratorRotOffset" % cylinderName) # not clear why decoratorRotOffset is not in radians but # rotationOffset is decoratorRotOffset = decoratorRotOffset * math.pi / 180 starting_rotation = (math.pi * (not parity)) + rotationOffset + \ decoratorRotOffset + \ (math.pi * strandType) fullrotation = -rotation * base * math.pi / 180 #print full rotation xComp = self._x + radius * \ math.cos(starting_rotation + fullrotation) yComp = self._y + radius * \ math.sin(starting_rotation + fullrotation) #print "%f %f %f" % (xComp, yComp, zComp) return (xComp, yComp, zComp) else: raise IndexError def clearDecorators(self): """Remove all the Pre-Decortators""" m = Mom() for mID in self.stapleModIndicatorIDs: transformName = "%s%s" % (m.decoratorTransformName, mID) #print "delete %s" % transformName m = Mom() m.removeDecoratorMapping(mID) if cmds.objExists(transformName): cmds.delete(transformName) self.stapleModIndicatorIDs = [] self.stapleIndicatorCount = 0 def createDecorators(self, strand): """Create a set of new Pre-Decortators for a given strand""" m = Mom() strandId = m.strandMayaID(strand) totalNumBases = self._modelVirtualHelix.part().maxBaseIdx() preDecoratorIdxList = strand.getPreDecoratorIdxList() for baseIdx in preDecoratorIdxList: # XXX [SB+AT] NOT THREAD SAFE while cmds.objExists("%s%s_%s" % (m.decoratorNodeName, strandId, self.stapleIndicatorCount)): self.stapleIndicatorCount += 1 stapleId = "%s_%s" % (strandId, self.stapleIndicatorCount) coords = self.cadnanoVBaseToMayaCoords(baseIdx, strand) stapleModNodeInfo = self.createDecoratorNodes(coords, stapleId) self.stapleModIndicatorIDs.append(stapleId) m = Mom() m.decoratorToVirtualHelixItem[stapleModNodeInfo[2]] = (self, baseIdx, strand) m.decoratorToVirtualHelixItem[stapleModNodeInfo[1]] = (self, baseIdx, strand) def createDecoratorNodes(self, coords, mID): """Create a actual Maya Nodes for a new Pre-Decortators""" m = Mom() stapleModIndicatorName = "%s%s" % (m.decoratorNodeName, mID) transformName = "%s%s" % (m.decoratorTransformName, mID) meshName = "%s%s" % (m.decoratorMeshName, mID) shaderName = "%s" % m.decoratorShaderName cmds.createNode("transform", name=transformName, skipSelect=True) cmds.setAttr("%s.rotateX" % transformName, 90) cmds.setAttr("%s.translateX" % transformName, coords[0]) cmds.setAttr("%s.translateY" % transformName, coords[1]) cmds.setAttr("%s.translateZ" % transformName, coords[2]) cmds.createNode("mesh", name=meshName, parent=transformName, skipSelect=True) #cmds.createNode("spPreDecoratorNode", name=stapleModIndicatorName) cmds.createNode("polySphere", name=stapleModIndicatorName, skipSelect=True) cmds.setAttr("%s.radius" % stapleModIndicatorName, .25) cmds.setAttr("%s.subdivisionsAxis" % stapleModIndicatorName, 4) cmds.setAttr("%s.subdivisionsHeight" % stapleModIndicatorName, 4) #cmds.connectAttr("%s.outputMesh" % stapleModIndicatorName, # "%s.inMesh" % meshName) cmds.connectAttr("%s.output" % stapleModIndicatorName, "%s.inMesh" % meshName) if not cmds.objExists(shaderName): # Shader does not exist create one cmds.shadingNode('lambert', asShader=True, name=shaderName) cmds.sets(n="%sSG" % shaderName, r=True, nss=True, em=True) cmds.connectAttr("%s.outColor" % shaderName, "%sSG.surfaceShader" % shaderName) cmds.setAttr("%s.color" % shaderName, 0.0, 0.0, 0.0, type="double3") cmds.sets(meshName, forceElement="%sSG" % shaderName) else: #shader exist connect cmds.sets(meshName, forceElement="%sSG" % shaderName) return (stapleModIndicatorName, transformName, meshName, shaderName) # end def def removeStrandItem(self, strandItem): """Remove a StrandItem from the local list of StrandItems""" del self._strandItems[strandItem]