Example #1
0
    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()
Example #2
0
    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)
    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()
Example #4
0
    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)
    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)
Example #6
0
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.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)
Example #8
0
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]
Example #10
0
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)
Example #12
0
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]