def __init__(self, model_part, parent=None): """ Parent should be either a SliceRootItem, or an AssemblyItem. Invariant: keys in _empty_helix_hash = 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 = model_part self._controller = PartItemController(self, model_part) self._active_slice_item = ActiveSliceItem(self, model_part.activeBaseIndex()) self._scaleFactor = self._RADIUS / model_part.radius() self._empty_helix_hash = {} self._virtual_helix_hash = {} 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([], model_part.generatorFullLattice()) self.setFlag(QGraphicsItem.ItemHasNoContents) # never call paint self.setZValue(styles.ZPARTITEM) self._initModifierCircle()
def __init__(self, model_part, parent=None): """ Parent should be either a SliceRootItem, or an AssemblyItem. Invariant: keys in _empty_helix_hash = 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 = model_part self._controller = PartItemController(self, model_part) self._active_slice_item = ActiveSliceItem(self, model_part.activeBaseIndex()) self._scaleFactor = self._RADIUS/model_part.radius() self._empty_helix_hash = {} self._virtual_helix_hash = {} 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([], model_part.generatorFullLattice()) self.setFlag(QGraphicsItem.ItemHasNoContents) # never call paint self.setZValue(styles.ZPARTITEM) self._initModifierCircle()
def __init__(self, model_part, viewroot, active_tool_getter, parent): """parent should always be pathrootitem""" super(PartItem, self).__init__(parent) self._model_part = m_p = model_part self._viewroot = viewroot self._getActiveTool = active_tool_getter self._activeSliceItem = ActiveSliceItem(self, m_p.activeBaseIndex()) self._active_virtual_helix_item = None self._controller = PartItemController(self, m_p) self._pre_xover_items = [] # crossover-related self._virtual_helix_hash = {} self._virtual_helix_item_list = [] self._vh_rect = QRectF() self.setAcceptHoverEvents(True) self._initModifierRect() self._initResizeButtons() self._proxy_parent = ProxyParentItem(self) self._proxy_parent.setFlag(QGraphicsItem.ItemHasNoContents)
def __init__(self, model_part,viewroot, parent): """parent should always be pathrootitem""" super(PartItem, self).__init__(parent) self._model_part = m_p = model_part self._viewroot = viewroot # self._getActiveTool = active_tool_getter # self._activeSliceItem = ActiveSliceItem(self, m_p.activeBaseIndex()) self._active_virtual_helix_item = None self._controller = PartItemController(self, m_p) self._pre_xover_items = [] # crossover-related self._virtual_helix_hash = {} self._virtual_helix_item_list = [] self._vh_rect = QRectF() self.setAcceptHoverEvents(True) self._initModifierRect() self._initResizeButtons() self._proxy_parent = ProxyParentItem(self) self._proxy_parent.setFlag(QGraphicsItem.ItemHasNoContents)
class PartItem(QGraphicsItem): _RADIUS = styles.SLICE_HELIX_RADIUS def __init__(self, model_part, parent=None): """ Parent should be either a SliceRootItem, or an AssemblyItem. Invariant: keys in _empty_helix_hash = 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 = model_part self._controller = PartItemController(self, model_part) self._active_slice_item = ActiveSliceItem(self, model_part.activeBaseIndex()) self._scaleFactor = self._RADIUS / model_part.radius() self._empty_helix_hash = {} self._virtual_helix_hash = {} 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([], model_part.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._can_show_mod_circ = False self._mod_circ = m_c = QGraphicsEllipseItem(_HOVER_RECT, self) m_c.setPen(_MOD_PEN) m_c.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._active_slice_item.removed() self.parentItem().removePartItem(self) scene = self.scene() self._virtual_helix_hash = None for item in list(self._empty_helix_hash.items()): key, val = item scene.removeItem(val) del self._empty_helix_hash[key] self._empty_helix_hash = None scene.removeItem(self) self._part = None self.probe = None self._mod_circ = 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().slice_graphics_view view.scene_root_item.resetTransform() view.centerOn(vhi) view.zoomIn() mC = self._mod_circ x, y = self._part.latticeCoordToPositionXY(row, col, self.scaleFactor()) mC.setPos(x, y) if self._can_show_mod_circ: mC.show() # end def def partVirtualHelixAddedSlot(self, sender, virtual_helix): vh = virtual_helix coords = vh.coord() empty_helix_item = self._empty_helix_hash[coords] # TODO test to see if self._virtual_helix_hash is necessary vhi = VirtualHelixItem(vh, empty_helix_item) self._virtual_helix_hash[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._empty_helix_hash[(row, column)] = helix # end def def _killHelixItemAt(row, column): s = self._empty_helix_hash[(row, column)] s.scene().removeItem(s) del self._empty_helix_hash[(row, column)] # end def def _setLattice(self, old_coords, new_coords): """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""" old_set = set(old_coords) old_list = list(old_set) new_set = set(new_coords) new_list = list(new_set) for coord in old_list: if coord not in new_set: self._killHelixItemAt(*coord) # end for for coord in new_list: if coord not in old_set: self._spawnEmptyHelixItemAt(*coord) # end for # self._updateGeometry(newCols, newRows) # self.prepareGeometryChange() # the Deselector copies our rect so it changes too self.deselector.prepareGeometryChange() if not getReopen(): self.zoomToFit() # end def ### PUBLIC SUPPORT METHODS ### def getVirtualHelixItemByCoord(self, row, column): if (row, column) in self._empty_helix_hash: return self._virtual_helix_hash[(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._empty_helix_hash.values(): sh.setSelected(sh.virtualHelix() in newSel) # end def def setModifyState(self, bool): """Hides the mod_rect when modify state disabled.""" self._can_show_mod_circ = bool if bool == False: self._mod_circ.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._empty_helix_hash[(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, parent_HGI): super(PartItem.Deselector, self).__init__() self.parent_HGI = parent_HGI def mousePressEvent(self, event): self.parent_HGI.part().setSelection(()) super(PartItem.Deselector, self).mousePressEvent(event) def boundingRect(self): return self.parent_HGI.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
class PartItem(QGraphicsItem): _RADIUS = styles.SLICE_HELIX_RADIUS def __init__(self, model_part, parent=None): """ Parent should be either a SliceRootItem, or an AssemblyItem. Invariant: keys in _empty_helix_hash = 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 = model_part self._controller = PartItemController(self, model_part) self._active_slice_item = ActiveSliceItem(self, model_part.activeBaseIndex()) self._scaleFactor = self._RADIUS/model_part.radius() self._empty_helix_hash = {} self._virtual_helix_hash = {} 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([], model_part.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._can_show_mod_circ = False self._mod_circ = m_c = QGraphicsEllipseItem(_HOVER_RECT, self) m_c.setPen(_MOD_PEN) m_c.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._active_slice_item.removed() self.parentItem().removePartItem(self) scene = self.scene() self._virtual_helix_hash = None for item in list(self._empty_helix_hash.items()): key, val = item scene.removeItem(val) del self._empty_helix_hash[key] self._empty_helix_hash = None scene.removeItem(self) self._part = None self.probe = None self._mod_circ = 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().slice_graphics_view view.scene_root_item.resetTransform() view.centerOn(vhi) view.zoomIn() mC = self._mod_circ x,y = self._part.latticeCoordToPositionXY(row, col, self.scaleFactor()) mC.setPos(x,y) if self._can_show_mod_circ: mC.show() # end def def partVirtualHelixAddedSlot(self, sender, virtual_helix): vh = virtual_helix coords = vh.coord() empty_helix_item = self._empty_helix_hash[coords] vhi = VirtualHelixItem(vh, empty_helix_item) self._virtual_helix_hash[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._empty_helix_hash[(row, column)] = helix # end def def _killHelixItemAt(row, column): s = self._empty_helix_hash[(row, column)] s.scene().removeItem(s) del self._empty_helix_hash[(row, column)] # end def def _setLattice(self, old_coords, new_coords): """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""" old_set = set(old_coords) old_list = list(old_set) new_set = set(new_coords) new_list = list(new_set) for coord in old_list: if coord not in new_set: self._killHelixItemAt(*coord) # end for for coord in new_list: if coord not in old_set: self._spawnEmptyHelixItemAt(*coord) # end for # self._updateGeometry(newCols, newRows) # self.prepareGeometryChange() # the Deselector copies our rect so it changes too self.deselector.prepareGeometryChange() if not getReopen(): self.zoomToFit() # end def ### PUBLIC SUPPORT METHODS ### def getVirtualHelixItemByCoord(self, row, column): if (row, column) in self._empty_helix_hash: return self._virtual_helix_hash[(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._empty_helix_hash.values(): sh.setSelected(sh.virtualHelix() in newSel) # end def def setModifyState(self, bool): """Hides the mod_rect when modify state disabled.""" self._can_show_mod_circ = bool if bool == False: self._mod_circ.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._empty_helix_hash[(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, parent_HGI): super(PartItem.Deselector, self).__init__() self.parent_HGI = parent_HGI def mousePressEvent(self, event): self.parent_HGI.part().setSelection(()) super(PartItem.Deselector, self).mousePressEvent(event) def boundingRect(self): return self.parent_HGI.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
class PartItem(QGraphicsRectItem): findChild = util.findChild # for debug def __init__(self, model_part,viewroot, parent): """parent should always be pathrootitem""" super(PartItem, self).__init__(parent) self._model_part = m_p = model_part self._viewroot = viewroot # self._getActiveTool = active_tool_getter # self._activeSliceItem = ActiveSliceItem(self, m_p.activeBaseIndex()) self._active_virtual_helix_item = None self._controller = PartItemController(self, m_p) self._pre_xover_items = [] # crossover-related self._virtual_helix_hash = {} self._virtual_helix_item_list = [] self._vh_rect = QRectF() self.setAcceptHoverEvents(True) self._initModifierRect() self._initResizeButtons() self._proxy_parent = ProxyParentItem(self) self._proxy_parent.setFlag(QGraphicsItem.ItemHasNoContents) # end def def proxy(self): return self._proxy_parent # end def def _initModifierRect(self): """docstring for _initModifierRect""" self._can_show_mod_rect = False self._mod_rect = m_r = QGraphicsRectItem(_DEFAULT_RECT, self) m_r.setPen(_MOD_PEN) m_r.hide() # end def def _initResizeButtons(self): """Instantiate the buttons used to change the canvas size.""" self._add_bases_button = SVGButton(":/pathtools/add-bases", self) self._add_bases_button.clicked.connect(self._addBasesClicked) self._add_bases_button.hide() self._remove_bases_button = SVGButton(":/pathtools/remove-bases", self) self._remove_bases_button.clicked.connect(self._removeBasesClicked) self._remove_bases_button.hide() # end def def vhItemForVH(self, vhref): """Returns the pathview VirtualHelixItem corresponding to vhref""" vh = self._model_part.virtualHelix(vhref) return self._virtual_helix_hash.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, virtual_helix): self.updatePreXoverItems() #end def def partDimensionsChangedSlot(self, part): if len(self._virtual_helix_item_list) > 0: vhi = self._virtual_helix_item_list[0] vhi_rect = vhi.boundingRect() vhi_h_rect = vhi.handle().boundingRect() self._vh_rect.setLeft(vhi_h_rect.left()) self._vh_rect.setRight(vhi_rect.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._model_part = None self._virtual_helix_hash = None self._virtual_helix_item_list = None self._controller.disconnectSignals() self._controller = None # end def def partPreDecoratorSelectedSlot(self, sender, row, col, base_idx): """docstring for partPreDecoratorSelectedSlot""" part = self._model_part vh = part.virtualHelixAtCoord((row,col)) vhi = self.itemForVirtualHelix(vh) y_offset = _BW if vh.isEvenParity() else 0 p = QPointF(base_idx*_BW, vhi.y() + y_offset) view = self.window().path_graphics_view view.scene_root_item.resetTransform() view.centerOn(p) view.zoomIn() self._mod_rect.setPos(p) if self._can_show_mod_rect: self._mod_rect.show() # end def def partVirtualHelixAddedSlot(self, sender, model_virtual_helix): """ When a virtual helix is added to the model, this slot handles the instantiation of a virtualhelix item. """ # print("PartItem.partVirtualHelixAddedSlot") vh = model_virtual_helix vhi = VirtualHelixItem(self, model_virtual_helix, self._viewroot) self._virtual_helix_hash[vh.coord()] = vhi self._virtual_helix_item_list.append(vhi) ztf = not getBatch() self._setVirtualHelixItemList(self._virtual_helix_item_list, zoom_to_fit=ztf) self._updateBoundingRect() # end def def partVirtualHelixRenumberedSlot(self, sender, coord): """Notifies the virtualhelix at coord to change its number""" vh = self._virtual_helix_hash[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._virtual_helix_hash[coord] vh.resize() # end def def partVirtualHelicesReorderedSlot(self, sender, ordered_coord_list): """docstring for partVirtualHelicesReorderedSlot""" new_list = self._virtual_helix_item_list decorated = [(ordered_coord_list.index(vhi.coord()), vhi)\ for vhi in self._virtual_helix_item_list] decorated.sort() new_list = [vhi for idx, vhi in decorated] ztf = not getBatch() self._setVirtualHelixItemList(new_list, zoom_to_fit=ztf) # end def def updatePreXoverItemsSlot(self, sender, virtual_helix): part = self.part() if virtual_helix == None: self.setPreXoverItemsVisible(None) elif part.areSameOrNeighbors(part.activeVirtualHelix(), virtual_helix): vhi = self.itemForVirtualHelix(virtual_helix) self.setActiveVirtualHelixItem(vhi) self.setPreXoverItemsVisible(self.activeVirtualHelixItem()) # end def ### ACCESSORS ### def activeVirtualHelixItem(self): return self._active_virtual_helix_item # end def def part(self): """Return a reference to the model's part object""" return self._model_part # end def def document(self): """Return a reference to the model's document object""" return self._model_part.document() # end def def removeVirtualHelixItem(self, virtual_helix_item): vh = virtual_helix_item.virtualHelix() self._virtual_helix_item_list.remove(virtual_helix_item) del self._virtual_helix_hash[vh.coord()] ztf = not getBatch() self._setVirtualHelixItemList(self._virtual_helix_item_list, zoom_to_fit=ztf) self._updateBoundingRect() # end def def itemForVirtualHelix(self, virtual_helix): return self._virtual_helix_hash[virtual_helix.coord()] # end def def virtualHelixBoundingRect(self): return self._vh_rect # end def def window(self): return self.parentItem().window() # end def ### PRIVATE METHODS ### def _addBasesClicked(self): part = self._model_part 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._model_part self._addBasesDialog.intValueSelected.disconnect(self._addBasesCallback) del self._addBasesDialog maxDelta = 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._model_part step_size = part.stepSize() # first find out the right edge of the part idx = part.indexOfRightmostNonemptyBase() # next snap to a multiple of stepsize idx = ceil((idx + 1) / step_size)*step_size # finally, make sure we're a minimum of step_size bases idx = util.clamp(idx, step_size, 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, new_list, zoom_to_fit=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 leftmost_extent = 0 rightmost_extent = 0 scene = self.scene() vhi_rect = None vhi_h_rect = None for vhi in new_list: vhi.setPos(0, y) if vhi_rect is None: vhi_rect = vhi.boundingRect() step = vhi_rect.height() + styles.PATH_HELIX_PADDING # end if # get the VirtualHelixHandleItem vhi_h = vhi.handle() if vhi_h.parentItem() != self._viewroot._vhi_h_selection_group: vhi_h.setParentItem(self) if vhi_h_rect is None: vhi_h_rect = vhi_h.boundingRect() vhi_h.setPos(-2 * vhi_h_rect.width(), y + (vhi_rect.height() - vhi_h_rect.height()) / 2) leftmost_extent = min(leftmost_extent, -2 * vhi_h_rect.width()) rightmost_extent = max(rightmost_extent, vhi_rect.width()) y += step self.updateXoverItems(vhi) # end for self._vh_rect = QRectF(leftmost_extent, -40, -leftmost_extent + rightmost_extent, y + 40) self._virtual_helix_item_list = new_list if zoom_to_fit: # self.scene().views()[0].zoomToFit() pass # 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 add_button = self._add_bases_button rm_button = self._remove_bases_button if len(self._virtual_helix_item_list) > 0: addRect = add_button.boundingRect() rmRect = rm_button.boundingRect() x = self._vh_rect.right() y = -styles.PATH_HELIX_PADDING add_button.setPos(x, y) rm_button.setPos(x-rmRect.width(), y) add_button.show() rm_button.show() else: add_button.hide() rm_button.hide() # end def ### PUBLIC METHODS ### def setModifyState(self, bool): """Hides the modRect when modify state disabled.""" self._can_show_mod_rect = bool if bool == False: self._mod_rect.hide() def getOrderedVirtualHelixList(self): """Used for encoding.""" ret = [] for vhi in self._virtual_helix_item_list: ret.append(vhi.coord()) return ret # end def def numberOfVirtualHelices(self): return len(self._virtual_helix_item_list) # end def def reorderHelices(self, first, last, index_delta): """ Reorder helices by moving helices _pathHelixList[first:last] by a distance delta in the list. Notify each PathHelix and PathHelixHandle of its new location. """ vhi_list = self._virtual_helix_item_list helix_numbers = [vhi.number() for vhi in vhi_list] first_index = helix_numbers.index(first) last_index = helix_numbers.index(last) + 1 if index_delta < 0: # move group earlier in the list new_index = max(0, index_delta + first_index) new_list = vhi_list[0:new_index] +\ vhi_list[first_index:last_index] +\ vhi_list[new_index:first_index] +\ vhi_list[last_index:] # end if else: # move group later in list new_index = min(len(vhi_list), index_delta + last_index) new_list = vhi_list[:first_index] +\ vhi_list[last_index:new_index] +\ vhi_list[first_index:last_index] +\ vhi_list[new_index:] # end else # call the method to move the items and store the list self._setVirtualHelixItemList(new_list, zoom_to_fit=False) # end def def setActiveVirtualHelixItem(self, new_active_vhi): if new_active_vhi != self._active_virtual_helix_item: self._active_virtual_helix_item = new_active_vhi # self._model_part.setActiveVirtualHelix(new_active_vhi.virtualHelix()) # end def def setPreXoverItemsVisible(self, virtual_helix_item): """ self._pre_xover_items list references prexovers parented to other PathHelices such that only the activeHelix maintains the list of visible prexovers """ vhi = virtual_helix_item if vhi == None: if self._pre_xover_items: # clear all PreXoverItems list(map(PreXoverItem.remove, self._pre_xover_items)) self._pre_xover_items = [] return vh = vhi.virtualHelix() part_item = self part = self.part() idx = part.activeVirtualHelixIdx() # clear all PreXoverItems list(map(PreXoverItem.remove, self._pre_xover_items)) self._pre_xover_items = [] potential_xovers = part.potentialCrossoverList(vh, idx) for neighbor, index, strand_type, is_low_idx in potential_xovers: # create one half neighbor_vhi = self.itemForVirtualHelix(neighbor) pxi = PreXoverItem(vhi, neighbor_vhi, index, strand_type, is_low_idx) # add to list self._pre_xover_items.append(pxi) # create the complement pxi = PreXoverItem(neighbor_vhi, vhi, index, strand_type, is_low_idx) # add to list self._pre_xover_items.append(pxi) # end for # end def def updatePreXoverItems(self): self.setPreXoverItemsVisible(self.activeVirtualHelixItem()) # end def def updateXoverItems(self, virtual_helix_item): for item in virtual_helix_item.childItems(): if isinstance(item, XoverNode3): item.refreshXover() # end def def updateStatusBar(self, status_string): """Shows status_string in the MainWindow's status bar.""" self.window().statusBar().showMessage(status_string) ### 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._virtual_helix_item_list 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._virtual_helix_item_list 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. """ # tool_method_name = self._getActiveTool().methodPrefix() + "HoverMove" # if hasattr(self, tool_method_name): # getattr(self, tool_method_name)(event.pos()) # end def def pencilToolHoverMove(self, pt): """Pencil the strand is possible.""" part_item = self active_tool = self._getActiveTool() if not active_tool.isFloatingXoverBegin(): temp_xover = active_tool.floatingXover() temp_xover.updateFloatingFromPartItem(self, pt)
class PartItem(QGraphicsRectItem): findChild = util.findChild # for debug def __init__(self, model_part, viewroot, active_tool_getter, parent): """parent should always be pathrootitem""" super(PartItem, self).__init__(parent) self._model_part = m_p = model_part self._viewroot = viewroot self._getActiveTool = active_tool_getter self._activeSliceItem = ActiveSliceItem(self, m_p.activeBaseIndex()) self._active_virtual_helix_item = None self._controller = PartItemController(self, m_p) self._pre_xover_items = [] # crossover-related self._virtual_helix_hash = {} self._virtual_helix_item_list = [] self._vh_rect = QRectF() self.setAcceptHoverEvents(True) self._initModifierRect() self._initResizeButtons() self._proxy_parent = ProxyParentItem(self) self._proxy_parent.setFlag(QGraphicsItem.ItemHasNoContents) # end def def proxy(self): return self._proxy_parent # end def def _initModifierRect(self): """docstring for _initModifierRect""" self._can_show_mod_rect = False self._mod_rect = m_r = QGraphicsRectItem(_DEFAULT_RECT, self) m_r.setPen(_MOD_PEN) m_r.hide() # end def def _initResizeButtons(self): """Instantiate the buttons used to change the canvas size.""" self._add_bases_button = SVGButton(":/pathtools/add-bases", self) self._add_bases_button.clicked.connect(self._addBasesClicked) self._add_bases_button.hide() self._remove_bases_button = SVGButton(":/pathtools/remove-bases", self) self._remove_bases_button.clicked.connect(self._removeBasesClicked) self._remove_bases_button.hide() # end def def vhItemForVH(self, vhref): """Returns the pathview VirtualHelixItem corresponding to vhref""" vh = self._model_part.virtualHelix(vhref) return self._virtual_helix_hash.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, virtual_helix): self.updatePreXoverItems() #end def def partDimensionsChangedSlot(self, part): if len(self._virtual_helix_item_list) > 0: vhi = self._virtual_helix_item_list[0] vhi_rect = vhi.boundingRect() vhi_h_rect = vhi.handle().boundingRect() self._vh_rect.setLeft(vhi_h_rect.left()) self._vh_rect.setRight(vhi_rect.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._model_part = None self._virtual_helix_hash = None self._virtual_helix_item_list = None self._controller.disconnectSignals() self._controller = None # end def def partPreDecoratorSelectedSlot(self, sender, row, col, base_idx): """docstring for partPreDecoratorSelectedSlot""" part = self._model_part vh = part.virtualHelixAtCoord((row, col)) vhi = self.itemForVirtualHelix(vh) y_offset = _BW if vh.isEvenParity() else 0 p = QPointF(base_idx * _BW, vhi.y() + y_offset) view = self.window().path_graphics_view view.scene_root_item.resetTransform() view.centerOn(p) view.zoomIn() self._mod_rect.setPos(p) if self._can_show_mod_rect: self._mod_rect.show() # end def def partVirtualHelixAddedSlot(self, sender, model_virtual_helix): """ When a virtual helix is added to the model, this slot handles the instantiation of a virtualhelix item. """ # print("PartItem.partVirtualHelixAddedSlot") vh = model_virtual_helix vhi = VirtualHelixItem(self, model_virtual_helix, self._viewroot) self._virtual_helix_hash[vh.coord()] = vhi self._virtual_helix_item_list.append(vhi) ztf = not getBatch() self._setVirtualHelixItemList(self._virtual_helix_item_list, zoom_to_fit=ztf) self._updateBoundingRect() # end def def partVirtualHelixRenumberedSlot(self, sender, coord): """Notifies the virtualhelix at coord to change its number""" vh = self._virtual_helix_hash[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._virtual_helix_hash[coord] vh.resize() # end def def partVirtualHelicesReorderedSlot(self, sender, ordered_coord_list): """docstring for partVirtualHelicesReorderedSlot""" new_list = self._virtual_helix_item_list decorated = [(ordered_coord_list.index(vhi.coord()), vhi)\ for vhi in self._virtual_helix_item_list] decorated.sort() new_list = [vhi for idx, vhi in decorated] ztf = not getBatch() self._setVirtualHelixItemList(new_list, zoom_to_fit=ztf) # end def def updatePreXoverItemsSlot(self, sender, virtual_helix): part = self.part() if virtual_helix == None: self.setPreXoverItemsVisible(None) elif part.areSameOrNeighbors(part.activeVirtualHelix(), virtual_helix): vhi = self.itemForVirtualHelix(virtual_helix) self.setActiveVirtualHelixItem(vhi) self.setPreXoverItemsVisible(self.activeVirtualHelixItem()) # end def ### ACCESSORS ### def activeVirtualHelixItem(self): return self._active_virtual_helix_item # end def def part(self): """Return a reference to the model's part object""" return self._model_part # end def def document(self): """Return a reference to the model's document object""" return self._model_part.document() # end def def removeVirtualHelixItem(self, virtual_helix_item): vh = virtual_helix_item.virtualHelix() self._virtual_helix_item_list.remove(virtual_helix_item) del self._virtual_helix_hash[vh.coord()] ztf = not getBatch() self._setVirtualHelixItemList(self._virtual_helix_item_list, zoom_to_fit=ztf) self._updateBoundingRect() # end def def itemForVirtualHelix(self, virtual_helix): return self._virtual_helix_hash[virtual_helix.coord()] # end def def virtualHelixBoundingRect(self): return self._vh_rect # end def def window(self): return self.parentItem().window() # end def ### PRIVATE METHODS ### def _addBasesClicked(self): part = self._model_part 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._model_part self._addBasesDialog.intValueSelected.disconnect( self._addBasesCallback) del self._addBasesDialog maxDelta = 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._model_part step_size = part.stepSize() # first find out the right edge of the part idx = part.indexOfRightmostNonemptyBase() # next snap to a multiple of stepsize idx = ceil((idx + 1) / step_size) * step_size # finally, make sure we're a minimum of step_size bases idx = util.clamp(idx, step_size, 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, new_list, zoom_to_fit=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 leftmost_extent = 0 rightmost_extent = 0 scene = self.scene() vhi_rect = None vhi_h_rect = None for vhi in new_list: vhi.setPos(0, y) if vhi_rect is None: vhi_rect = vhi.boundingRect() step = vhi_rect.height() + styles.PATH_HELIX_PADDING # end if # get the VirtualHelixHandleItem vhi_h = vhi.handle() if vhi_h.parentItem() != self._viewroot._vhi_h_selection_group: vhi_h.setParentItem(self) if vhi_h_rect is None: vhi_h_rect = vhi_h.boundingRect() vhi_h.setPos(-2 * vhi_h_rect.width(), y + (vhi_rect.height() - vhi_h_rect.height()) / 2) leftmost_extent = min(leftmost_extent, -2 * vhi_h_rect.width()) rightmost_extent = max(rightmost_extent, vhi_rect.width()) y += step self.updateXoverItems(vhi) # end for self._vh_rect = QRectF(leftmost_extent, -40, -leftmost_extent + rightmost_extent, y + 40) self._virtual_helix_item_list = new_list if zoom_to_fit: 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 add_button = self._add_bases_button rm_button = self._remove_bases_button if len(self._virtual_helix_item_list) > 0: addRect = add_button.boundingRect() rmRect = rm_button.boundingRect() x = self._vh_rect.right() y = -styles.PATH_HELIX_PADDING add_button.setPos(x, y) rm_button.setPos(x - rmRect.width(), y) add_button.show() rm_button.show() else: add_button.hide() rm_button.hide() # end def ### PUBLIC METHODS ### def setModifyState(self, bool): """Hides the modRect when modify state disabled.""" self._can_show_mod_rect = bool if bool == False: self._mod_rect.hide() def getOrderedVirtualHelixList(self): """Used for encoding.""" ret = [] for vhi in self._virtual_helix_item_list: ret.append(vhi.coord()) return ret # end def def numberOfVirtualHelices(self): return len(self._virtual_helix_item_list) # end def def reorderHelices(self, first, last, index_delta): """ Reorder helices by moving helices _pathHelixList[first:last] by a distance delta in the list. Notify each PathHelix and PathHelixHandle of its new location. """ vhi_list = self._virtual_helix_item_list helix_numbers = [vhi.number() for vhi in vhi_list] first_index = helix_numbers.index(first) last_index = helix_numbers.index(last) + 1 if index_delta < 0: # move group earlier in the list new_index = max(0, index_delta + first_index) new_list = vhi_list[0:new_index] +\ vhi_list[first_index:last_index] +\ vhi_list[new_index:first_index] +\ vhi_list[last_index:] # end if else: # move group later in list new_index = min(len(vhi_list), index_delta + last_index) new_list = vhi_list[:first_index] +\ vhi_list[last_index:new_index] +\ vhi_list[first_index:last_index] +\ vhi_list[new_index:] # end else # call the method to move the items and store the list self._setVirtualHelixItemList(new_list, zoom_to_fit=False) # end def def setActiveVirtualHelixItem(self, new_active_vhi): if new_active_vhi != self._active_virtual_helix_item: self._active_virtual_helix_item = new_active_vhi # self._model_part.setActiveVirtualHelix(new_active_vhi.virtualHelix()) # end def def setPreXoverItemsVisible(self, virtual_helix_item): """ self._pre_xover_items list references prexovers parented to other PathHelices such that only the activeHelix maintains the list of visible prexovers """ vhi = virtual_helix_item if vhi == None: if self._pre_xover_items: # clear all PreXoverItems list(map(PreXoverItem.remove, self._pre_xover_items)) self._pre_xover_items = [] return vh = vhi.virtualHelix() part_item = self part = self.part() idx = part.activeVirtualHelixIdx() # clear all PreXoverItems list(map(PreXoverItem.remove, self._pre_xover_items)) self._pre_xover_items = [] potential_xovers = part.potentialCrossoverList(vh, idx) for neighbor, index, strand_type, is_low_idx in potential_xovers: # create one half neighbor_vhi = self.itemForVirtualHelix(neighbor) pxi = PreXoverItem(vhi, neighbor_vhi, index, strand_type, is_low_idx) # add to list self._pre_xover_items.append(pxi) # create the complement pxi = PreXoverItem(neighbor_vhi, vhi, index, strand_type, is_low_idx) # add to list self._pre_xover_items.append(pxi) # end for # end def def updatePreXoverItems(self): self.setPreXoverItemsVisible(self.activeVirtualHelixItem()) # end def def updateXoverItems(self, virtual_helix_item): for item in virtual_helix_item.childItems(): if isinstance(item, XoverNode3): item.refreshXover() # end def def updateStatusBar(self, status_string): """Shows status_string in the MainWindow's status bar.""" self.window().statusBar().showMessage(status_string) ### 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._virtual_helix_item_list 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._virtual_helix_item_list 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. """ tool_method_name = self._getActiveTool().methodPrefix() + "HoverMove" if hasattr(self, tool_method_name): getattr(self, tool_method_name)(event.pos()) # end def def pencilToolHoverMove(self, pt): """Pencil the strand is possible.""" part_item = self active_tool = self._getActiveTool() if not active_tool.isFloatingXoverBegin(): temp_xover = active_tool.floatingXover() temp_xover.updateFloatingFromPartItem(self, pt)