Example #1
0
    def __init__(self, modelPart, parent=None):
        """
        Parent should be either a SliceRootItem, or an AssemblyItem.

        Invariant: keys in _emptyhelixhash = range(_nrows) x range(_ncols)
        where x is the cartesian product.

        Order matters for deselector, probe, and setlattice
        """
        super(PartItem, self).__init__(parent)
        self._part = modelPart
        self._controller = PartItemController(self, modelPart)
        self._activeSliceItem = ActiveSliceItem(self, modelPart.activeBaseIndex())
        self._scaleFactor = self._radius/modelPart.radius()
        self._emptyhelixhash = {}
        self._virtualHelixHash = {}
        self._nrows, self._ncols = 0, 0
        self._rect = QRectF(0, 0, 0, 0)
        self._initDeselector()
        # Cache of VHs that were active as of last call to activeSliceChanged
        # If None, all slices will be redrawn and the cache will be filled.
        # Connect destructor. This is for removing a part from scenes.
        self.probe = self.IntersectionProbe(self)
        # initialize the PartItem with an empty set of old coords
        self._setLattice([], modelPart.generatorFullLattice())
        self.setFlag(QGraphicsItem.ItemHasNoContents)  # never call paint
        self.setZValue(styles.ZPARTITEM)
        self._initModifierCircle()
Example #2
0
    def __init__(self, modelPart, parent=None):
        """
        Parent should be either a SliceRootItem, or an AssemblyItem.

        Invariant: keys in _emptyhelixhash = range(_nrows) x range(_ncols)
        where x is the cartesian product.
        
        Order matters for deselector, probe, and setlattice
        """
        super(PartItem, self).__init__(parent)
        self._part = modelPart
        self._controller = PartItemController(self, modelPart)
        self._activeSliceItem = ActiveSliceItem(self, modelPart.activeBaseIndex())
        self._scaleFactor = self._radius/modelPart.radius()
        self._emptyhelixhash = {}
        self._virtualHelixHash = {}
        self._nrows, self._ncols = 0, 0
        self._rect = QRectF(0, 0, 0, 0)
        self._initDeselector()
        # Cache of VHs that were active as of last call to activeSliceChanged
        # If None, all slices will be redrawn and the cache will be filled.
        # Connect destructor. This is for removing a part from scenes.
        self.probe = self.IntersectionProbe(self)
        # initialize the PartItem with an empty set of old coords
        self._setLattice([], modelPart.generatorFullLattice())
        self.setFlag(QGraphicsItem.ItemHasNoContents)  # never call paint
        self.setZValue(styles.ZPARTITEM)
        self._initModifierCircle()
Example #3
0
 def __init__(self, modelPart, viewroot, activeTool, parent):
     """parent should always be pathrootitem"""
     super(PartItem, self).__init__(parent)
     self._modelPart = mP = modelPart
     self._viewroot = viewroot
     self._activeTool = activeTool
     self._activeSliceItem = ActiveSliceItem(self, mP.activeBaseIndex())
     self._activeVirtualHelixItem = None
     self._controller = PartItemController(self, mP)
     self._preXoverItems = []  # crossover-related
     self._virtualHelixHash = {}
     self._virtualHelixItemList = []
     self._vHRect = QRectF()
     self.setAcceptHoverEvents(True)
     self._initModifierRect()
     self._initResizeButtons()
     self._proxyParent = ProxyParentItem(self)
     self._proxyParent.setFlag(QGraphicsItem.ItemHasNoContents)
Example #4
0
    def __init__(self, modelPart, parent=None):
        """
        Loads in all the Maya plugins (Nodes) needed for visualizing
        the model (XXX [SB] -this code should probably go somewhere else).
        Initiates some private variables that are constant across all
        strands (XXX [SB] -this code should also probably go somewhere else).
        Sets up PartItemController that is used to setup all the
        slots and signals between strand model and this PartItem.
        """
        self._parentItem = parent
        pluginPath = os.path.join(os.environ['CADNANO_PATH'], "views",
                                  "solidview")
        hchPath = os.path.join(pluginPath, "halfcylinderhelixnode.py")
        smiPath = os.path.join(pluginPath, "predecoratornode.py")

        if (not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            cmds.loadPlugin(hchPath)

        if (not cmds.pluginInfo(smiPath, query=True, loaded=True)):
            cmds.loadPlugin(smiPath)

        if (not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            print("HalfCylinderHelixNode failed to load")
            return

        #print "maya PartItem created"
        self._type = modelPart.crossSectionType()

        #self.mayaScale = 1.0
        #later updates using basecount from the VH
        # XXX [SB] - need to ask CandNano for rows and cols...
        # top left cornder of maya 3d scene X Y Z
        self.mayaOrigin = (-15 * 2.25, 16 * 2.25, 0.0)
        self.helixRadius = 1.125  # diamiter is 2.25nm
        self._virtualHelixItems = {}

        self._part = modelPart
        self._controller = PartItemController(self, modelPart)

        self.modifyState = False
Example #5
0
 def __init__(self, modelPart, viewroot, activeTool, parent):
     """parent should always be pathrootitem"""
     super(PartItem, self).__init__(parent)
     self._modelPart = mP = modelPart
     self._viewroot = viewroot
     self._activeTool = activeTool
     self._activeSliceItem = ActiveSliceItem(self, mP.activeBaseIndex())
     self._activeVirtualHelixItem = None
     self._controller = PartItemController(self, mP)
     self._preXoverItems = []  # crossover-related
     self._virtualHelixHash = {}
     self._virtualHelixItemList = []
     self._vHRect = QRectF()
     self.setAcceptHoverEvents(True)
     self._initModifierRect()
     self._initResizeButtons()
     self._proxyParent = ProxyParentItem(self)
     self._proxyParent.setFlag(QGraphicsItem.ItemHasNoContents)
Example #6
0
    def __init__(self, modelPart, parent=None):
        """
        Loads in all the Maya plugins (Nodes) needed for visualizing
        the model (XXX [SB] -this code should probably go somewhere else).
        Initiates some private variables that are constant across all
        strands (XXX [SB] -this code should also probably go somewhere else).
        Sets up PartItemController that is used to setup all the
        slots and signals between strand model and this PartItem.
        """
        self._parentItem = parent
        pluginPath = os.path.join(os.environ['CADNANO_PATH'],
                                  "views",
                                  "solidview")
        hchPath = os.path.join(pluginPath, "halfcylinderhelixnode.py")
        smiPath = os.path.join(pluginPath, "predecoratornode.py")

        if(not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            cmds.loadPlugin(hchPath)

        if(not cmds.pluginInfo(smiPath, query=True, loaded=True)):
            cmds.loadPlugin(smiPath)

        if(not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            print "HalfCylinderHelixNode failed to load"
            return

        #print "maya PartItem created"
        self._type = modelPart.crossSectionType()

        #self.mayaScale = 1.0
        #later updates using basecount from the VH
        # XXX [SB] - need to ask CandNano for rows and cols...
        # top left cornder of maya 3d scene X Y Z
        self.mayaOrigin = (-15 * 2.25, 16 * 2.25, 0.0)
        self.helixRadius = 1.125  # diamiter is 2.25nm
        self._virtualHelixItems = {}

        self._part = modelPart
        self._controller = PartItemController(self, modelPart)

        self.modifyState = False
Example #7
0
class PartItem(QGraphicsItem):
    _radius = styles.SLICE_HELIX_RADIUS

    def __init__(self, modelPart, parent=None):
        """
        Parent should be either a SliceRootItem, or an AssemblyItem.

        Invariant: keys in _emptyhelixhash = range(_nrows) x range(_ncols)
        where x is the cartesian product.

        Order matters for deselector, probe, and setlattice
        """
        super(PartItem, self).__init__(parent)
        self._part = modelPart
        self._controller = PartItemController(self, modelPart)
        self._activeSliceItem = ActiveSliceItem(self, modelPart.activeBaseIndex())
        self._scaleFactor = self._radius/modelPart.radius()
        self._emptyhelixhash = {}
        self._virtualHelixHash = {}
        self._nrows, self._ncols = 0, 0
        self._rect = QRectF(0, 0, 0, 0)
        self._initDeselector()
        # Cache of VHs that were active as of last call to activeSliceChanged
        # If None, all slices will be redrawn and the cache will be filled.
        # Connect destructor. This is for removing a part from scenes.
        self.probe = self.IntersectionProbe(self)
        # initialize the PartItem with an empty set of old coords
        self._setLattice([], modelPart.generatorFullLattice())
        self.setFlag(QGraphicsItem.ItemHasNoContents)  # never call paint
        self.setZValue(styles.ZPARTITEM)
        self._initModifierCircle()
    # end def

    def _initDeselector(self):
        """
        The deselector grabs mouse events that missed a slice and clears the
        selection when it gets one.
        """
        self.deselector = ds = PartItem.Deselector(self)
        ds.setParentItem(self)
        ds.setFlag(QGraphicsItem.ItemStacksBehindParent)
        ds.setZValue(styles.ZDESELECTOR)

    def _initModifierCircle(self):
        self._canShowModCirc = False
        self._modCirc = mC = QGraphicsEllipseItem(_hoverRect, self)
        mC.setPen(_modPen)
        mC.hide()
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def partActiveVirtualHelixChangedSlot(self, part, virtualHelix):
        pass

    def partDimensionsChangedSlot(self, sender):
        pass
    # end def

    def partHideSlot(self, sender):
        self.hide()
    # end def

    def partParentChangedSlot(self, sender):
        """docstring for partParentChangedSlot"""
        # print "PartItem.partParentChangedSlot"
        pass

    def partRemovedSlot(self, sender):
        """docstring for partRemovedSlot"""
        self._activeSliceItem.removed()
        self.parentItem().removePartItem(self)

        scene = self.scene()

        self._virtualHelixHash = None

        for item in list(self._emptyhelixhash.items()):
            key, val = item
            scene.removeItem(val)
            del self._emptyhelixhash[key]
        self._emptyhelixhash = None

        scene.removeItem(self)

        self._part = None
        self.probe = None
        self._modCirc = None

        self.deselector = None
        self._controller.disconnectSignals()
        self._controller = None
    # end def

    def partVirtualHelicesReorderedSlot(self, sender, orderedCoordList):
        pass
    # end def

    def partPreDecoratorSelectedSlot(self, sender, row, col, baseIdx):
        """docstring for partPreDecoratorSelectedSlot"""
        vhi = self.getVirtualHelixItemByCoord(row, col)
        view = self.window().sliceGraphicsView
        view.sceneRootItem.resetTransform()
        view.centerOn(vhi)
        view.zoomIn()
        mC = self._modCirc
        x,y = self._part.latticeCoordToPositionXY(row, col, self.scaleFactor())
        mC.setPos(x,y)
        if self._canShowModCirc:
            mC.show()
    # end def

    def partVirtualHelixAddedSlot(self, sender, virtualHelix):
        vh = virtualHelix
        coords = vh.coord()

        emptyHelixItem = self._emptyhelixhash[coords]
        # TODO test to see if self._virtualHelixHash is necessary
        vhi = VirtualHelixItem(vh, emptyHelixItem)
        self._virtualHelixHash[coords] = vhi
    # end def

    def partVirtualHelixRenumberedSlot(self, sender, coord):
        pass
    # end def

    def partVirtualHelixResizedSlot(self, sender, coord):
        pass
    # end def

    def updatePreXoverItemsSlot(self, sender, virtualHelix):
        pass
    # end def

    ### ACCESSORS ###
    def boundingRect(self):
        return self._rect
    # end def

    def part(self):
        return self._part
    # end def

    def scaleFactor(self):
        return self._scaleFactor
    # end def

    def setPart(self, newPart):
        self._part = newPart
    # end def

    def window(self):
        return self.parentItem().window()
    # end def

    ### PRIVATE SUPPORT METHODS ###
    def _upperLeftCornerForCoords(self, row, col):
        pass  # subclass
    # end def

    def _updateGeometry(self):
        self._rect = QRectF(0, 0, *self.part().dimensions())
    # end def

    def _spawnEmptyHelixItemAt(self, row, column):
        helix = EmptyHelixItem(row, column, self)
        # helix.setFlag(QGraphicsItem.ItemStacksBehindParent, True)
        self._emptyhelixhash[(row, column)] = helix
    # end def

    def _killHelixItemAt(row, column):
        s = self._emptyhelixhash[(row, column)]
        s.scene().removeItem(s)
        del self._emptyhelixhash[(row, column)]
    # end def

    def _setLattice(self, oldCoords, newCoords):
        """A private method used to change the number of rows,
        cols in response to a change in the dimensions of the
        part represented by the receiver"""
        oldSet = set(oldCoords)
        oldList = list(oldSet)
        newSet = set(newCoords)
        newList = list(newSet)
        for coord in oldList:
            if coord not in newSet:
                self._killHelixItemAt(*coord)
        # end for
        for coord in newList:
            if coord not in oldSet:
                self._spawnEmptyHelixItemAt(*coord)
        # end for
        # self._updateGeometry(newCols, newRows)
        # self.prepareGeometryChange()
        # the Deselector copies our rect so it changes too
        self.deselector.prepareGeometryChange()
        self.zoomToFit()
    # end def

    ### PUBLIC SUPPORT METHODS ###
    def getVirtualHelixItemByCoord(self, row, column):
        if (row, column) in self._emptyhelixhash:
            return self._virtualHelixHash[(row, column)]
        else:
            return None
    # end def

    def paint(self, painter, option, widget=None):
        pass
    # end def

    def selectionWillChange(self, newSel):
        if self.part() == None:
            return
        if self.part().selectAllBehavior():
            return
        for sh in self._emptyhelixhash.values():
            sh.setSelected(sh.virtualHelix() in newSel)
    # end def

    def setModifyState(self, bool):
        """Hides the modRect when modify state disabled."""
        self._canShowModCirc = bool
        if bool == False:
            self._modCirc.hide()

    def updateStatusBar(self, statusString):
        """Shows statusString in the MainWindow's status bar."""
        pass  # disabled for now.
        # self.window().statusBar().showMessage(statusString, timeout)

    def vhAtCoordsChanged(self, row, col):
        self._emptyhelixhash[(row, col)].update()
    # end def

    def zoomToFit(self):
        thescene = self.scene()
        theview = thescene.views()[0]
        theview.zoomToFit()
    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        # self.createOrAddBasesToVirtualHelix()
        QGraphicsItem.mousePressEvent(self, event)
    # end def

    class Deselector(QGraphicsItem):
        """The deselector lives behind all the slices and observes mouse press
        events that miss slices, emptying the selection when they do"""
        def __init__(self, parentHGI):
            super(PartItem.Deselector, self).__init__()
            self.parentHGI = parentHGI

        def mousePressEvent(self, event):
            self.parentHGI.part().setSelection(())
            super(PartItem.Deselector, self).mousePressEvent(event)

        def boundingRect(self):
            return self.parentHGI.boundingRect()

        def paint(self, painter, option, widget=None):
            pass

    class IntersectionProbe(QGraphicsItem):
        def boundingRect(self):
            return QRectF(0, 0, .1, .1)
        def paint(self, painter, option, widget=None):
            pass
Example #8
0
class PartItem(object):
    """
    PartItem stores VirtualHelixItems for a given DNA Part model
    """
    def __init__(self, modelPart, parent=None):
        """
        Loads in all the Maya plugins (Nodes) needed for visualizing
        the model (XXX [SB] -this code should probably go somewhere else).
        Initiates some private variables that are constant across all
        strands (XXX [SB] -this code should also probably go somewhere else).
        Sets up PartItemController that is used to setup all the
        slots and signals between strand model and this PartItem.
        """
        self._parentItem = parent
        pluginPath = os.path.join(os.environ['CADNANO_PATH'], "views",
                                  "solidview")
        hchPath = os.path.join(pluginPath, "halfcylinderhelixnode.py")
        smiPath = os.path.join(pluginPath, "predecoratornode.py")

        if (not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            cmds.loadPlugin(hchPath)

        if (not cmds.pluginInfo(smiPath, query=True, loaded=True)):
            cmds.loadPlugin(smiPath)

        if (not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            print("HalfCylinderHelixNode failed to load")
            return

        #print "maya PartItem created"
        self._type = modelPart.crossSectionType()

        #self.mayaScale = 1.0
        #later updates using basecount from the VH
        # XXX [SB] - need to ask CandNano for rows and cols...
        # top left cornder of maya 3d scene X Y Z
        self.mayaOrigin = (-15 * 2.25, 16 * 2.25, 0.0)
        self.helixRadius = 1.125  # diamiter is 2.25nm
        self._virtualHelixItems = {}

        self._part = modelPart
        self._controller = PartItemController(self, modelPart)

        self.modifyState = False

    # end def

    def parentItem(self):
        """Return parent item, which is SolidRootItem in this case"""
        return self._parentItem

    # end def

    def setModifyState(self, val):
        """Change Modify state for all the strands in this PartItem"""
        self.modifyState = val
        self.updateModifyState()

    # end def

    def updateModifyState(self):
        """Update Modify state for all the strands in this PartItem"""
        for sh in self._virtualHelixItems:
            sh.setModifyState(self.modifyState)
            sh.updateDecorators()

    # end def

    def isInModifyState(self):
        """Accessor for Modify State"""
        return self.modifyState

    # end def

    def type(self):
        """Accessor for Cross Section Type, (Honeycomb vs. Square)"""
        return self._type

    # end def

    def part(self):
        """Accessor for the part model"""
        return self._part

    # end def

    def setPart(self, p):
        """set part model"""
        self._part = p

    # end def

    ### SLOTS ###
    def partDimensionsChangedSlot(self, part):
        """
        Receives notification from the model when a dimentions of the part
        changes. Needs to change an attribute of every Maya Helix Nodes, so
        that they are all are aligned corectly with new strands that are
        created.
        """
        mom = Mom()
        for vh in self._virtualHelixItems:
            for mID in vh.StrandIDs():
                cylinderName = "%s%s" % (mom.helixNodeName, mID)
                totalNumBases = self._part.maxBaseIdx()
                cmds.setAttr("%s.totalBases" % cylinderName,
                             int(totalNumBases))
            vh.updateDecorators()

    # end def

    def partParentChangedSlot(self, sender, part):
        """partParentChangedSlot - empty"""
        pass

    # end def

    def partRemovedSlot(self, sender, part):
        """clears out private variables and disconnects signals"""
        # print "solidview.PartItem.partRemovedSlot"
        self._virtualHelixItems = None
        self._parentItem.removePartItem(self)
        self._parentItem = None
        self._part = None
        self._controller.disconnectSignals()
        self._controller = None

    # end def

    def partPreDecoratorSelectedSlot(self, sender, row, col, baseIdx):
        """partPreDecoratorSelectedSlot - empty"""
        pass

    # end def

    def partVirtualHelixAddedSlot(self, sender, virtualHelix):
        """Receives notification when new VitualHelix is added"""
        sh = self.createNewVirtualHelixItem(virtualHelix)
        sh.setModifyState(self.modifyState)

    # end def

    @pyqtSlot(tuple)
    def partVirtualHelixRenumberedSlot(self, sender, coord):
        """partVirtualHelixRenumberedSlot - empty"""
        pass

    # end def

    @pyqtSlot(tuple)
    def partVirtualHelixResizedSlot(self, sender, coord):
        """partVirtualHelixResizedSlot - empty"""
        pass

    # end def

    @pyqtSlot(list)
    def partVirtualHelicesReorderedSlot(self, sender, orderedCoordList):
        """partVirtualHelicesReorderedSlot - empty"""
        pass

    # end def

    def updatePreXoverItemsSlot(self, sender, virtualHelix):
        """updatePreXoverItemsSlot - empty"""
        pass

    # end def

    ### METHODS ###
    def cadnanoToMayaCoords(self, row, col):
        """Converts cadnano row and col to Maya coordinates"""
        x, y = self.part().latticeCoordToPositionXY(row, col)
        return x + self.mayaOrigin[0], self.mayaOrigin[1] - y

    # end def

    def createNewVirtualHelixItem(self, virtualHelix):
        """Create a new Virtual Helix """
        coords = virtualHelix.coord()
        #print "solidview.PartItem.createNewVirtualHelixItem: %d %d" % \
        #                                                (coords[0], coords[1])
        # figure out Maya Coordinates
        x, y = self.cadnanoToMayaCoords(coords[0], coords[1])
        #print virtualHelix
        newHelix = VirtualHelixItem(self, virtualHelix, x, y)
        self._virtualHelixItems[newHelix] = True
        return newHelix

    # end def

    def removeVirtualHelixItem(self, vhelixItem):
        """Remove a new Virtual Helix """
        del self._virtualHelixItems[vhelixItem]
Example #9
0
class PartItem(object):
    """
    PartItem stores VirtualHelixItems for a given DNA Part model
    """
    def __init__(self, modelPart, parent=None):
        """
        Loads in all the Maya plugins (Nodes) needed for visualizing
        the model (XXX [SB] -this code should probably go somewhere else).
        Initiates some private variables that are constant across all
        strands (XXX [SB] -this code should also probably go somewhere else).
        Sets up PartItemController that is used to setup all the
        slots and signals between strand model and this PartItem.
        """
        self._parentItem = parent
        pluginPath = os.path.join(os.environ['CADNANO_PATH'],
                                  "views",
                                  "solidview")
        hchPath = os.path.join(pluginPath, "halfcylinderhelixnode.py")
        smiPath = os.path.join(pluginPath, "predecoratornode.py")

        if(not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            cmds.loadPlugin(hchPath)

        if(not cmds.pluginInfo(smiPath, query=True, loaded=True)):
            cmds.loadPlugin(smiPath)

        if(not cmds.pluginInfo(hchPath, query=True, loaded=True)):
            print "HalfCylinderHelixNode failed to load"
            return

        #print "maya PartItem created"
        self._type = modelPart.crossSectionType()

        #self.mayaScale = 1.0
        #later updates using basecount from the VH
        # XXX [SB] - need to ask CandNano for rows and cols...
        # top left cornder of maya 3d scene X Y Z
        self.mayaOrigin = (-15 * 2.25, 16 * 2.25, 0.0)
        self.helixRadius = 1.125  # diamiter is 2.25nm
        self._virtualHelixItems = {}

        self._part = modelPart
        self._controller = PartItemController(self, modelPart)

        self.modifyState = False
    # end def

    def parentItem(self):
        """Return parent item, which is SolidRootItem in this case"""
        return self._parentItem
    # end def

    def setModifyState(self, val):
        """Change Modify state for all the strands in this PartItem"""
        self.modifyState = val
        self.updateModifyState()
    # end def

    def updateModifyState(self):
        """Update Modify state for all the strands in this PartItem"""
        for sh in self._virtualHelixItems:
            sh.setModifyState(self.modifyState)
            sh.updateDecorators()
    # end def

    def isInModifyState(self):
        """Accessor for Modify State"""
        return self.modifyState
    # end def

    def type(self):
        """Accessor for Cross Section Type, (Honeycomb vs. Square)"""
        return self._type
    # end def

    def part(self):
        """Accessor for the part model"""
        return self._part
    # end def

    def setPart(self, p):
        """set part model"""
        self._part = p
    # end def

    ### SLOTS ###
    def partDimensionsChangedSlot(self, part):
        """
        Receives notification from the model when a dimentions of the part
        changes. Needs to change an attribute of every Maya Helix Nodes, so
        that they are all are aligned corectly with new strands that are
        created.
        """
        mom = Mom()
        for vh in self._virtualHelixItems:
            for mID in vh.StrandIDs():
                cylinderName = "%s%s" % (mom.helixNodeName, mID)
                totalNumBases = self._part.maxBaseIdx()
                cmds.setAttr("%s.totalBases" % cylinderName,
                                                int(totalNumBases))
            vh.updateDecorators()
    # end def

    def partParentChangedSlot(self, sender, part):
        """partParentChangedSlot - empty"""
        pass
    # end def

    def partRemovedSlot(self, sender, part):
        """clears out private variables and disconnects signals"""
        # print "solidview.PartItem.partRemovedSlot"
        self._virtualHelixItems = None
        self._parentItem.removePartItem(self)
        self._parentItem = None
        self._part = None
        self._controller.disconnectSignals()
        self._controller = None
    # end def

    def partPreDecoratorSelectedSlot(self, sender, row, col, baseIdx):
        """partPreDecoratorSelectedSlot - empty"""
        pass
    # end def

    def partVirtualHelixAddedSlot(self, sender, virtualHelix):
        """Receives notification when new VitualHelix is added"""
        sh = self.createNewVirtualHelixItem(virtualHelix)
        sh.setModifyState(self.modifyState)
    # end def

    @pyqtSlot(tuple)
    def partVirtualHelixRenumberedSlot(self, sender, coord):
        """partVirtualHelixRenumberedSlot - empty"""
        pass
    # end def

    @pyqtSlot(tuple)
    def partVirtualHelixResizedSlot(self, sender, coord):
        """partVirtualHelixResizedSlot - empty"""
        pass
    # end def

    @pyqtSlot(list)
    def partVirtualHelicesReorderedSlot(self, sender, orderedCoordList):
        """partVirtualHelicesReorderedSlot - empty"""
        pass
    # end def

    def updatePreXoverItemsSlot(self, sender, virtualHelix):
        """updatePreXoverItemsSlot - empty"""
        pass
    # end def

    ### METHODS ###
    def cadnanoToMayaCoords(self, row, col):
        """Converts cadnano row and col to Maya coordinates"""
        x, y = self.part().latticeCoordToPositionXY(row, col)
        return x + self.mayaOrigin[0], self.mayaOrigin[1] - y
    # end def

    def createNewVirtualHelixItem(self, virtualHelix):
        """Create a new Virtual Helix """
        coords = virtualHelix.coord()
        #print "solidview.PartItem.createNewVirtualHelixItem: %d %d" % \
        #                                                (coords[0], coords[1])
        # figure out Maya Coordinates
        x, y = self.cadnanoToMayaCoords(coords[0], coords[1])
        #print virtualHelix
        newHelix = VirtualHelixItem(self, virtualHelix, x, y)
        self._virtualHelixItems[newHelix] = True
        return newHelix
    # end def

    def removeVirtualHelixItem(self, vhelixItem):
        """Remove a new Virtual Helix """
        del self._virtualHelixItems[vhelixItem]
Example #10
0
class PartItem(QGraphicsRectItem):
    findChild = util.findChild  # for debug

    def __init__(self, modelPart, viewroot, activeTool, parent):
        """parent should always be pathrootitem"""
        super(PartItem, self).__init__(parent)
        self._modelPart = mP = modelPart
        self._viewroot = viewroot
        self._activeTool = activeTool
        self._activeSliceItem = ActiveSliceItem(self, mP.activeBaseIndex())
        self._activeVirtualHelixItem = None
        self._controller = PartItemController(self, mP)
        self._preXoverItems = []  # crossover-related
        self._virtualHelixHash = {}
        self._virtualHelixItemList = []
        self._vHRect = QRectF()
        self.setAcceptHoverEvents(True)
        self._initModifierRect()
        self._initResizeButtons()
        self._proxyParent = ProxyParentItem(self)
        self._proxyParent.setFlag(QGraphicsItem.ItemHasNoContents)

    # end def

    def proxy(self):
        return self._proxyParent

    # end def

    def _initModifierRect(self):
        """docstring for _initModifierRect"""
        self._canShowModRect = False
        self._modRect = mR = QGraphicsRectItem(_defaultRect, self)
        mR.setPen(_modPen)
        mR.hide()

    # end def

    def _initResizeButtons(self):
        """Instantiate the buttons used to change the canvas size."""
        self._addBasesButton = SVGButton(":/pathtools/add-bases", self)
        self._addBasesButton.clicked.connect(self._addBasesClicked)
        self._addBasesButton.hide()
        self._removeBasesButton = SVGButton(":/pathtools/remove-bases", self)
        self._removeBasesButton.clicked.connect(self._removeBasesClicked)
        self._removeBasesButton.hide()

    # end def

    def vhItemForVH(self, vhref):
        """Returns the pathview VirtualHelixItem corresponding to vhref"""
        vh = self._modelPart.virtualHelix(vhref)
        return self._virtualHelixHash.get(vh.coord())

    ### SIGNALS ###

    ### SLOTS ###
    def partParentChangedSlot(self, sender):
        """docstring for partParentChangedSlot"""
        # print "PartItem.partParentChangedSlot"
        pass

    # end def

    def partHideSlot(self, sender):
        self.hide()

    # end def

    def partActiveVirtualHelixChangedSlot(self, part, virtualHelix):
        self.updatePreXoverItems()

    #end def

    def partDimensionsChangedSlot(self, part):
        if len(self._virtualHelixItemList) > 0:
            vhi = self._virtualHelixItemList[0]
            vhiRect = vhi.boundingRect()
            vhiHRect = vhi.handle().boundingRect()
            self._vHRect.setLeft(vhiHRect.left())
            self._vHRect.setRight(vhiRect.right())
        self.scene().views()[0].zoomToFit()
        self._activeSliceItem.resetBounds()
        self._updateBoundingRect()

    # end def

    def partRemovedSlot(self, sender):
        """docstring for partRemovedSlot"""
        self._activeSliceItem.removed()
        self.parentItem().removePartItem(self)
        scene = self.scene()
        scene.removeItem(self)
        self._modelPart = None
        self._virtualHelixHash = None
        self._virtualHelixItemList = None
        self._controller.disconnectSignals()
        self._controller = None

    # end def

    def partPreDecoratorSelectedSlot(self, sender, row, col, baseIdx):
        """docstring for partPreDecoratorSelectedSlot"""
        part = self._modelPart
        vh = part.virtualHelixAtCoord((row, col))
        vhi = self.itemForVirtualHelix(vh)
        yOffset = _bw if vh.isEvenParity() else 0
        p = QPointF(baseIdx * _bw, vhi.y() + yOffset)
        view = self.window().pathGraphicsView
        view.sceneRootItem.resetTransform()
        view.centerOn(p)
        view.zoomIn()
        self._modRect.setPos(p)
        if self._canShowModRect:
            self._modRect.show()

    # end def

    def partVirtualHelixAddedSlot(self, sender, modelVirtualHelix):
        """
        When a virtual helix is added to the model, this slot handles
        the instantiation of a virtualhelix item.
        """
        # print "PartItem.partVirtualHelixAddedSlot"
        vh = modelVirtualHelix
        vhi = VirtualHelixItem(self, modelVirtualHelix, self._viewroot,
                               self._activeTool)
        self._virtualHelixHash[vh.coord()] = vhi
        self._virtualHelixItemList.append(vhi)
        self._setVirtualHelixItemList(self._virtualHelixItemList)
        self._updateBoundingRect()

    # end def

    def partVirtualHelixRenumberedSlot(self, sender, coord):
        """Notifies the virtualhelix at coord to change its number"""
        vh = self._virtualHelixHash[coord]
        # check for new number
        # notify VirtualHelixHandleItem to update its label
        # notify VirtualHelix to update its xovers
        # if the VirtualHelix is active, refresh prexovers
        pass

    # end def

    def partVirtualHelixResizedSlot(self, sender, coord):
        """Notifies the virtualhelix at coord to resize."""
        vh = self._virtualHelixHash[coord]
        vh.resize()

    # end def

    def partVirtualHelicesReorderedSlot(self, sender, orderedCoordList):
        """docstring for partVirtualHelicesReorderedSlot"""
        newList = self._virtualHelixItemList
        decorated = [(orderedCoordList.index(vhi.coord()), vhi)\
                        for vhi in self._virtualHelixItemList]
        decorated.sort()
        newList = [vhi for idx, vhi in decorated]
        self._setVirtualHelixItemList(newList)

    # end def

    def updatePreXoverItemsSlot(self, sender, virtualHelix):
        part = self.part()
        if virtualHelix == None:
            self.setPreXoverItemsVisible(None)
        elif part.areSameOrNeighbors(part.activeVirtualHelix(), virtualHelix):
            vhi = self.itemForVirtualHelix(virtualHelix)
            self.setActiveVirtualHelixItem(vhi)
            self.setPreXoverItemsVisible(self.activeVirtualHelixItem())

    # end def

    ### ACCESSORS ###
    def activeTool(self):
        return self._activeTool

    # end def

    def activeVirtualHelixItem(self):
        return self._activeVirtualHelixItem

    # end def

    def part(self):
        """Return a reference to the model's part object"""
        return self._modelPart

    # end def

    def document(self):
        """Return a reference to the model's document object"""
        return self._modelPart.document()

    # end def

    def removeVirtualHelixItem(self, virtualHelixItem):
        vh = virtualHelixItem.virtualHelix()
        self._virtualHelixItemList.remove(virtualHelixItem)
        del self._virtualHelixHash[vh.coord()]
        self._setVirtualHelixItemList(self._virtualHelixItemList)
        self._updateBoundingRect()

    # end def

    def itemForVirtualHelix(self, virtualHelix):
        return self._virtualHelixHash[virtualHelix.coord()]

    # end def

    def virtualHelixBoundingRect(self):
        return self._vHRect

    # end def

    def window(self):
        return self.parentItem().window()

    # end def

    ### PRIVATE METHODS ###
    def _addBasesClicked(self):
        part = self._modelPart
        step = part.stepSize()
        self._addBasesDialog = dlg = QInputDialog(self.window())
        dlg.setInputMode(QInputDialog.IntInput)
        dlg.setIntMinimum(0)
        dlg.setIntValue(step)
        dlg.setIntMaximum(100000)
        dlg.setIntStep(step)
        dlg.setLabelText(( "Number of bases to add to the existing"\
                         + " %i bases\n(must be a multiple of %i)")\
                         % (part.maxBaseIdx(), step))
        dlg.intValueSelected.connect(self._addBasesCallback)
        dlg.open()

    # end def

    def _addBasesCallback(self, n):
        """
        Given a user-chosen number of bases to add, snap it to an index
        where index modulo stepsize is 0 and calls resizeVirtualHelices to
        adjust to that size.
        """
        part = self._modelPart
        self._addBasesDialog.intValueSelected.disconnect(
            self._addBasesCallback)
        del self._addBasesDialog
        maxDelta = int(n / part.stepSize()) * part.stepSize()
        part.resizeVirtualHelices(0, maxDelta)
        if app().isInMaya():
            import maya.cmds as cmds
            cmds.select(clear=True)

    # end def

    def _removeBasesClicked(self):
        """
        Determines the minimum maxBase index where index modulo stepsize == 0
        and is to the right of the rightmost nonempty base, and then resize
        each calls the resizeVirtualHelices to adjust to that size.
        """
        part = self._modelPart
        stepSize = part.stepSize()
        # first find out the right edge of the part
        idx = part.indexOfRightmostNonemptyBase()
        # next snap to a multiple of stepsize
        idx = int(ceil(float(idx + 1) / stepSize)) * stepSize
        # finally, make sure we're a minimum of stepSize bases
        idx = util.clamp(idx, part.stepSize(), 10000)
        delta = idx - (part.maxBaseIdx() + 1)
        if delta < 0:
            part.resizeVirtualHelices(0, delta)
            if app().isInMaya():
                import maya.cmds as cmds
                cmds.select(clear=True)

    # end def

    def _setVirtualHelixItemList(self, newList, zoomToFit=True):
        """
        Give me a list of VirtualHelixItems and I'll parent them to myself if
        necessary, position them in a column, adopt their handles, and
        position them as well.
        """
        y = 0  # How far down from the top the next PH should be
        leftmostExtent = 0
        rightmostExtent = 0

        scene = self.scene()
        vhiRect = None
        vhiHRect = None

        for vhi in newList:
            vhi.setPos(0, y)
            if not vhiRect:
                vhiRect = vhi.boundingRect()
                step = vhiRect.height() + styles.PATH_HELIX_PADDING
            # end if

            # get the VirtualHelixHandleItem
            vhiH = vhi.handle()
            if vhiH.parentItem() != self._viewroot._vhiHSelectionGroup:
                vhiH.setParentItem(self)

            if not vhiHRect:
                vhiHRect = vhiH.boundingRect()

            vhiH.setPos(-2 * vhiHRect.width(),
                        y + (vhiRect.height() - vhiHRect.height()) / 2)

            leftmostExtent = min(leftmostExtent, -2 * vhiHRect.width())
            rightmostExtent = max(rightmostExtent, vhiRect.width())
            y += step
            self.updateXoverItems(vhi)
        # end for
        self._vHRect = QRectF(leftmostExtent, -40,
                              -leftmostExtent + rightmostExtent, y + 40)
        self._virtualHelixItemList = newList
        if zoomToFit:
            self.scene().views()[0].zoomToFit()

    # end def

    def _updateBoundingRect(self):
        """
        Updates the bounding rect to the size of the childrenBoundingRect,
        and refreshes the addBases and removeBases buttons accordingly.

        Called by partVirtualHelixAddedSlot, partDimensionsChangedSlot, or
        removeVirtualHelixItem.
        """
        self.setPen(QPen(Qt.NoPen))
        self.setRect(self.childrenBoundingRect())
        # move and show or hide the buttons if necessary
        addButton = self._addBasesButton
        rmButton = self._removeBasesButton
        if len(self._virtualHelixItemList) > 0:
            addRect = addButton.boundingRect()
            rmRect = rmButton.boundingRect()
            x = self._vHRect.right()
            y = -styles.PATH_HELIX_PADDING
            addButton.setPos(x, y)
            rmButton.setPos(x - rmRect.width(), y)
            addButton.show()
            rmButton.show()
        else:
            addButton.hide()
            rmButton.hide()

    # end def

    ### PUBLIC METHODS ###
    def setModifyState(self, bool):
        """Hides the modRect when modify state disabled."""
        self._canShowModRect = bool
        if bool == False:
            self._modRect.hide()

    def getOrderedVirtualHelixList(self):
        """Used for encoding."""
        ret = []
        for vhi in self._virtualHelixItemList:
            ret.append(vhi.coord())
        return ret

    # end def

    def numberOfVirtualHelices(self):
        return len(self._virtualHelixItemList)

    # end def

    def reorderHelices(self, first, last, indexDelta):
        """
        Reorder helices by moving helices _pathHelixList[first:last]
        by a distance delta in the list. Notify each PathHelix and
        PathHelixHandle of its new location.
        """
        vhiList = self._virtualHelixItemList
        helixNumbers = [vhi.number() for vhi in vhiList]
        firstIndex = helixNumbers.index(first)
        lastIndex = helixNumbers.index(last) + 1

        if indexDelta < 0:  # move group earlier in the list
            newIndex = max(0, indexDelta + firstIndex)
            newList = vhiList[0:newIndex] +\
                                vhiList[firstIndex:lastIndex] +\
                                vhiList[newIndex:firstIndex] +\
                                vhiList[lastIndex:]
        # end if
        else:  # move group later in list
            newIndex = min(len(vhiList), indexDelta + lastIndex)
            newList = vhiList[:firstIndex] +\
                                 vhiList[lastIndex:newIndex] +\
                                 vhiList[firstIndex:lastIndex] +\
                                 vhiList[newIndex:]
        # end else

        # call the method to move the items and store the list
        self._setVirtualHelixItemList(newList, zoomToFit=False)

    # end def

    def setActiveVirtualHelixItem(self, newActiveVHI):
        if newActiveVHI != self._activeVirtualHelixItem:
            self._activeVirtualHelixItem = newActiveVHI
            # self._modelPart.setActiveVirtualHelix(newActiveVHI.virtualHelix())

    # end def

    def setPreXoverItemsVisible(self, virtualHelixItem):
        """
        self._preXoverItems list references prexovers parented to other
        PathHelices such that only the activeHelix maintains the list of
        visible prexovers
        """
        vhi = virtualHelixItem

        if vhi == None:
            if self._preXoverItems:
                # clear all PreXoverItems
                list(map(PreXoverItem.remove, self._preXoverItems))
                self._preXoverItems = []
            return

        vh = vhi.virtualHelix()
        partItem = self
        part = self.part()
        idx = part.activeVirtualHelixIdx()

        # clear all PreXoverItems
        list(map(PreXoverItem.remove, self._preXoverItems))
        self._preXoverItems = []

        potentialXovers = part.potentialCrossoverList(vh, idx)
        for neighbor, index, strandType, isLowIdx in potentialXovers:
            # create one half
            neighborVHI = self.itemForVirtualHelix(neighbor)
            pxi = PreXoverItem(vhi, neighborVHI, index, strandType, isLowIdx)
            # add to list
            self._preXoverItems.append(pxi)
            # create the complement
            pxi = PreXoverItem(neighborVHI, vhi, index, strandType, isLowIdx)
            # add to list
            self._preXoverItems.append(pxi)
        # end for

    # end def

    def updatePreXoverItems(self):
        self.setPreXoverItemsVisible(self.activeVirtualHelixItem())

    # end def

    def updateXoverItems(self, virtualHelixItem):
        for item in virtualHelixItem.childItems():
            if isinstance(item, XoverNode3):
                item.refreshXover()

    # end def

    def updateStatusBar(self, statusString):
        """Shows statusString in the MainWindow's status bar."""
        self.window().statusBar().showMessage(statusString)

    ### COORDINATE METHODS ###
    def keyPanDeltaX(self):
        """How far a single press of the left or right arrow key should move
        the scene (in scene space)"""
        vhs = self._virtualHelixItemList
        return vhs[0].keyPanDeltaX() if vhs else 5

    # end def

    def keyPanDeltaY(self):
        """How far an an arrow key should move the scene (in scene space)
        for a single press"""
        vhs = self._virtualHelixItemList
        if not len(vhs) > 1:
            return 5
        dy = vhs[0].pos().y() - vhs[1].pos().y()
        dummyRect = QRectF(0, 0, 1, dy)
        return self.mapToScene(dummyRect).boundingRect().height()

    # end def

    ### TOOL METHODS ###
    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        activeTool = self._activeTool()
        toolMethodName = str(activeTool) + "HoverMove"
        if hasattr(self, toolMethodName):
            getattr(self, toolMethodName)(event.pos())

    # end def

    def pencilToolHoverMove(self, pt):
        """Pencil the strand is possible."""
        partItem = self
        activeTool = self._activeTool()
        if not activeTool.isFloatingXoverBegin():
            tempXover = activeTool.floatingXover()
            tempXover.updateFloatingFromPartItem(self, pt)
Example #11
0
class PartItem(QGraphicsItem):
    _radius = styles.SLICE_HELIX_RADIUS

    def __init__(self, modelPart, parent=None):
        """
        Parent should be either a SliceRootItem, or an AssemblyItem.

        Invariant: keys in _emptyhelixhash = range(_nrows) x range(_ncols)
        where x is the cartesian product.
        
        Order matters for deselector, probe, and setlattice
        """
        super(PartItem, self).__init__(parent)
        self._part = modelPart
        self._controller = PartItemController(self, modelPart)
        self._activeSliceItem = ActiveSliceItem(self, modelPart.activeBaseIndex())
        self._scaleFactor = self._radius/modelPart.radius()
        self._emptyhelixhash = {}
        self._virtualHelixHash = {}
        self._nrows, self._ncols = 0, 0
        self._rect = QRectF(0, 0, 0, 0)
        self._initDeselector()
        # Cache of VHs that were active as of last call to activeSliceChanged
        # If None, all slices will be redrawn and the cache will be filled.
        # Connect destructor. This is for removing a part from scenes.
        self.probe = self.IntersectionProbe(self)
        # initialize the PartItem with an empty set of old coords
        self._setLattice([], modelPart.generatorFullLattice())
        self.setFlag(QGraphicsItem.ItemHasNoContents)  # never call paint
        self.setZValue(styles.ZPARTITEM)
        self._initModifierCircle()
    # end def

    def _initDeselector(self):
        """
        The deselector grabs mouse events that missed a slice and clears the
        selection when it gets one.
        """
        self.deselector = ds = PartItem.Deselector(self)
        ds.setParentItem(self)
        ds.setFlag(QGraphicsItem.ItemStacksBehindParent)
        ds.setZValue(styles.ZDESELECTOR)

    def _initModifierCircle(self):
        self._canShowModCirc = False
        self._modCirc = mC = QGraphicsEllipseItem(_hoverRect, self)
        mC.setPen(_modPen)
        mC.hide()
    # end def

    ### SIGNALS ###

    ### SLOTS ###
    def partActiveVirtualHelixChangedSlot(self, part, virtualHelix):
        pass

    def partDimensionsChangedSlot(self, sender):
        pass
    # end def

    def partHideSlot(self, sender):
        self.hide()
    # end def

    def partParentChangedSlot(self, sender):
        """docstring for partParentChangedSlot"""
        # print "PartItem.partParentChangedSlot"
        pass

    def partRemovedSlot(self, sender):
        """docstring for partRemovedSlot"""
        self._activeSliceItem.removed()
        self.parentItem().removePartItem(self)
        
        scene = self.scene()
        
        self._virtualHelixHash = None
        
        for item in self._emptyhelixhash.items():
            key, val = item
            scene.removeItem(val)
            del self._emptyhelixhash[key]
        self._emptyhelixhash = None

        scene.removeItem(self)
        
        self._part = None
        self.probe = None
        self._modCirc = None
        
        self.deselector = None
        self._controller.disconnectSignals()
        self._controller = None
    # end def

    def partVirtualHelicesReorderedSlot(self, sender, orderedCoordList):
        pass
    # end def

    def partPreDecoratorSelectedSlot(self, sender, row, col, baseIdx):
        """docstring for partPreDecoratorSelectedSlot"""
        vhi = self.getVirtualHelixItemByCoord(row, col)
        view = self.window().sliceGraphicsView
        view.sceneRootItem.resetTransform()
        view.centerOn(vhi)
        view.zoomIn()
        mC = self._modCirc
        x,y = self._part.latticeCoordToPositionXY(row, col, self.scaleFactor())
        mC.setPos(x,y)
        if self._canShowModCirc:
            mC.show()
    # end def

    def partVirtualHelixAddedSlot(self, sender, virtualHelix):
        vh = virtualHelix
        coords = vh.coord()

        emptyHelixItem = self._emptyhelixhash[coords]
        # TODO test to see if self._virtualHelixHash is necessary
        vhi = VirtualHelixItem(vh, emptyHelixItem)
        self._virtualHelixHash[coords] = vhi
    # end def

    def partVirtualHelixRenumberedSlot(self, sender, coord):
        pass
    # end def

    def partVirtualHelixResizedSlot(self, sender, coord):
        pass
    # end def

    def updatePreXoverItemsSlot(self, sender, virtualHelix):
        pass
    # end def

    ### ACCESSORS ###
    def boundingRect(self):
        return self._rect
    # end def

    def part(self):
        return self._part
    # end def

    def scaleFactor(self):
        return self._scaleFactor
    # end def

    def setPart(self, newPart):
        self._part = newPart
    # end def

    def window(self):
        return self.parentItem().window()
    # end def

    ### PRIVATE SUPPORT METHODS ###
    def _upperLeftCornerForCoords(self, row, col):
        pass  # subclass
    # end def

    def _updateGeometry(self):
        self._rect = QRectF(0, 0, *self.part().dimensions())
    # end def

    def _spawnEmptyHelixItemAt(self, row, column):
        helix = EmptyHelixItem(row, column, self)
        # helix.setFlag(QGraphicsItem.ItemStacksBehindParent, True)
        self._emptyhelixhash[(row, column)] = helix
    # end def

    def _killHelixItemAt(row, column):
        s = self._emptyhelixhash[(row, column)]
        s.scene().removeItem(s)
        del self._emptyhelixhash[(row, column)]
    # end def

    def _setLattice(self, oldCoords, newCoords):
        """A private method used to change the number of rows,
        cols in response to a change in the dimensions of the
        part represented by the receiver"""
        oldSet = set(oldCoords)
        oldList = list(oldSet)
        newSet = set(newCoords)
        newList = list(newSet)
        for coord in oldList:
            if coord not in newSet:
                self._killHelixItemAt(*coord)
        # end for
        for coord in newList:
            if coord not in oldSet:
                self._spawnEmptyHelixItemAt(*coord)
        # end for
        # self._updateGeometry(newCols, newRows)
        # self.prepareGeometryChange()
        # the Deselector copies our rect so it changes too
        self.deselector.prepareGeometryChange()
        self.zoomToFit()
    # end def

    ### PUBLIC SUPPORT METHODS ###
    def getVirtualHelixItemByCoord(self, row, column):
        if (row, column) in self._emptyhelixhash:
            return self._virtualHelixHash[(row, column)]
        else:
            return None
    # end def

    def paint(self, painter, option, widget=None):
        pass
    # end def

    def selectionWillChange(self, newSel):
        if self.part() == None:
            return
        if self.part().selectAllBehavior():
            return
        for sh in self._emptyhelixhash.itervalues():
            sh.setSelected(sh.virtualHelix() in newSel)
    # end def

    def setModifyState(self, bool):
        """Hides the modRect when modify state disabled."""
        self._canShowModCirc = bool
        if bool == False:
            self._modCirc.hide()

    def updateStatusBar(self, statusString):
        """Shows statusString in the MainWindow's status bar."""
        pass  # disabled for now.
        # self.window().statusBar().showMessage(statusString, timeout)

    def vhAtCoordsChanged(self, row, col):
        self._emptyhelixhash[(row, col)].update()
    # end def

    def zoomToFit(self):
        thescene = self.scene()
        theview = thescene.views()[0]
        theview.zoomToFit()
    # end def

    ### EVENT HANDLERS ###
    def mousePressEvent(self, event):
        # self.createOrAddBasesToVirtualHelix()
        QGraphicsItem.mousePressEvent(self, event)
    # end def


    class Deselector(QGraphicsItem):
        """The deselector lives behind all the slices and observes mouse press
        events that miss slices, emptying the selection when they do"""
        def __init__(self, parentHGI):
            super(PartItem.Deselector, self).__init__()
            self.parentHGI = parentHGI
        def mousePressEvent(self, event):
            self.parentHGI.part().setSelection(())
            super(PartItem.Deselector, self).mousePressEvent(event)
        def boundingRect(self):
            return self.parentHGI.boundingRect()
        def paint(self, painter, option, widget=None):
            pass


    class IntersectionProbe(QGraphicsItem):
        def boundingRect(self):
            return QRectF(0, 0, .1, .1)
        def paint(self, painter, option, widget=None):
            pass
Example #12
0
class PartItem(QGraphicsRectItem):
    findChild = util.findChild  # for debug

    def __init__(self, modelPart, viewroot, activeTool, parent):
        """parent should always be pathrootitem"""
        super(PartItem, self).__init__(parent)
        self._modelPart = mP = modelPart
        self._viewroot = viewroot
        self._activeTool = activeTool
        self._activeSliceItem = ActiveSliceItem(self, mP.activeBaseIndex())
        self._activeVirtualHelixItem = None
        self._controller = PartItemController(self, mP)
        self._preXoverItems = []  # crossover-related
        self._virtualHelixHash = {}
        self._virtualHelixItemList = []
        self._vHRect = QRectF()
        self.setAcceptHoverEvents(True)
        self._initModifierRect()
        self._initResizeButtons()
        self._proxyParent = ProxyParentItem(self)
        self._proxyParent.setFlag(QGraphicsItem.ItemHasNoContents)
    # end def
    
    def proxy(self):
        return self._proxyParent
    # end def

    def _initModifierRect(self):
        """docstring for _initModifierRect"""
        self._canShowModRect = False
        self._modRect = mR = QGraphicsRectItem(_defaultRect, self)
        mR.setPen(_modPen)
        mR.hide()
    # end def

    def _initResizeButtons(self):
        """Instantiate the buttons used to change the canvas size."""
        self._addBasesButton = SVGButton(":/pathtools/add-bases", self)
        self._addBasesButton.clicked.connect(self._addBasesClicked)
        self._addBasesButton.hide()
        self._removeBasesButton = SVGButton(":/pathtools/remove-bases", self)
        self._removeBasesButton.clicked.connect(self._removeBasesClicked)
        self._removeBasesButton.hide()
    # end def

    def vhItemForVH(self, vhref):
        """Returns the pathview VirtualHelixItem corresponding to vhref"""
        vh = self._modelPart.virtualHelix(vhref)
        return self._virtualHelixHash.get(vh.coord())

    ### SIGNALS ###

    ### SLOTS ###
    def partParentChangedSlot(self, sender):
        """docstring for partParentChangedSlot"""
        # print "PartItem.partParentChangedSlot"
        pass
    # end def

    def partHideSlot(self, sender):
        self.hide()
    # end def

    def partActiveVirtualHelixChangedSlot(self, part, virtualHelix):
        self.updatePreXoverItems()
    #end def

    def partDimensionsChangedSlot(self, part):
        if len(self._virtualHelixItemList) > 0:
            vhi = self._virtualHelixItemList[0]
            vhiRect = vhi.boundingRect()
            vhiHRect = vhi.handle().boundingRect()
            self._vHRect.setLeft(vhiHRect.left())
            self._vHRect.setRight(vhiRect.right())
        self.scene().views()[0].zoomToFit()
        self._activeSliceItem.resetBounds()
        self._updateBoundingRect()
    # end def

    def partRemovedSlot(self, sender):
        """docstring for partRemovedSlot"""
        self._activeSliceItem.removed()
        self.parentItem().removePartItem(self)
        scene = self.scene()
        scene.removeItem(self)
        self._modelPart = None
        self._virtualHelixHash = None
        self._virtualHelixItemList = None
        self._controller.disconnectSignals()
        self._controller = None
    # end def

    def partPreDecoratorSelectedSlot(self, sender, row, col, baseIdx):
        """docstring for partPreDecoratorSelectedSlot"""
        part = self._modelPart
        vh = part.virtualHelixAtCoord((row,col))
        vhi = self.itemForVirtualHelix(vh)
        yOffset = _bw if vh.isEvenParity() else 0
        p = QPointF(baseIdx*_bw, vhi.y() + yOffset)
        view = self.window().pathGraphicsView
        view.sceneRootItem.resetTransform()
        view.centerOn(p)
        view.zoomIn()
        self._modRect.setPos(p)
        if self._canShowModRect:
            self._modRect.show()
    # end def

    def partVirtualHelixAddedSlot(self, sender, modelVirtualHelix):
        """
        When a virtual helix is added to the model, this slot handles
        the instantiation of a virtualhelix item.
        """
        # print "PartItem.partVirtualHelixAddedSlot"
        vh = modelVirtualHelix
        vhi = VirtualHelixItem(self, modelVirtualHelix, self._viewroot, self._activeTool)
        self._virtualHelixHash[vh.coord()] = vhi
        self._virtualHelixItemList.append(vhi)
        self._setVirtualHelixItemList(self._virtualHelixItemList)
        self._updateBoundingRect()
    # end def

    def partVirtualHelixRenumberedSlot(self, sender, coord):
        """Notifies the virtualhelix at coord to change its number"""
        vh = self._virtualHelixHash[coord]
        # check for new number
        # notify VirtualHelixHandleItem to update its label
        # notify VirtualHelix to update its xovers
        # if the VirtualHelix is active, refresh prexovers
        pass
    # end def

    def partVirtualHelixResizedSlot(self, sender, coord):
        """Notifies the virtualhelix at coord to resize."""
        vh = self._virtualHelixHash[coord]
        vh.resize()
    # end def

    def partVirtualHelicesReorderedSlot(self, sender, orderedCoordList):
        """docstring for partVirtualHelicesReorderedSlot"""
        newList = self._virtualHelixItemList
        decorated = [(orderedCoordList.index(vhi.coord()), vhi)\
                        for vhi in self._virtualHelixItemList]
        decorated.sort()
        newList = [vhi for idx, vhi in decorated]
        self._setVirtualHelixItemList(newList)
    # end def

    def updatePreXoverItemsSlot(self, sender, virtualHelix):
        part = self.part()
        if virtualHelix == None:
            self.setPreXoverItemsVisible(None)
        elif part.areSameOrNeighbors(part.activeVirtualHelix(), virtualHelix):
            vhi = self.itemForVirtualHelix(virtualHelix)
            self.setActiveVirtualHelixItem(vhi)
            self.setPreXoverItemsVisible(self.activeVirtualHelixItem())
    # end def

    ### ACCESSORS ###
    def activeTool(self):
        return self._activeTool
    # end def

    def activeVirtualHelixItem(self):
        return self._activeVirtualHelixItem
    # end def

    def part(self):
        """Return a reference to the model's part object"""
        return self._modelPart
    # end def

    def document(self):
        """Return a reference to the model's document object"""
        return self._modelPart.document()
    # end def

    def removeVirtualHelixItem(self, virtualHelixItem):
        vh = virtualHelixItem.virtualHelix()
        self._virtualHelixItemList.remove(virtualHelixItem)
        del self._virtualHelixHash[vh.coord()]
        self._setVirtualHelixItemList(self._virtualHelixItemList)
        self._updateBoundingRect()

    # end def

    def itemForVirtualHelix(self, virtualHelix):
        return self._virtualHelixHash[virtualHelix.coord()]
    # end def

    def virtualHelixBoundingRect(self):
        return self._vHRect
    # end def

    def window(self):
        return self.parentItem().window()
    # end def

    ### PRIVATE METHODS ###
    def _addBasesClicked(self):
        part = self._modelPart
        step = part.stepSize()
        self._addBasesDialog = dlg = QInputDialog(self.window())
        dlg.setInputMode(QInputDialog.IntInput)
        dlg.setIntMinimum(0)
        dlg.setIntValue(step)
        dlg.setIntMaximum(100000)
        dlg.setIntStep(step)
        dlg.setLabelText(( "Number of bases to add to the existing"\
                         + " %i bases\n(must be a multiple of %i)")\
                         % (part.maxBaseIdx(), step))
        dlg.intValueSelected.connect(self._addBasesCallback)
        dlg.open()
    # end def

    @pyqtSlot(int)
    def _addBasesCallback(self, n):
        """
        Given a user-chosen number of bases to add, snap it to an index
        where index modulo stepsize is 0 and calls resizeVirtualHelices to
        adjust to that size.
        """
        part = self._modelPart
        self._addBasesDialog.intValueSelected.disconnect(self._addBasesCallback)
        del self._addBasesDialog
        maxDelta = int(n) / part.stepSize() * part.stepSize()
        part.resizeVirtualHelices(0, maxDelta)
        if app().isInMaya():
            import maya.cmds as cmds
            cmds.select(clear=True)
    # end def

    def _removeBasesClicked(self):
        """
        Determines the minimum maxBase index where index modulo stepsize == 0
        and is to the right of the rightmost nonempty base, and then resize
        each calls the resizeVirtualHelices to adjust to that size.
        """
        part = self._modelPart
        stepSize = part.stepSize()
        # first find out the right edge of the part
        idx = part.indexOfRightmostNonemptyBase()
        # next snap to a multiple of stepsize
        idx = int(ceil(float(idx+1)/stepSize))*stepSize
        # finally, make sure we're a minimum of stepSize bases
        idx = util.clamp(idx, part.stepSize(), 10000)
        delta = idx - (part.maxBaseIdx() + 1)
        if delta < 0:
            part.resizeVirtualHelices(0, delta)
            if app().isInMaya():
                import maya.cmds as cmds
                cmds.select(clear=True)
    # end def

    def _setVirtualHelixItemList(self, newList, zoomToFit=True):
        """
        Give me a list of VirtualHelixItems and I'll parent them to myself if
        necessary, position them in a column, adopt their handles, and
        position them as well.
        """
        y = 0  # How far down from the top the next PH should be
        leftmostExtent = 0
        rightmostExtent = 0

        scene = self.scene()
        vhiRect = None
        vhiHRect = None

        for vhi in newList:
            vhi.setPos(0, y)
            if not vhiRect:
                vhiRect = vhi.boundingRect()
                step = vhiRect.height() + styles.PATH_HELIX_PADDING
            # end if

            # get the VirtualHelixHandleItem
            vhiH = vhi.handle()
            if vhiH.parentItem() != self._viewroot._vhiHSelectionGroup:
                vhiH.setParentItem(self)

            if not vhiHRect:
                vhiHRect = vhiH.boundingRect()

            vhiH.setPos(-2 * vhiHRect.width(), y + (vhiRect.height() - vhiHRect.height()) / 2)

            leftmostExtent = min(leftmostExtent, -2 * vhiHRect.width())
            rightmostExtent = max(rightmostExtent, vhiRect.width())
            y += step
            self.updateXoverItems(vhi)
        # end for
        self._vHRect = QRectF(leftmostExtent, -40, -leftmostExtent + rightmostExtent, y + 40)
        self._virtualHelixItemList = newList
        if zoomToFit:
            self.scene().views()[0].zoomToFit()
    # end def

    def _updateBoundingRect(self):
        """
        Updates the bounding rect to the size of the childrenBoundingRect,
        and refreshes the addBases and removeBases buttons accordingly.

        Called by partVirtualHelixAddedSlot, partDimensionsChangedSlot, or
        removeVirtualHelixItem.
        """
        self.setPen(QPen(Qt.NoPen))
        self.setRect(self.childrenBoundingRect())
        # move and show or hide the buttons if necessary
        addButton = self._addBasesButton
        rmButton = self._removeBasesButton
        if len(self._virtualHelixItemList) > 0:
            addRect = addButton.boundingRect()
            rmRect = rmButton.boundingRect()
            x = self._vHRect.right()
            y = -styles.PATH_HELIX_PADDING
            addButton.setPos(x, y)
            rmButton.setPos(x-rmRect.width(), y)
            addButton.show()
            rmButton.show()
        else:
            addButton.hide()
            rmButton.hide()
    # end def

    ### PUBLIC METHODS ###
    def setModifyState(self, bool):
        """Hides the modRect when modify state disabled."""
        self._canShowModRect = bool
        if bool == False:
            self._modRect.hide()

    def getOrderedVirtualHelixList(self):
        """Used for encoding."""
        ret = []
        for vhi in self._virtualHelixItemList:
            ret.append(vhi.coord())
        return ret
    # end def

    def numberOfVirtualHelices(self):
        return len(self._virtualHelixItemList)
    # end def

    def reorderHelices(self, first, last, indexDelta):
        """
        Reorder helices by moving helices _pathHelixList[first:last]
        by a distance delta in the list. Notify each PathHelix and
        PathHelixHandle of its new location.
        """
        vhiList = self._virtualHelixItemList
        helixNumbers = [vhi.number() for vhi in vhiList]
        firstIndex = helixNumbers.index(first)
        lastIndex = helixNumbers.index(last) + 1

        if indexDelta < 0:  # move group earlier in the list
            newIndex = max(0, indexDelta + firstIndex)
            newList = vhiList[0:newIndex] +\
                                vhiList[firstIndex:lastIndex] +\
                                vhiList[newIndex:firstIndex] +\
                                vhiList[lastIndex:]
        # end if
        else:  # move group later in list
            newIndex = min(len(vhiList), indexDelta + lastIndex)
            newList = vhiList[:firstIndex] +\
                                 vhiList[lastIndex:newIndex] +\
                                 vhiList[firstIndex:lastIndex] +\
                                 vhiList[newIndex:]
        # end else

        # call the method to move the items and store the list
        self._setVirtualHelixItemList(newList, zoomToFit=False)
    # end def

    def setActiveVirtualHelixItem(self, newActiveVHI):
        if newActiveVHI != self._activeVirtualHelixItem:
            self._activeVirtualHelixItem = newActiveVHI
            # self._modelPart.setActiveVirtualHelix(newActiveVHI.virtualHelix())
    # end def

    def setPreXoverItemsVisible(self, virtualHelixItem):
        """
        self._preXoverItems list references prexovers parented to other
        PathHelices such that only the activeHelix maintains the list of
        visible prexovers
        """
        vhi = virtualHelixItem

        if vhi == None:
            if self._preXoverItems:
                # clear all PreXoverItems
                map(PreXoverItem.remove, self._preXoverItems)
                self._preXoverItems = []
            return

        vh = vhi.virtualHelix()
        partItem = self
        part = self.part()
        idx = part.activeVirtualHelixIdx()

        # clear all PreXoverItems
        map(PreXoverItem.remove, self._preXoverItems)
        self._preXoverItems = []

        potentialXovers = part.potentialCrossoverList(vh, idx)
        for neighbor, index, strandType, isLowIdx in potentialXovers:
            # create one half
            neighborVHI = self.itemForVirtualHelix(neighbor)
            pxi = PreXoverItem(vhi, neighborVHI, index, strandType, isLowIdx)
            # add to list
            self._preXoverItems.append(pxi)
            # create the complement
            pxi = PreXoverItem(neighborVHI, vhi, index, strandType, isLowIdx)
            # add to list
            self._preXoverItems.append(pxi)
        # end for
    # end def

    def updatePreXoverItems(self):
        self.setPreXoverItemsVisible(self.activeVirtualHelixItem())
    # end def

    def updateXoverItems(self, virtualHelixItem):
        for item in virtualHelixItem.childItems():
            if isinstance(item, XoverNode3):
                item.refreshXover()
     # end def

    def updateStatusBar(self, statusString):
        """Shows statusString in the MainWindow's status bar."""
        self.window().statusBar().showMessage(statusString)

    ### COORDINATE METHODS ###
    def keyPanDeltaX(self):
        """How far a single press of the left or right arrow key should move
        the scene (in scene space)"""
        vhs = self._virtualHelixItemList
        return vhs[0].keyPanDeltaX() if vhs else 5
    # end def

    def keyPanDeltaY(self):
        """How far an an arrow key should move the scene (in scene space)
        for a single press"""
        vhs = self._virtualHelixItemList
        if not len(vhs) > 1:
            return 5
        dy = vhs[0].pos().y() - vhs[1].pos().y()
        dummyRect = QRectF(0, 0, 1, dy)
        return self.mapToScene(dummyRect).boundingRect().height()
    # end def

    ### TOOL METHODS ###
    def hoverMoveEvent(self, event):
        """
        Parses a mouseMoveEvent to extract strandSet and base index,
        forwarding them to approproate tool method as necessary.
        """
        activeTool = self._activeTool()
        toolMethodName = str(activeTool) + "HoverMove"
        if hasattr(self, toolMethodName):
            getattr(self, toolMethodName)(event.pos())
    # end def

    def pencilToolHoverMove(self, pt):
        """Pencil the strand is possible."""
        partItem = self
        activeTool = self._activeTool()
        if not activeTool.isFloatingXoverBegin():
            tempXover = activeTool.floatingXover()
            tempXover.updateFloatingFromPartItem(self, pt)