diff options
Diffstat (limited to 'Master/texmf-dist/asymptote/GUI/xasy2asy.py')
-rwxr-xr-x | Master/texmf-dist/asymptote/GUI/xasy2asy.py | 823 |
1 files changed, 615 insertions, 208 deletions
diff --git a/Master/texmf-dist/asymptote/GUI/xasy2asy.py b/Master/texmf-dist/asymptote/GUI/xasy2asy.py index 12bccb453cf..1829ce436ba 100755 --- a/Master/texmf-dist/asymptote/GUI/xasy2asy.py +++ b/Master/texmf-dist/asymptote/GUI/xasy2asy.py @@ -9,12 +9,12 @@ # ########################################################################### -import PyQt5.QtWidgets as Qw -import PyQt5.QtGui as Qg -import PyQt5.QtCore as Qc -import PyQt5.QtSvg as Qs +import PyQt5.QtWidgets as QtWidgets +import PyQt5.QtGui as QtGui +import PyQt5.QtCore as QtCore +import PyQt5.QtSvg as QtSvg -import numpy as np +import numpy as numpy import sys import os @@ -37,8 +37,39 @@ import xasyOptions as xo import xasySvg as xs class AsymptoteEngine: - xasy=chr(4)+"\n" - + """ + Purpose: + -------- + Class that makes it possible for xasy to communicate with asy + through a background pipe. It communicates with asy through a + subprocess of an existing xasy process. + + Attributes: + ----------- + istream : input stream + ostream : output stream + keepFiles : keep communicated files + tmpdir : temporary directory + args : system call arguments to start a required subprocess + asyPath : directory path to asymptote + asyProcess : the subprocess through which xasy communicates with asy + + Virtual Methods: NULL + ---------------- + Static Methods: + --------------- NULL + Class Methods: + -------------- NULL + + Object Methods: + --------------- + start() + wait() + stop() + cleanup() + """ + + xasy=chr(4)+'\n' def __init__(self, path=None, keepFiles=DebugFlags.keepFiles, keepDefaultArgs=True): if path is None: path = xa.getArgs().asypath @@ -59,19 +90,29 @@ class AsymptoteEngine: os.set_inheritable(wa, True) self.ostream = os.fdopen(wx, 'w') self.istream = os.fdopen(ra, 'r') - + self.keepFiles = keepFiles if sys.platform[:3] == 'win': self.tmpdir = tempfile.mkdtemp(prefix='xasyData_',dir='./')+'/' else: self.tmpdir = tempfile.mkdtemp(prefix='xasyData_')+os.sep - self.args=['-xasy', '-noV', '-q', '-inpipe=' + str(rx), '-outpipe=' + str(wa), '-o', self.tmpdir] + if xa.getArgs().render: + renderDensity=xa.getArgs().render + else: + try: + renderDensity = xo.BasicConfigs.defaultOpt['renderDensity'] + except: + renderDensity = 2 + renderDensity=max(renderDensity,1) + + self.args=['-xasy', '-noV', '-q', '-outformat=', '-inpipe=' + str(rx), '-outpipe=' + str(wa), '-render='+str(renderDensity), '-o', self.tmpdir] self.asyPath = path self.asyProcess = None def start(self): + """ starts a subprocess (opens a pipe) """ try: if sys.platform[:3] == 'win': self.asyProcess = subprocess.Popen([self.asyPath] + self.args, @@ -85,6 +126,7 @@ class AsymptoteEngine: atexit.register(self.cleanup) def wait(self): + """ wait for the pipe to finish any outstanding communication """ if self.asyProcess.returncode is not None: return else: @@ -114,10 +156,12 @@ class AsymptoteEngine: return self.asyProcess.returncode is None def stop(self): + """ kill an active asyProcess and close the pipe """ if self.active: self.asyProcess.kill() def cleanup(self): + """ terminate processes and cleans up communication files """ self.stop() if self.asyProcess is not None: self.asyProcess.wait() @@ -125,11 +169,46 @@ class AsymptoteEngine: if os.path.isdir(self.tempDirName + os.sep): shutil.rmtree(self.tempDirName, ignore_errors=True) -class asyTransform(Qc.QObject): - """A python implementation of an asy transform""" +class asyTransform(QtCore.QObject): + """ + Purpose: + -------- + A python implementation of an asy transform. This class takes care of calibrating asymptote + coordinate system with the one used in PyQt to handle all existing inconsistencies. + To understand how this class works, having enough acquaintance with asymptote transform + feature is required. It is a child class of QtCore.QObject class. + + Attributes: + ----------- + t : The tuple + x, y, xx, xy, yx, yy : Coordinates corresponding to 6 entries + _deleted : Private local flag + + Virtual Methods: NULL + ---------------- + Static Methods: NULL + --------------- + + Class Methods: + -------------- + zero : Class method that returns an asyTransform object initialized with 6 zero entries + fromQTransform : Class method that converts QTransform object to asyTransform object + fromNumpyMatrix : Class method that converts transform matrix object to asyTransform object + + Object Methods: + -------------- + getRawCode : Returns the tuple entries + getCode : Returns the textual format of the asy code corresponding to the given transform + scale : Returns the scales version of the existing asyTransform + toQTransform : Converts asy transform object to QTransform object + identity : Return Identity asyTransform object + isIdentity : Check whether the asyTransform object is identity object + inverted : Applies the QTransform object's inverted method on the asyTransform object + yflip : Returns y-flipped asyTransform object + """ def __init__(self, initTuple, delete=False): - """Initialize the transform with a 6 entry tuple""" + """ Initialize the transform with a 6 entry tuple """ super().__init__() if isinstance(initTuple, (tuple, list)) and len(initTuple) == 6: self.t = initTuple @@ -151,14 +230,14 @@ class asyTransform(Qc.QObject): return asyTransform((0, 0, 0, 0, 0, 0)) @classmethod - def fromQTransform(cls, transform: Qg.QTransform): + def fromQTransform(cls, transform: QtGui.QTransform): tx, ty = transform.dx(), transform.dy() xx, xy, yx, yy = transform.m11(), transform.m21(), transform.m12(), transform.m22() return asyTransform((tx, ty, xx, xy, yx, yy)) @classmethod - def fromNumpyMatrix(cls, transform: np.ndarray): + def fromNumpyMatrix(cls, transform: numpy.ndarray): assert transform.shape == (3, 3) tx = transform[0, 2] @@ -171,8 +250,8 @@ class asyTransform(Qc.QObject): def getRawCode(self): return xu.tuple2StrWOspaces(self.t) - def getCode(self, asy2psmap=None): - """Obtain the asy code that represents this transform""" + def getCode(self, asy2psmap = None): + """ Obtain the asy code that represents this transform """ if asy2psmap is None: asy2psmap = asyTransform((0, 0, 1, 0, 0, 1)) if self.deleted: @@ -184,10 +263,10 @@ class asyTransform(Qc.QObject): return asyTransform((0, 0, s, 0, 0, s)) * self def toQTransform(self): - return Qg.QTransform(self.xx, self.yx, self.xy, self.yy, self.x, self.y) + return QtGui.QTransform(self.xx, self.yx, self.xy, self.yy, self.x, self.y) def __str__(self): - """Equivalent functionality to getCode(). It allows the expression str(asyTransform) to be meaningful.""" + """ Equivalent functionality to getCode(). It allows the expression str(asyTransform) to be meaningful """ return self.getCode() def isIdentity(self): @@ -200,7 +279,7 @@ class asyTransform(Qc.QObject): return list(self.t) == list(other.t) def __mul__(self, other): - """Define multiplication of transforms as composition.""" + """ Define multiplication of transforms as composition """ if isinstance(other, tuple): if len(other) == 6: return self * asyTransform(other) @@ -234,25 +313,88 @@ def identity(): def yflip(): return asyTransform((0, 0, 1, 0, 0, -1)) -class asyObj(Qc.QObject): - """A base class for asy objects: an item represented by asymptote code.""" +class asyObj(QtCore.QObject): + """ + Purpose: + -------- + A base class to create a Python object which contains all common + data and behaviors required during the translation of an xasy + object to its Asymptote code. + + Attributes: + ----------- + asyCode :The corresponding Asymptote code for the asyObj instance + + Virtual Methods: + ---------------- + updateCode :Must to be re-implemented + + Static Methods: NULL + -------------- + Class Methods: NULL + -------------- + + Object Methods: + --------------- + getCode :Return the Asymptote code that corresponds to the passed object + + """ + def __init__(self): - """Initialize the object""" + """ Initialize the object """ super().__init__() self.asyCode = '' - def updateCode(self, ps2asymap=identity()): - """Update the object's code: should be overriden.""" + def updateCode(self, ps2asymap = identity()): + """ Update the object's code: should be overridden """ raise NotImplementedError - def getCode(self, ps2asymap=identity()): - """Return the code describing the object""" + def getCode(self, ps2asymap = identity()): + """ Return the code describing the object """ self.updateCode(ps2asymap) return self.asyCode class asyPen(asyObj): - """A python wrapper for an asymptote pen""" + """ + Purpose: + -------- + A Python object that corresponds to an Asymptote pen type. It + extends the 'asyObj' class to include a pen object. This object + will be used to make the corresponding Asymptote pen when + an xasy object gets translated to Asymptote code. + + Attributes: + ----------- + color : The color of Path + options : The options that can be passed to the path + width : The path width + _asyengine : The Asymptote engine that will be used + _deferAsyfy : ? + + Virtual Methods: NULL + ---------------- + Static Methods: + --------------- + getColorFromQColor : + convertToQColor : + + Class Methods: + -------------- + fromAsyPen : + + Object Methods: + --------------- + asyEngine : + updateCode : + setWidth : + setColor : + setColorFromQColor : + computeColor : + tkColor : + toQPen : + """ + @staticmethod def getColorFromQColor(color): return color.redF(), color.greenF(), color.blueF() @@ -260,15 +402,16 @@ class asyPen(asyObj): @staticmethod def convertToQColor(color): r, g, b = color - return Qg.QColor.fromRgbF(r, g, b) + return QtGui.QColor.fromRgbF(r, g, b) @classmethod def fromAsyPen(cls, pen): assert isinstance(pen, cls) - return cls(asyengine=pen._asyengine, color=pen.color, width=pen.width, pen_options=pen.options) + return cls(asyengine = pen._asyengine, color = pen.color, width = pen.width, + pen_options = pen.options) - def __init__(self, asyengine=None, color=(0, 0, 0), width=0.5, pen_options=""): - """Initialize the pen""" + def __init__(self, asyengine = None, color=(0, 0, 0), width = 0.5, pen_options = ""): + """ Initialize the pen """ asyObj.__init__(self) self.color = (0, 0, 0) self.options = pen_options @@ -288,8 +431,8 @@ class asyPen(asyObj): def asyEngine(self, value): self._asyengine = value - def updateCode(self, asy2psmap=identity()): - """Generate the pen's code""" + def updateCode(self, asy2psmap = identity()): + """ Generate the pen's code """ if self._deferAsyfy: self.computeColor() self.asyCode = 'rgb({:g},{:g},{:g})+{:s}'.format(self.color[0], self.color[1], self.color[2], str(self.width)) @@ -297,12 +440,12 @@ class asyPen(asyObj): self.asyCode = self.asyCode + '+' + self.options def setWidth(self, newWidth): - """Set the pen's width""" + """ Set the pen's width """ self.width = newWidth self.updateCode() def setColor(self, color): - """Set the pen's color""" + """ Set the pen's color """ if isinstance(color, tuple) and len(color) == 3: self.color = color else: @@ -313,13 +456,12 @@ class asyPen(asyObj): self.setColor(asyPen.getColorFromQColor(color)) def computeColor(self): - """Find out the color of an arbitrary asymptote pen.""" + """ Find out the color of an arbitrary Asymptote pen """ assert isinstance(self.asyEngine, AsymptoteEngine) assert self.asyEngine.active fout = self.asyEngine.ostream fin = self.asyEngine.istream - fout.write("pen p=" + self.getCode() + ';\n') fout.write("write(_outpipe,colorspace(p),newl);\n") fout.write("write(_outpipe,colors(p));\n") @@ -347,15 +489,10 @@ class asyPen(asyObj): self.color = (r, g, b) self._deferAsyfy = False - def tkColor(self): - """Return the tk version of the pen's color""" - self.computeColor() - return '#{}'.format("".join(["{:02x}".format(min(int(256 * a), 255)) for a in self.color])) - def toQPen(self): if self._deferAsyfy: self.computeColor() - newPen = Qg.QPen() + newPen = QtGui.QPen() newPen.setColor(asyPen.convertToQColor(self.color)) newPen.setWidthF(self.width) @@ -363,10 +500,34 @@ class asyPen(asyObj): class asyPath(asyObj): - """A python wrapper for an asymptote path""" + """ + Purpose: + -------- + A Python object that corresponds to an Asymptote path type. It + extends the 'asyObj' class to include a path object. This object + will be used to make the corresponding Asymptote path object when + an xasy object gets translated to its Asymptote code. + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + def __init__(self, asyengine: AsymptoteEngine=None, forceCurve=False): - """Initialize the path to be an empty path: a path with no nodes, control points, or links.""" + """ Initialize the path to be an empty path: a path with no nodes, control points, or links """ super().__init__() self.nodeSet = [] self.linkSet = [] @@ -374,12 +535,14 @@ class asyPath(asyObj): self.controlSet = [] self.computed = False self.asyengine = asyengine + self.fill = False @classmethod def fromPath(cls, oldPath): newObj = asyPath(None) newObj.nodeSet = copy.copy(oldPath.nodeSet) newObj.linkSet = copy.copy(oldPath.linkSet) + newObj.fill = copy.copy(oldPath.fill) newObj.controlSet = copy.deepcopy(oldPath.controlSet) newObj.computed = oldPath.computed newObj.asyengine = oldPath.asyengine @@ -406,6 +569,7 @@ class asyPath(asyObj): def setInfo(self, path): self.nodeSet = copy.copy(path.nodeSet) self.linkSet = copy.copy(path.linkSet) + self.fill = copy.copy(path.fill) self.controlSet = copy.deepcopy(path.controlSet) self.computed = path.computed @@ -417,12 +581,12 @@ class asyPath(asyObj): def isDrawable(self): return len(self.nodeSet) >= 2 - def toQPainterPath(self) -> Qg.QPainterPath: + def toQPainterPath(self) -> QtGui.QPainterPath: return self.toQPainterPathCurve() if self.containsCurve else self.toQPainterPathLine() def toQPainterPathLine(self): baseX, baseY = self.nodeSet[0] - painterPath = Qg.QPainterPath(Qc.QPointF(baseX, baseY)) + painterPath = QtGui.QPainterPath(QtCore.QPointF(baseX, baseY)) for pointIndex in range(1, len(self.nodeSet)): node = self.nodeSet[pointIndex] @@ -439,43 +603,43 @@ class asyPath(asyObj): self.computeControls() baseX, baseY = self.nodeSet[0] - painterPath = Qg.QPainterPath(Qc.QPointF(baseX, baseY)) + painterPath = QtGui.QPainterPath(QtCore.QPointF(baseX, baseY)) for pointIndex in range(1, len(self.nodeSet)): node = self.nodeSet[pointIndex] if self.nodeSet[pointIndex] == 'cycle': node = self.nodeSet[0] - endPoint = Qc.QPointF(node[0], node[1]) - ctrlPoint1 = Qc.QPointF(self.controlSet[pointIndex-1][0][0], self.controlSet[pointIndex-1][0][1]) - ctrlPoint2 = Qc.QPointF(self.controlSet[pointIndex-1][1][0], self.controlSet[pointIndex-1][1][1]) + endPoint = QtCore.QPointF(node[0], node[1]) + ctrlPoint1 = QtCore.QPointF(self.controlSet[pointIndex-1][0][0], self.controlSet[pointIndex-1][0][1]) + ctrlPoint2 = QtCore.QPointF(self.controlSet[pointIndex-1][1][0], self.controlSet[pointIndex-1][1][1]) painterPath.cubicTo(ctrlPoint1, ctrlPoint2, endPoint) return painterPath def initFromNodeList(self, nodeSet, linkSet): - """Initialize the path from a set of nodes and link types, "--", "..", or "::" """ + """ Initialize the path from a set of nodes and link types, '--', '..', or '::' """ if len(nodeSet) > 0: self.nodeSet = nodeSet[:] self.linkSet = linkSet[:] self.computed = False def initFromControls(self, nodeSet, controlSet): - """Initialize the path from nodes and control points""" + """ Initialize the path from nodes and control points """ self.controlSet = controlSet[:] self.nodeSet = nodeSet[:] self.computed = True def makeNodeStr(self, node): - """Represent a node as a string""" + """ Represent a node as a string """ if node == 'cycle': return node else: # if really want to, disable this rounding - # shouldn't be to much of a problem since 10e-6 is quite small... + # shouldn't be to much of a problem since 10e-6 is quite small... return '({:.6g},{:.6g})'.format(node[0], node[1]) def updateCode(self, ps2asymap=identity()): - """Generate the code describing the path""" + """ Generate the code describing the path """ # currently at postscript. Convert to asy asy2psmap = ps2asymap.inverted() with io.StringIO() as rawAsyCode: @@ -499,28 +663,28 @@ class asyPath(asyObj): return '..' in self.linkSet or self.forceCurve def getNode(self, index): - """Return the requested node""" + """ Return the requested node """ return self.nodeSet[index] def getLink(self, index): - """Return the requested link""" + """ Return the requested link """ return self.linkSet[index] def setNode(self, index, newNode): - """Set a node to a new position""" + """ Set a node to a new position """ self.nodeSet[index] = newNode def moveNode(self, index, offset): - """Translate a node""" + """ Translate a node """ if self.nodeSet[index] != "cycle": self.nodeSet[index] = (self.nodeSet[index][0] + offset[0], self.nodeSet[index][1] + offset[1]) def setLink(self, index, ltype): - """Change the specified link""" + """ Change the specified link """ self.linkSet[index] = ltype def addNode(self, point, ltype): - """Add a node to the end of a path""" + """ Add a node to the end of a path """ self.nodeSet.append(point) if len(self.nodeSet) != 1: self.linkSet.append(ltype) @@ -528,14 +692,14 @@ class asyPath(asyObj): self.computeControls() def insertNode(self, index, point, ltype=".."): - """Insert a node, and its corresponding link, at the given index""" + """ Insert a node, and its corresponding link, at the given index """ self.nodeSet.insert(index, point) self.linkSet.insert(index, ltype) if self.computed: self.computeControls() def setControl(self, index, position): - """Set a control point to a new position""" + """ Set a control point to a new position """ self.controlSet[index] = position def popNode(self): @@ -545,11 +709,11 @@ class asyPath(asyObj): self.linkSet.pop() def moveControl(self, index, offset): - """Translate a control point""" + """ Translate a control point """ self.controlSet[index] = (self.controlSet[index][0] + offset[0], self.controlSet[index][1] + offset[1]) def computeControls(self): - """Evaluate the code of the path to obtain its control points""" + """ Evaluate the code of the path to obtain its control points """ # For now, if no asymptote process is given spawns a new one. # Only happens if asyengine is None. if self.asyengine is not None: @@ -595,9 +759,33 @@ class asyPath(asyObj): asy.stop() class asyLabel(asyObj): - """A python wrapper for an asy label""" + """ + Purpose: + -------- + A Python object that corresponds to an asymptote label + type. It extends the 'asyObj' class to include a label + object. This object will be used to make the corresponding + Asymptote label object when an xasy object gets translated to its + asymptote code. + + Attributes: + ----------- + + Virtual Methods: + ---------------- - def __init__(self, text="", location=(0, 0), pen=None, align=None, fontSize:int=None): + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + + def __init__(self, text = "", location = (0, 0), pen = None, align = None, fontSize:int = None): """Initialize the label with the given test, location, and pen""" asyObj.__init__(self) self.align = align @@ -610,8 +798,8 @@ class asyLabel(asyObj): self.text = text self.location = location - def updateCode(self, asy2psmap=identity()): - """Generate the code describing the label""" + def updateCode(self, asy2psmap = identity()): + """ Generate the code describing the label """ newLoc = asy2psmap.inverted() * self.location locStr = xu.tuple2StrWOspaces(newLoc) self.asyCode = 'Label("{0}",{1},p={2}{4},align={3})'.format(self.text, locStr, self.pen.getCode(), self.align, @@ -624,22 +812,45 @@ class asyLabel(asyObj): return '' def setText(self, text): - """Set the label's text""" + """ Set the label's text """ self.text = text self.updateCode() def setPen(self, pen): - """Set the label's pen""" + """ Set the label's pen """ self.pen = pen self.updateCode() def moveTo(self, newl): - """Translate the label's location""" + """ Translate the label's location """ self.location = newl class asyImage: - """A structure containing an image and its format, bbox, and IDTag""" + """ + Purpose: + -------- + A Python object that is a container for an image coming from + Asymptote that is populated with the format, bounding box, and + IDTag, Asymptote key. + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + def __init__(self, image, format, bbox, transfKey=None, keyIndex=0): self.image = image self.format = format @@ -648,15 +859,38 @@ class asyImage: self.key = transfKey self.keyIndex = keyIndex -class xasyItem(Qc.QObject): - """A base class for items in the xasy GUI""" +class xasyItem(QtCore.QObject): + """ + Purpose: + -------- + A base class for any xasy object that can be drawn in PyQt. This class takes + care of all common behaviors available on any xasy item as well as all common + actions that can be done or applied to every xasy item. + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + mapString = 'xmap' setKeyFormatStr = string.Template('$map("{:s}",{:s});').substitute(map=mapString) setKeyAloneFormatStr = string.Template('$map("{:s}");').substitute(map=mapString) resizeComment="// Resize to initial xasy transform" asySize="" def __init__(self, canvas=None, asyengine=None): - """Initialize the item to an empty item""" + """ Initialize the item to an empty item """ super().__init__() self.transfKeymap = {} # the new keymap. # should be a dictionary to a list... @@ -672,18 +906,17 @@ class xasyItem(Qc.QObject): self.setKeyed = True self.unsetKeys = set() self.userKeys = set() - self.lineOffset = 0 self.imageHandleQueue = queue.Queue() - def updateCode(self, ps2asymap=identity()): - """Update the item's code: to be overriden""" + def updateCode(self, ps2asymap = identity()): + """ Update the item's code: to be overridden """ with io.StringIO() as rawCode: transfCode = self.getTransformCode() objCode = self.getObjectCode() rawCode.write(transfCode) rawCode.write(objCode) - self.asyCode = rawCode.getvalue() + self.asyCode = rawCode.getvalue() return len(transfCode.splitlines()), len(objCode.splitlines()) @@ -695,34 +928,32 @@ class xasyItem(Qc.QObject): def asyengine(self, value): self._asyengine = value - def getCode(self, ps2asymap=identity()): - """Return the code describing the item""" + def getCode(self, ps2asymap = identity()): + """ Return the code describing the item """ self.updateCode(ps2asymap) return self.asyCode - def getTransformCode(self, asy2psmap=identity()): + def getTransformCode(self, asy2psmap = identity()): raise NotImplementedError - def getObjectCode(self, asy2psmap=identity()): + def getObjectCode(self, asy2psmap = identity()): raise NotImplementedError def generateDrawObjects(self): raise NotImplementedError - def handleImageReception(self, file, fileformat, bbox, count, key=None, localCount=0, containsClip=False): - """Receive an image from an asy deconstruction. It replaces the default n asyProcess.""" + def handleImageReception(self, file, fileformat, bbox, count, key = None, localCount = 0, containsClip = False): + """ Receive an image from an asy deconstruction. It replaces the default n asyProcess """ # image = Image.open(file).transpose(Image.FLIP_TOP_BOTTOM) - if fileformat == 'png': - image = Qg.QImage(file) - elif fileformat == 'svg': + if fileformat == 'svg': if containsClip: image = xs.SvgObject(self.asyengine.tempDirName+file) else: - image = Qs.QSvgRenderer(file) + image = QtSvg.QSvgRenderer(file) assert image.isValid() else: - raise Exception('Format not supported!') - self.imageList.append(asyImage(image, fileformat, bbox, transfKey=key, keyIndex=localCount)) + raise Exception('Format {} not supported!'.format(fileformat)) + self.imageList.append(asyImage(image, fileformat, bbox, transfKey = key, keyIndex = localCount)) if self.onCanvas is not None: # self.imageList[-1].iqt = ImageTk.PhotoImage(image) currImage = self.imageList[-1] @@ -733,7 +964,7 @@ class xasyItem(Qc.QObject): currImage.performCanvasTransform = False # handle this case if transform is not in the map yet. - # if deleted - set transform to 0, 0, 0, 0, 0 + # if deleted - set transform to (0,0,0,0,0,0) transfExists = key in self.transfKeymap.keys() if transfExists: transfExists = localCount <= len(self.transfKeymap[key]) - 1 @@ -745,7 +976,7 @@ class xasyItem(Qc.QObject): if (not transfExists) or validKey: currImage.IDTag = str(file) newDrawObj = DrawObject(currImage.iqt, self.onCanvas['canvas'], transform=identity(), - btmRightanchor=Qc.QPointF(bbox[0], bbox[2]), drawOrder=-1, key=key, + btmRightanchor=QtCore.QPointF(bbox[0], bbox[2]), drawOrder=-1, key=key, parentObj=self, keyIndex=localCount) newDrawObj.setBoundingBoxPs(bbox) newDrawObj.setParent(self) @@ -757,7 +988,8 @@ class xasyItem(Qc.QObject): else: self.drawObjectsMap[key].append(newDrawObj) return containsClip - def asyfy(self, force=False): + + def asyfy(self, force = False): if self.asyengine is None: return 1 if self.asyfied and not force: @@ -772,7 +1004,7 @@ class xasyItem(Qc.QObject): self.userKeys.clear() self.imageHandleQueue = queue.Queue() - worker = threading.Thread(target=self.asyfyThread, args=[]) + worker = threading.Thread(target = self.asyfyThread, args = []) worker.start() item = self.imageHandleQueue.get() cwd=os.getcwd(); @@ -797,22 +1029,25 @@ class xasyItem(Qc.QObject): worker.join() def asyfyThread(self): - """Convert the item to a list of images by deconstructing this item's code""" + """ + Convert the item to a list of images by deconstructing this item's code + """ assert self.asyengine.active fout = self.asyengine.ostream fin = self.asyengine.istream - self.lineOffset = len(self.getTransformCode().splitlines()) + self.maxKey=0 fout.write("reset\n") fout.flush(); for line in self.getCode().splitlines(): - if DebugFlags.printDeconstTranscript: - print('fout:', line) + if DebugFlags.printAsyTranscript: + print(line) fout.write(line+"\n") fout.write(self.asySize) - fout.write("deconstruct();\n") + + fout.write('deconstruct();\n') fout.write('write(_outpipe,yscale(-1)*currentpicture.calculateTransform(),endl);\n') fout.write(self.asyengine.xasy) fout.flush() @@ -826,37 +1061,44 @@ class xasyItem(Qc.QObject): for i in range(len(imageInfos)): box, key, localCount, useClip = imageInfos[i] l, b, r, t = [float(a) for a in box.split()] - name = "_{:d}.{:s}".format(i, fileformat) + name = '_{:d}.{:s}'.format(1+i, fileformat) self.imageHandleQueue.put((name, fileformat, (l, -t, r, -b), i, key, localCount, useClip)) # key first, box second. - # if key is "Done" + # if key is 'Done' raw_text = fin.readline() - text = "" + text = '' if DebugFlags.printDeconstTranscript: + print(self.asyengine.tmpdir) print(raw_text.strip()) - # template=AsyTempDir+"%d_%d.%s" - fileformat = 'svg' + fileformat = 'svg' # Output format - while raw_text != "Done\n" and raw_text != "Error\n": + while raw_text != 'Done\n' and raw_text != 'Error\n': # print(raw_text) text = fin.readline() # the actual bounding box. # print('TESTING:', text) keydata = raw_text.strip().replace('KEY=', '', 1) # key clipflag = keydata[-1] == '1' + deleted = keydata[-1] == '2' userkey = keydata[-2] == '1' keydata = keydata[:-3] if not userkey: - self.unsetKeys.add(keydata) # the line and column to replace. + self.unsetKeys.add(keydata) # the line and column to replace. else: + if keydata.isdigit(): + self.maxKey=max(self.maxKey,int(keydata)) self.userKeys.add(keydata) - + # print(line, col) + if deleted: + raw_text = fin.readline() + continue + if keydata not in keyCounts.keys(): keyCounts[keydata] = 0 @@ -873,23 +1115,45 @@ class xasyItem(Qc.QObject): n += 1 - if raw_text != "Error\n": - if text == "Error\n": - self.imageHandleQueue.put(("ERROR", fin.readline())) + if raw_text != 'Error\n': + if text == 'Error\n': + self.imageHandleQueue.put(('ERROR', fin.readline())) else: render() self.asy2psmap = asyTransform(xu.listize(fin.readline().rstrip(),float)) else: - self.asy2psmap = identity() + self.asy2psmap = yflip() self.imageHandleQueue.put((None,)) self.asyfied = True class xasyDrawnItem(xasyItem): - """A base class for GUI items was drawn by the user. It combines a path, a pen, and a transform.""" + """ + Purpose: + -------- + A base class dedicated to any xasy item that is drawn on GUI. Every object of this class + will correspond to a particular drawn xasy item on GUI, which contains all its particular + data. + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- - def __init__(self, path, engine, pen=None, transform=identity(), key=None): - """Initialize the item with a path, pen, and transform""" + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + + def __init__(self, path, engine, pen = None, transform = identity(), key = None): + """ Initialize the item with a path, pen, and transform """ super().__init__(canvas=None, asyengine=engine) if pen is None: pen = asyPen() @@ -921,7 +1185,9 @@ class xasyDrawnItem(xasyItem): raise NotImplementedError def appendPoint(self, point, link=None): - """Append a point to the path. If the path is cyclic, add this point before the 'cycle' node.""" + """ Append a point to the path. If the path is cyclic, add this point before the 'cycle' + node + """ if self.path.nodeSet[-1] == 'cycle': self.path.nodeSet[-1] = point self.path.nodeSet.append('cycle') @@ -933,12 +1199,14 @@ class xasyDrawnItem(xasyItem): self.path.linkSet.append(link) def clearTransform(self): - """Reset the item's transform""" + """ Reset the item's transform """ self.transform = [identity()] self.asyfied = False def removeLastPoint(self): - """Remove the last point in the path. If the path is cyclic, remove the node before the 'cycle' node.""" + """ Remove the last point in the path. If the path is cyclic, remove the node before the 'cycle' + node + """ if self.path.nodeSet[-1] == 'cycle': del self.path.nodeSet[-2] else: @@ -948,7 +1216,9 @@ class xasyDrawnItem(xasyItem): self.asyfied = False def setLastPoint(self, point): - """Modify the last point in the path. If the path is cyclic, modify the node before the 'cycle' node.""" + """ Modify the last point in the path. If the path is cyclic, modify the node before the 'cycle' + node + """ if self.path.nodeSet[-1] == 'cycle': self.path.nodeSet[-2] = point else: @@ -958,13 +1228,38 @@ class xasyDrawnItem(xasyItem): class xasyShape(xasyDrawnItem): - """An outlined shape drawn on the GUI""" + """ An outlined shape drawn on the GUI """ + """ + Purpose: + -------- + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + + def __init__(self, path, asyengine, pen=None, transform=identity()): """Initialize the shape with a path, pen, and transform""" super().__init__(path=path, engine=asyengine, pen=pen, transform=transform) def getObjectCode(self, asy2psmap=identity()): - return 'draw(KEY="{0}",{1},{2});'.format(self.transfKey, self.path.getCode(asy2psmap), self.pen.getCode())+'\n\n' + if self.path.fill: + return 'fill(KEY="{0}",{1},{2});'.format(self.transfKey, self.path.getCode(asy2psmap), self.pen.getCode())+'\n\n' + else: + return 'draw(KEY="{0}",{1},{2});'.format(self.transfKey, self.path.getCode(asy2psmap), self.pen.getCode())+'\n\n' def getTransformCode(self, asy2psmap=identity()): transf = self.transfKeymap[self.transfKey][0] @@ -982,63 +1277,121 @@ class xasyShape(xasyDrawnItem): key=self.transfKey) newObj.originalObj = self newObj.setParent(self) + newObj.fill=self.path.fill return [newObj] def __str__(self): - """Create a string describing this shape""" + """ Create a string describing this shape """ return "xasyShape code:{:s}".format("\n\t".join(self.getCode().splitlines())) + def swapFill(self): + self.path.fill = not self.path.fill + + def copy(self): + return type(self)(self.path,self._asyengine,self.pen) + class xasyFilledShape(xasyShape): - """A filled shape drawn on the GUI""" + """ A filled shape drawn on the GUI """ - def __init__(self, path, asyengine, pen=None, transform=identity()): - """Initialize this shape with a path, pen, and transform""" + """ + Purpose: + -------- + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + + def __init__(self, path, asyengine, pen = None, transform = identity()): + """ Initialize this shape with a path, pen, and transform """ if path.nodeSet[-1] != 'cycle': raise Exception("Filled paths must be cyclic") super().__init__(path, asyengine, pen, transform) + self.path.fill=True def getObjectCode(self, asy2psmap=identity()): - return 'fill(KEY="{0}",{1},{2});'.format(self.transfKey, self.path.getCode(asy2psmap), self.pen.getCode())+'\n\n' + if self.path.fill: + return 'fill(KEY="{0}",{1},{2});'.format(self.transfKey, self.path.getCode(asy2psmap), self.pen.getCode())+'\n\n' + else: + return 'draw(KEY="{0}",{1},{2});'.format(self.transfKey, self.path.getCode(asy2psmap), self.pen.getCode())+'\n\n' - def generateDrawObjects(self, forceUpdate=False): + def generateDrawObjects(self, forceUpdate = False): if self.path.containsCurve: self.path.computeControls() - newObj = DrawObject(self.path.toQPainterPath(), None, drawOrder=0, transform=self.transfKeymap[self.transfKey][0], - pen=self.pen, key=self.transfKey, fill=True) + newObj = DrawObject(self.path.toQPainterPath(), None, drawOrder = 0, transform = self.transfKeymap[self.transfKey][0], + pen = self.pen, key = self.transfKey, fill = True) newObj.originalObj = self newObj.setParent(self) + newObj.fill=self.path.fill return [newObj] def __str__(self): - """Return a string describing this shape""" + """ Return a string describing this shape """ return "xasyFilledShape code:{:s}".format("\n\t".join(self.getCode().splitlines())) + def swapFill(self): + self.path.fill = not self.path.fill + class xasyText(xasyItem): - """Text created by the GUI""" + """ Text created by the GUI """ + + """ + Purpose: + -------- + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- - def __init__(self, text, location, asyengine, pen=None, transform=yflip(), key=None, align=None, fontsize:int=None): - """Initialize this item with text, a location, pen, and transform""" - super().__init__(asyengine=asyengine) + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + + def __init__(self, text, location, asyengine, pen = None, transform = yflip(), key = None, align = None, fontsize:int = None): + """ Initialize this item with text, a location, pen, and transform """ + super().__init__(asyengine = asyengine) if pen is None: - pen = asyPen(asyengine=asyengine) + pen = asyPen(asyengine = asyengine) if pen.asyEngine is None: pen.asyEngine = asyengine - self.label = asyLabel(text, location, pen, align, fontSize=fontsize) + self.label = asyLabel(text, location, pen, align, fontSize = fontsize) # self.transform = [transform] self.transfKey = key self.transfKeymap = {self.transfKey: [transform]} self.asyfied = False self.onCanvas = None - - def setKey(self, newKey=None): + self.pen = pen + + def setKey(self, newKey = None): transform = self.transfKeymap[self.transfKey][0] self.transfKey = newKey self.transfKeymap = {self.transfKey: [transform]} - def getTransformCode(self, asy2psmap=yflip()): + def getTransformCode(self, asy2psmap = yflip()): transf = self.transfKeymap[self.transfKey][0] if transf == yflip(): # return xasyItem.setKeyAloneFormatStr.format(self.transfKey) @@ -1046,10 +1399,10 @@ class xasyText(xasyItem): else: return xasyItem.setKeyFormatStr.format(self.transfKey, transf.getCode(asy2psmap))+"\n" - def getObjectCode(self, asy2psmap=yflip()): + def getObjectCode(self, asy2psmap = yflip()): return 'label(KEY="{0}",{1});'.format(self.transfKey, self.label.getCode(asy2psmap))+'\n' - def generateDrawObjects(self, forceUpdate=False): + def generateDrawObjects(self, forceUpdate = False): self.asyfy(forceUpdate) return self.drawObjects @@ -1060,12 +1413,36 @@ class xasyText(xasyItem): def __str__(self): return "xasyText code:{:s}".format("\n\t".join(self.getCode().splitlines())) + def copy(self): + return type(self)(self.label.text,self.label.location,self._asyengine) + class xasyScript(xasyItem): - """A set of images create from asymptote code. It is always deconstructed.""" + """ A set of images create from asymptote code. It is always deconstructed """ + + """ + Purpose: + -------- + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ def __init__(self, canvas, engine, script="", transforms=None, transfKeyMap=None): - """Initialize this script item""" + """ Initialize this script item """ super().__init__(canvas, asyengine=engine) if transfKeyMap is not None: self.transfKeymap = transfKeyMap @@ -1080,7 +1457,7 @@ class xasyScript(xasyItem): self.updatedPrefix = True def clearTransform(self): - """Reset the transforms for each of the deconstructed images""" + """ Reset the transforms for each of the deconstructed images """ # self.transform = [identity()] * len(self.imageList) keyCount = {} @@ -1093,14 +1470,6 @@ class xasyScript(xasyItem): for key in keyCount: self.transfKeymap[key] = [identity()] * keyCount[key] - def getMaxKeyCounter(self): - maxCounter = -1 - for key in self.transfKeymap: - testNum = re.match(r'^x(\d+)$', key) - if testNum is not None: - maxCounter = max(maxCounter, int(testNum.group(1))) - return maxCounter + 1 - def getTransformCode(self, asy2psmap=identity()): with io.StringIO() as rawAsyCode: if self.transfKeymap: @@ -1109,21 +1478,22 @@ class xasyScript(xasyItem): writeval = list(reversed(val)) # need to map all transforms in a list if there is any non-identity - # unfortunately, have to check all transformations in the list. - while not all(checktransf == identity() for checktransf in writeval) and writeval: + # unfortunately, have to check all transformations in the list. + while not all((checktransf == identity() and not checktransf.deleted) for checktransf in writeval) and writeval: transf = writeval.pop() if transf.deleted: - rawAsyCode.write(xasyItem.setKeyFormatStr.format(key, transf.getCode(asy2psmap)) + '\n//') - if transf == identity() and not transf.deleted: - rawAsyCode.write(xasyItem.setKeyAloneFormatStr.format(key)) - else: rawAsyCode.write(xasyItem.setKeyFormatStr.format(key, transf.getCode(asy2psmap))) + else: + if transf == identity(): + rawAsyCode.write(xasyItem.setKeyAloneFormatStr.format(key)) + else: + rawAsyCode.write(xasyItem.setKeyFormatStr.format(key, transf.getCode(asy2psmap))) rawAsyCode.write('\n') result = rawAsyCode.getvalue() return result def findNonIdKeys(self): - return {key for key in self.transfKeymap if not all(transf == identity() for transf in self.transfKeymap[key]) } + return {key for key in self.transfKeymap if not all(not transf.deleted and transf == identity() for transf in self.transfKeymap[key]) } def getObjectCode(self, asy2psmap=identity()): numeric=r'([-+]?(?:(?:\d*\.\d+)|(?:\d+\.?)))' @@ -1144,7 +1514,7 @@ class xasyScript(xasyItem): return self.updatedCode def setScript(self, script): - """Sets the content of the script item.""" + """ Sets the content of the script item """ self.script = script self.updateCode() @@ -1155,7 +1525,7 @@ class xasyScript(xasyItem): def getReplacedKeysCode(self, key2replace: set=None) -> str: keylist = {} prefix = '' - + key2replaceSet = self.unsetKeys if key2replace is None else \ self.unsetKeys & key2replace @@ -1180,9 +1550,8 @@ class xasyScript(xasyItem): raw_code_lines = self.script.splitlines() with io.StringIO() as raw_str: - for i_0 in range(len(raw_code_lines)): - i = i_0 + self.lineOffset - curr_str = raw_code_lines[i_0] + for i in range(len(raw_code_lines)): + curr_str = raw_code_lines[i] if i + 1 in keylist.keys(): # this case, we have a key. with io.StringIO() as raw_line: @@ -1205,11 +1574,13 @@ class xasyScript(xasyItem): baseCounter += 1 return newKey - def asyfy(self, keyOnly=False): - """Generate the list of images described by this object and adjust the length of the transform list.""" + def asyfy(self, keyOnly = False): + """ Generate the list of images described by this object and adjust the length of the + transform list + """ super().asyfy() - # Id --> Transf --> asy-fied --> Transf + # Id --> Transf --> asyfied --> Transf # Transf should keep the original, raw transformation # but for all new drawn objects - assign Id as transform. @@ -1246,7 +1617,7 @@ class xasyScript(xasyItem): else: self.key2imagemap[im.key].append(im) - + for key in keyCount: if key not in self.transfKeymap.keys(): @@ -1258,7 +1629,7 @@ class xasyScript(xasyItem): # while len(self.transfKeymap[key]) > keyCount[key]: # self.transfKeymap[key].pop() - # change of basis + # change of basis for keylist in self.transfKeymap.values(): for i in range(len(keylist)): if keylist[i] != identity(): @@ -1272,17 +1643,41 @@ class xasyScript(xasyItem): return self.drawObjects def __str__(self): - """Return a string describing this script""" + """ Return a string describing this script """ retVal = "xasyScript\n\tTransforms:\n" for xform in self.transform: retVal += "\t" + str(xform) + "\n" - retVal += "\tCode Ommitted" + retVal += "\tCode Omitted" return retVal -class DrawObject(Qc.QObject): - def __init__(self, drawObject, mainCanvas=None, transform=identity(), btmRightanchor=Qc.QPointF(0, 0), - drawOrder=(-1, -1), pen=None, key=None, parentObj=None, fill=False, keyIndex=0): +class DrawObject(QtCore.QObject): + """ + Purpose: + -------- + The main Python class to draw an object with the help of PyQt graphical library. + Every instance of the class is + + + Attributes: + ----------- + + Virtual Methods: + ---------------- + + Static Methods: + --------------- + + Class Methods: + -------------- + + Object Methods: + --------------- + + """ + + def __init__(self, drawObject, mainCanvas = None, transform = identity(), btmRightanchor = QtCore.QPointF(0, 0), + drawOrder = (-1, -1), pen = None, key = None, parentObj = None, fill = False, keyIndex = 0): super().__init__() self.drawObject = drawObject self.mainCanvas = mainCanvas @@ -1302,8 +1697,8 @@ class DrawObject(Qc.QObject): self.fill = fill def getInteriorScrTransform(self, transform): - """Generates the transform with Interior transform applied beforehand.""" - if isinstance(transform, Qg.QTransform): + """ Generates the transform with Interior transform applied beforehand """ + if isinstance(transform, QtGui.QTransform): transform = asyTransform.fromQTransform(transform) return self.transform * transform * self.baseTransform.inverted() @@ -1317,25 +1712,36 @@ class DrawObject(Qc.QObject): def setBoundingBoxPs(self, bbox): l, b, r, t = bbox - self.explicitBoundingBox = Qc.QRectF(Qc.QPointF(l, b), Qc.QPointF(r, t)) - # self.explicitBoundingBox = Qc.QRectF(0, 0, 100, 100) + self.explicitBoundingBox = QtCore.QRectF(QtCore.QPointF(l, b), QtCore.QPointF(r, t)) + # self.explicitBoundingBox = QtCore.QRectF(0, 0, 100, 100) @property def boundingBox(self): if self.explicitBoundingBox is not None: - testBbox = self.explicitBoundingBox + tempItem = self.baseTransform.toQTransform().mapRect(self.explicitBoundingBox) + testBbox = self.getScreenTransform().toQTransform().mapRect(tempItem) + elif isinstance(self.drawObject, QtGui.QPainterPath): + tempItem = self.baseTransform.toQTransform().map(self.drawObject) + testBbox = self.getScreenTransform().toQTransform().map(tempItem).boundingRect() else: - if isinstance(self.drawObject, Qg.QImage): - testBbox = self.drawObject.rect() - testBbox.moveTo(self.btmRightAnchor.toPoint()) - elif isinstance(self.drawObject, Qg.QPainterPath): - testBbox = self.baseTransform.toQTransform().mapRect(self.drawObject.boundingRect()) - else: - raise TypeError('drawObject is not a valid type!') - pointList = [self.getScreenTransform().toQTransform().map(point) for point in [ - testBbox.topLeft(), testBbox.topRight(), testBbox.bottomLeft(), testBbox.bottomRight() - ]] - return Qg.QPolygonF(pointList).boundingRect() + raise TypeError('drawObject is not a valid type!') + + if self.pen is not None: + lineWidth = self.pen.width + const = lineWidth/2 + bl = QtCore.QPointF(-const, const) + br = QtCore.QPointF(const, const) + tl = QtCore.QPointF(-const, -const) + tr = QtCore.QPointF(const, -const) + + pointList = [testBbox.topLeft(), testBbox.topRight(), testBbox.bottomLeft(), testBbox.bottomRight() + ] + + else: + pointList = [testBbox.topLeft(), testBbox.topRight(), testBbox.bottomLeft(), testBbox.bottomRight() + ] + + return QtGui.QPolygonF(pointList).boundingRect() @property def localBoundingBox(self): @@ -1345,22 +1751,25 @@ class DrawObject(Qc.QObject): def getScreenTransform(self): scrTransf = self.baseTransform.toQTransform().inverted()[0] * self.pTransform.toQTransform() + # print(asyTransform.fromQTransform(scrTransf).t) return asyTransform.fromQTransform(scrTransf) - def draw(self, additionalTransformation=None, applyReverse=False, canvas: Qg.QPainter=None, dpi=300): + def draw(self, additionalTransformation = None, applyReverse = False, canvas: QtGui.QPainter = None, dpi = 300): if canvas is None: canvas = self.mainCanvas if additionalTransformation is None: - additionalTransformation = Qg.QTransform() - + additionalTransformation = QtGui.QTransform() + assert canvas.isActive() canvas.save() if self.pen: - oldPen = Qg.QPen(canvas.pen()) - canvas.setPen(self.pen.toQPen()) + oldPen = QtGui.QPen(canvas.pen()) + localPen = self.pen.toQPen() + # localPen.setCosmetic(True) + canvas.setPen(localPen) #this fixes the object but not the box else: - oldPen = Qg.QPen() + oldPen = QtGui.QPen() if not applyReverse: canvas.setTransform(additionalTransformation, True) @@ -1371,11 +1780,9 @@ class DrawObject(Qc.QObject): canvas.setTransform(self.baseTransform.toQTransform().inverted()[0], True) - if isinstance(self.drawObject, Qg.QImage): - canvas.drawImage(self.explicitBoundingBox, self.drawObject) - elif isinstance(self.drawObject, xs.SvgObject): + if isinstance(self.drawObject, xs.SvgObject): threshold = 1.44 - + if self.cachedDPI is None or self.cachedSvgImg is None \ or dpi > self.maxDPI*threshold: self.cachedDPI = dpi @@ -1383,15 +1790,15 @@ class DrawObject(Qc.QObject): self.cachedSvgImg = self.drawObject.render(dpi) canvas.drawImage(self.explicitBoundingBox, self.cachedSvgImg) - elif isinstance(self.drawObject, Qs.QSvgRenderer): + elif isinstance(self.drawObject, QtSvg.QSvgRenderer): self.drawObject.render(canvas, self.explicitBoundingBox) - elif isinstance(self.drawObject, Qg.QPainterPath): + elif isinstance(self.drawObject, QtGui.QPainterPath): path = self.baseTransform.toQTransform().map(self.drawObject) if self.fill: if self.pen: brush = self.pen.toQPen().brush() else: - brush = Qg.QBrush() + brush = QtGui.QBrush() canvas.fillPath(path, brush) else: canvas.drawPath(path) @@ -1400,14 +1807,14 @@ class DrawObject(Qc.QObject): canvas.setPen(oldPen) canvas.restore() - def collide(self, coords, canvasCoordinates=True): - # modify these values to grow/shrink the fuzz. + def collide(self, coords, canvasCoordinates = True): + # modify these values to grow/shrink the fuzz. fuzzTolerance = 1 marginGrowth = 1 leftMargin = marginGrowth if self.boundingBox.width() < fuzzTolerance else 0 topMargin = marginGrowth if self.boundingBox.height() < fuzzTolerance else 0 - newMargin = Qc.QMarginsF(leftMargin, topMargin, leftMargin, topMargin) + newMargin = QtCore.QMarginsF(leftMargin, topMargin, leftMargin, topMargin) return self.boundingBox.marginsAdded(newMargin).contains(coords) def getID(self): |