summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/asymptote/GUI/xasy2asy.py
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/asymptote/GUI/xasy2asy.py')
-rwxr-xr-xMaster/texmf-dist/asymptote/GUI/xasy2asy.py823
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):