##############################################################################
# ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
#
# Copyright (c) 2001-2009 Thanasis Stamos, August 23, 2009
# URL: http://thancad.sourceforge.net
# e-mail: cyberthanasis@excite.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details (www.gnu.org/licenses/gpl.html).
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
"""\
ThanCad 0.0.9 "DoesSomething": 2dimensional CAD with raster support for engineers.
This module defines a ThanCad drawing, which contains elements, has layers,
viewports, etc."
"""
import weakref
from math import fabs
import p_ggen
from p_gmath import thanNear3
import thanfonts, thandefs
from thanlayer import ThanLayerTree,THANNAME
from thanvar import ThanId,ThanRectCoorTransf
from thanelem import ThanElement
from thanline import ThanLine
from thantext import ThanText
from thanpoint import ThanPoint
from thanroad import ThanRoad
class ThanDoundo:
"Implements the undo/redo scheme."
def __init__(self):
self.__doundo = []
self.__i = 0
def thanAdd(self, text, redoFun, redoArgs, undoFun, undoArgs):
"A new command (or task) to undo/redo."
del self.__doundo[self.__i:]
self.__doundo.append((text, redoFun, redoArgs, undoFun, undoArgs))
self.__i = len(self.__doundo)
def thanRedo(self, proj):
"Redoes previously 'undone' command/task."
assert self.__i < len(self.__doundo), "Nothing left to redo"
redoFun, redoArgs = self.__doundo[self.__i][1:3]
redoFun(proj, *redoArgs)
self.__i += 1
def thanUndo(self, proj):
"Undoes previous command/task."
assert self.__i > 0, "Nothing left to undo"
undoFun, undoArgs = self.__doundo[self.__i-1][3:5]
undoFun(proj, *undoArgs)
self.__i -= 1
def thanRedotry(self):
"Finds out if redo is possible; returns command's text if possible."
if self.__i >= len(self.__doundo): return None # Nothing left to redo
return self.__doundo[self.__i][0]
def thanUndotry(self):
"Finds out if undo is possible; returns command's text if possible."
if self.__i <= 0: return None # Nothing left to undo
return self.__doundo[self.__i-1][0]
############################################################################
############################################################################
class ThanTagel(dict):
"A dictionary with predefined/readonly items."
rdlist = frozenset(("e0", "edrag", "enull"))
__slots__ = ()
def __init__(self, *args, **kw):
"Set predefined items, if not already in args/kw."
dict.__init__(self, *args, **kw)
temp = ThanElement() # Null element: This is to ensure that temporary elemenmts..
# ..do not lead to KeyError, and do all operations passively
for key in self.rdlist:
self.setdefault(key, temp)
def __delitem__(self, key):
"Do not delete readonly items."
if key in self.rdlist: return
dict.__delitem__(self, key)
def __setitem__(self, key, val):
"Do not delete readonly items."
if key in self.rdlist: raise KeyError, "Readonly item: key: %s" % key
dict.__setitem__(self, key, val)
############################################################################
############################################################################
class ThanDrawing:
"Represents a whole drawing."
#===========================================================================
def __init__ (self):
"Creates a new drawing instance."
self.viewPort = [-10.0, -10.0, 100.0, 100.0]
self.thanAreaIterated = (None, None, None, None) # No element -> no limit in regen
self.xMinAct = self.yMinAct = self.xMaxAct = self.yMaxAct = None
self.thanLayerTree = ThanLayerTree()
t = thandefs.ThanTstyle("standard", thanfonts.thanFonts["thanprime1"])
self.thanTstyles = {t.thanName: t}
self.thanLtypes = {}
self.__idTag = ThanId(prefix="E")
self.thanTagel = ThanTagel()
self.thanUnits = thandefs.ThanUnits()
self.thanDoundo = ThanDoundo()
self.__variables()
self.thanEdus = weakref.WeakKeyDictionary()
self.__modified = False
self.thanPrevLine = weakref.ref(p_ggen.Struct()) # An invalid weak reference
self.thanDtm = None
self.thanTri = None # Triangulation
def __variables(self):
"Set ThanCad variables."
self.thanVar = {}
n = self.thanVar["dimensionality"] = 3 # Number of dimensions a node has
self.thanVar["elevation"] = [0.0]*n # Elevation - limited n-dimensional support
self.thanVar["thickness"] = [0.0]*n # Thickness - limited n-dimensional support
self.thanVar["insbase"] = [0.0]*n # Insertion point of the drawing (as block)
self.thanVar["imageframe"]= ["on"] # If false, the bounding rectangles of images are not displayed
self.thanVar["useroffsetdistance"] = 1.0 # Default distance for the offset command
def __getstate__(self):
odict = self.__dict__.copy()
del odict["thanPrevLine"] # Do not save weak reference (it can be built again)
odict["thanEdus"] = odict["thanEdus"].keys()
return odict
def __setstate__(self, odict):
self.__dict__.update(odict)
self.thanPrevLine = weakref.ref(p_ggen.Struct()) # Recreate an invalid weak reference
c = self.thanEdus
self.thanEdus = weakref.WeakKeyDictionary()
for e in c: self.thanEdus[e] = True
def thanDestroy(self):
"Destroy circular references."
self.thanLayerTree.thanDestroy()
del self.thanDoundo
def thanAfterPickle(self):
"""Initialise a drawing after it is unpickled (load from a file).
The only thing that has to be done is a regen, which will redefine
thanAreaIterated and xyminmaxact.
"""
pass
def thanElementAdd(self, elem):
"Adds an element to the drawing."
cl = self.thanLayerTree.thanCur
tag = self.__idTag.new()
if elem.thanTkCompound > 1:
elem.thanTags = tag, cl.thanTag
else:
elem.thanTags = tag, cl.thanTag, "nocomp" # Not a compound element
self.__elementAddHouse(elem, cl)
def __elementAddHouse(self, elem, cl):
"""Do housekeeping for the new element.
Since the new element is drawn (if not frozen) thanAreaIterated is unchanged.
"""
self.thanTagel[elem.thanTags[0]] = elem
cl.thanQuad.add(elem)
if not cl.thanAtts["frozen"].thanVal:
if self.xMinAct == None:
self.xMinAct = elem.thanXymm[0]
self.yMinAct = elem.thanXymm[1]
self.xMaxAct = elem.thanXymm[2]
self.yMaxAct = elem.thanXymm[3]
else:
if elem.thanXymm[0] < self.xMinAct: self.xMinAct = elem.thanXymm[0]
if elem.thanXymm[1] < self.yMinAct: self.yMinAct = elem.thanXymm[1]
if elem.thanXymm[2] > self.xMaxAct: self.xMaxAct = elem.thanXymm[2]
if elem.thanXymm[3] > self.yMaxAct: self.yMaxAct = elem.thanXymm[3]
self.__modified = True
#===========================================================================
def thanTkDraw(self, than, lays="all"):
"""Draws the active elements (inside the viewport) into a gui window.
Active are the elements which do not belong to a layer which is 'off'.
This function checks each element of ThanDrawing and if it, or part of it,
is inside the viewport (which is just a rectangle), draws it into 'win'.
Thus, in general, 'win' has less elements than ThanDrawing.
'win' has all the elements that are, at least partialy, inside viewport.
If we assume that all elements of ThanDrawing are partialy inside
viewport, then 'win' has actually all ThanDrawing's elements, even
though viewport is smaller than the viewport that fully covers all
the elements.
It is also possible that 'win' does not have all ThanDrawing's elements,
but all elements that are covered by a bigger rectangle than viewport.
This bigger rectangle is called 'thanAreaIterated'. 'thanAreaIterated' is
the rectangle which is smaller (i.e. it does not cover) any part of the
elements not drawn.
'thanAreaIterated' is quite useful because if the user pans or zooms
the drawing, the new viewport may be inside 'thanAreaIterated'. It is
thus not necessary to redraw (actualy regenerate) the elements, since
these elements are already in 'win'. All that is needed is that 'win'
pans or zooms its elements, which is usually much faster.
This feature is so useful that when thanDraw is called, it draws the
elements of a rectangle 5 (or 25) times bigger than the viewport, so
that pan and zoom can be done faster.
Another concept, completely unrelated to the above, is the smallest
rectangle 'xyMinMaxAct', which covers all the active elements of
ThanDrawing. This is needed when the user wants to zoom all.
xyMinMaxAct are the x and y min and max of the active elements
(elements of the visible layers). When a drawing is read, 'xyMinMaxAct'
is computed. When an element is added, 'xyMinMaxAct' is updated.
When a layer is turned on, all its elements are checked, to see if
they are inside the viewport, and xyMinMaxAct is updated.
However, when an element is deleted, and its rectangle share at
least one edje with xyMinMaxAct, then xyMinMaxAct may no longer be valid.
It will always cover all the active elements, but it may not be the
smaller rectangle which covers all the elements.
Since thanDraw iterates through all visible elements, xyMinMaxAct
is also computed.
When a user zooms all, and xyMinMaxAct is within thanAreaIterated,
thanDraw() is not called. However, it should be possible to ask the gui
window for its version of xyMinMaxAct, and pass it here.
"""
self.thanAreaIterated = (1, 1, -1, -1) # Set as invalid in case user aborts
self.xMinAct = self.yMinAct = self.xMaxAct = self.yMaxAct = None # Set as invalid in case user aborts
xymm = self.viewPort
dx = xymm[2] - xymm[0]
dy = xymm[3] - xymm[1]
xymm = [xymm[0]-2*dx, xymm[1]-2*dy, xymm[2]+2*dx, xymm[3]+2*dy] # xymm is a new list now: not viewPort.thanXymm
#------Initialise min,max with first element
if lays == "all": lays = self.thanLayerTree.dilay.values()
lays = [(lay.thanAtts["draworder"].thanVal, lay) for lay in lays]
lays.sort()
lays = [lay for i,lay in lays]
for lay in lays:
if lay.thanAtts["frozen"].thanVal: continue
for e in lay.thanQuad:
self.xMinAct = e.thanXymm[0]
self.yMinAct = e.thanXymm[1]
self.xMaxAct = e.thanXymm[2]
self.yMaxAct = e.thanXymm[3]
break
else: continue
break
else:
print "no elements found in active layers"
self.thanAreaIterated = (None, None, None, None) # No limit in all directions
self.xMinAct = self.yMinAct = self.xMaxAct = self.yMaxAct = None
return # Drawing has no elements in active layers
#-------Now iterate through all elements of active layers
for lay in lays:
if lay.thanAtts["frozen"].thanVal: continue
lay.thanTkSet(than, self.thanTstyles)
for e in lay.thanQuad:
if e.thanXymm[0] < self.xMinAct: self.xMinAct = e.thanXymm[0]
if e.thanXymm[1] < self.yMinAct: self.yMinAct = e.thanXymm[1]
if e.thanXymm[2] > self.xMaxAct: self.xMaxAct = e.thanXymm[2]
if e.thanXymm[3] > self.yMaxAct: self.yMaxAct = e.thanXymm[3]
if e.thanInbox(xymm): e.thanTkDraw(than)
#-------Compute limits of thanAreaIterated
if xymm[0] <= self.xMinAct: xymm[0] = None
if xymm[1] <= self.yMinAct: xymm[1] = None
if xymm[2] >= self.xMaxAct: xymm[2] = None
if xymm[3] >= self.yMaxAct: xymm[3] = None
self.thanAreaIterated = tuple(xymm)
self.thanLayerTree.thanCur.thanTkSet(than, self.thanTstyles)
def thanTkHiwin(self, than):
"Lengthens (a little) very small elements so that they become visible."
seen = set()
tagel = self.thanTagel
dc = than.dc
for item in dc.find_all():
tags = dc.gettags(item)
if not tags: continue # Sentinel elements
titem = tags[0]
if titem[0] != "E": continue # not a ThanCad Element
if titem in seen: continue
print "titem=", titem
seen.add(titem)
el = tagel[titem]
el.thanTkHiwin(than)
#===========================================================================
def thanMoveSel(self, elems, dc):
"Moves the selected elements."
if all(dc1==0.0 for dc1 in dc): return
for e in elems:
e.thanMove(dc)
if e.thanXymm[0] < self.xMinAct: self.xMinAct = e.thanXymm[0]
if e.thanXymm[1] < self.yMinAct: self.yMinAct = e.thanXymm[1]
if e.thanXymm[2] > self.xMaxAct: self.xMaxAct = e.thanXymm[2]
if e.thanXymm[3] > self.yMaxAct: self.yMaxAct = e.thanXymm[3]
self.thanTouch()
def thanCopySel(self, elems, dc):
"Copies the selected elements with offset."
lt = self.thanLayerTree
thanCur1 = lt.thanCur
dilay = lt.dilay
copelems = []
for e in elems:
lt.thanCur = dilay[e.thanTags[1]]
e1 = e.thanClone()
e1.thanMove(dc)
self.thanElementAdd(e1)
copelems.append(e1)
lt.thanCur = thanCur1
self.thanTouch()
return copelems
def thanRotateSel(self, elems, cc, phi):
"Rotates the selected elements."
if phi == 0.0: return
ThanElement.thanRotateSet(cc, phi)
for e in elems:
e.thanRotate()
if e.thanXymm[0] < self.xMinAct: self.xMinAct = e.thanXymm[0]
if e.thanXymm[1] < self.yMinAct: self.yMinAct = e.thanXymm[1]
if e.thanXymm[2] > self.xMaxAct: self.xMaxAct = e.thanXymm[2]
if e.thanXymm[3] > self.yMaxAct: self.yMaxAct = e.thanXymm[3]
self.thanTouch()
def thanMirrorSel(self, elems, c1, t):
"Mirrors the selected elements."
ThanElement.thanMirrorSet(c1, t)
for e in elems:
e.thanMirror()
if e.thanXymm[0] < self.xMinAct: self.xMinAct = e.thanXymm[0]
if e.thanXymm[1] < self.yMinAct: self.yMinAct = e.thanXymm[1]
if e.thanXymm[2] > self.xMaxAct: self.xMaxAct = e.thanXymm[2]
if e.thanXymm[3] > self.yMaxAct: self.yMaxAct = e.thanXymm[3]
self.thanTouch()
def thanReverseSel(self, elems):
"Reverses the orientation of the direction of lines."
for e in elems:
assert isinstance(e, ThanLine), "thanReverseSel work only for lines"
e.cp.reverse()
self.thanTouch()
def thanExplodeSel(self, proj, elems):
"Explodes the selected elements."
than = proj[2].than
lt = proj[1].thanLayerTree
thanCur1 = lt.thanCur
dilay = lt.dilay
for e in elems:
if isinstance(e, ThanLine):
lay = dilay[e.thanTags[1]]
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
lay.thanQuad.remove(e)
for i in xrange(1, len(e.cp)):
e1 = ThanLine()
e1.thanSet(e.cp[i-1:i+1])
if e1.thanIsNormal():
self.thanElementAdd(e1)
e1.thanTkDraw(than)
lay = thanCur1
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
self.thanTouch()
def thanReplaceSel(self, proj, e, e1, e2):
"Replace e with e1 and e2, if e1 and q2 are not both None."
if e1 == None and e2 == None:
proj[2].thanCom.thanAppend(T["Element can not be deleted; use 'ERASE'."], "can")
return
than = proj[2].than
lt = proj[1].thanLayerTree
thanCur1 = lt.thanCur
dilay = lt.dilay
lay = dilay[e.thanTags[1]]
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
lay.thanQuad.remove(e)
if e1 != None: self.thanElementAdd(e1); e1.thanTkDraw(than)
if e2 != None: self.thanElementAdd(e2); e2.thanTkDraw(than)
lay = thanCur1
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
self.thanTouch()
def thanOffsetSel(self, proj, e, dis, removeoriginal=False):
"Offsets an element by distance dis."
than = proj[2].than
lt = proj[1].thanLayerTree
thanCur1 = lt.thanCur
dilay = lt.dilay
e1 = e.thanOffset(dis)
lay = dilay[e.thanTags[1]]
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
if removeoriginal: lay.thanQuad.remove(e)
if e1 != None: self.thanElementAdd(e1); e1.thanTkDraw(than)
lay = thanCur1
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
self.thanTouch()
def thanJoinSel(self, proj, elems):
"Joins consecutive lines to 1 bigger line."
than = proj[2].than
lt = proj[1].thanLayerTree
thanCur1 = lt.thanCur
dilay = lt.dilay
lines = set(e for e in elems if isinstance(e, ThanLine))
joinedlines = 0
while len(lines) > 0:
e = lines.pop()
cbeg = e.cp[0]
cend = e.cp[-1]
for e1 in lines:
if thanNear3(cbeg, e1.cp[0]):
lay = dilay[e1.thanTags[1]]
lay.thanQuad.remove(e1)
lines.remove(e1)
e1.cp.reverse(); e1.cp.pop()
e1.cp.extend(e.cp)
e.cp[:] = e1.cp
lines.add(e)
break
elif thanNear3(cbeg, e1.cp[-1]):
lay = dilay[e1.thanTags[1]]
lay.thanQuad.remove(e1)
lines.remove(e1)
e1.cp.pop()
e1.cp.extend(e.cp)
e.cp[:] = e1.cp
lines.add(e)
break
elif thanNear3(cend, e1.cp[0]):
lay = dilay[e1.thanTags[1]]
lay.thanQuad.remove(e1)
lines.remove(e1)
e1.cp.pop(0)
e.cp.extend(e1.cp)
lines.add(e)
break
elif thanNear3(cend, e1.cp[-1]):
lay = dilay[e1.thanTags[1]]
lay.thanQuad.remove(e1)
lines.remove(e1)
e1.cp.reverse(); e1.cp.pop(0)
e.cp.extend(e1.cp)
lines.add(e)
break
else:
joinedlines += 1
lay = dilay[e.thanTags[1]]
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
e.thanTkDraw(than)
lay = thanCur1
if lay != lt.thanCur:
lay.thanTkSet(than, proj[1].thanTstyles)
lt.thanCur = lay
self.thanTouch()
return joinedlines
def thanScaleSel(self, elems, cc, fact):
"Scales the selected elements."
if fact == 1.0: return
for e in elems:
e.thanScale(cc, fact)
if e.thanXymm[0] < self.xMinAct: self.xMinAct = e.thanXymm[0]
if e.thanXymm[1] < self.yMinAct: self.yMinAct = e.thanXymm[1]
if e.thanXymm[2] > self.xMaxAct: self.xMaxAct = e.thanXymm[2]
if e.thanXymm[3] > self.yMaxAct: self.yMaxAct = e.thanXymm[3]
self.thanTouch()
#===========================================================================
def thanDelSel(self, elems):
"Deletes elements from the drawing as simply as possible."
taglay = self.thanLayerTree.dilay
for e in elems:
lay = taglay[e.thanTags[1]]
lay.thanQuad.remove(e)
del self.thanTagel[e.thanTags[0]]
self.thanTouch()
def thanDelSelMany(self, elems):
"""Deletes a large number of elements efficiently from the drawing.
This should be faster if the number of elements to delete are,
say, over 10000. Again, this is not certain without actual test.
"""
taglay = self.thanLayerTree.dilay
elay = {}
for e in elems:
elay.setdefault(e.thanTags[1], set()).add(e)
del self.thanTagel[e.thanTags[0]]
for tlay,eset in elay.iteritems():
lay = taglay[tlay]
lay.thanQuad -= eset
self.thanTouch()
def thanElementRestore(self, elems, proj):
"""Restores previously deleted elements as simply as possible.
It is assumed that the elements' structure is already in legal state, and that
the layer they belong to is also in legal state.
FIXME: maybe it will be faster to sort by layer first, and call lay.thanTkSet
less times.
"""
taglay = self.thanLayerTree.dilay
than = proj[2].than
for e in elems:
lay = taglay[e.thanTags[1]]
lay.thanTkSet(than, self.thanTstyles)
self.__elementAddHouse(e, lay)
if not lay.thanAtts["frozen"].thanVal: e.thanTkDraw(than)
self.thanTouch()
self.thanLayerTree.thanCur.thanTkSet(than, self.thanTstyles)
def thanElementDelete(self, elems, proj):
"Deletes elements from the drawing and canvas as simply as possible, leaving structures intact."
taglay = self.thanLayerTree.dilay
dc = proj[2].thanCanvas
for e in elems:
lay = taglay[e.thanTags[1]]
lay.thanQuad.remove(e)
dc.delete(e.thanTags[0])
self.thanTouch()
#===========================================================================
def thanIsModified(self):
"Returns true if drawing has been modified since last save."
return self.__modified
def thanResetModified(self):
"Informs that drawing has just been saved or read and so it is unmodified."
self.__modified = 0
def thanTouch(self):
"Make the drawing as it it were modified."
self.__modified = True
#===========================================================================
def thanExpDxf(self, dxf, fout):
"Exports all the elements of the drawing to dxf file."
dxf.thanDxfPlots1(fout, vars=self.__calcVars())
dxf.thanDxfTableDef (' ', 0)
dxf.thanDxfTableDef('LAYER', 1)
self.thanLayerTree.thanExpDxf(dxf) # export layers
dxf.thanDxfTableDef('ENTITIES', 1)
for lay in self.thanLayerTree.dilay.itervalues():
dxf.thanDxfSetLayer(lay.thanGetPathname("__"))
for e in lay.thanQuad:
e.thanExpDxf(dxf)
def __calcVars(self):
"Determine the values of dxf variables."
lays = [self.thanLayerTree.thanRoot]
leaflayers = []
while len(lays) > 0:
lay = lays.pop(0)
if len(lay.thanChildren) > 0: lays.extend(lay.thanChildren)
else: leaflayers.append(lay)
vars = []
nfillon = 0
for lay in leaflayers:
ia = lay.thanAtts["fill"]
if ia.thanVal: nfillon += 1
print "fill.thanVal=", ia.thanVal
print "nfillon =", nfillon
if nfillon == len(leaflayers): vars.append(("$FILLMODE", 70, 1))
else: vars.append(("$FILLMODE", 70, 0))
return vars
def thanExpSyk(self, fSyk):
"Exports all the linear elements of the drawing to syk file."
than = p_ggen.Struct("ThanCad .syk file and options container")
than.write = fSyk.write
for lay in self.thanLayerTree.dilay.itervalues():
than.layname = lay.thanGetPathname("__")
for e in lay.thanQuad:
e.thanExpSyk(than)
def thanExpBrk(self, fSyk):
"Exports all the linear elements of the drawing to brk file."
than = p_ggen.Struct("ThanCad .brk file and options container")
than.write = fSyk.write
than.ibr = 0
than.form = "THC%07d%15.3f%15.3f%15.3f\n"
for lay in self.thanLayerTree.dilay.itervalues():
than.layname = lay.thanGetPathname("__")
for e in lay.thanQuad:
e.thanExpBrk(than)
def thanPlotPdf(self, scale):
"Plots all elements of the drawing to pdf file."
import pyx
than = p_ggen.Struct("ThanCad .pdf file and options container")
than.dc = pyx.canvas.canvas()
than.ct = ThanRectCoorTransf()
x1 = y1 = 0.0
x2 = y2 = 1.0
than.ct.set((x1, y1, x2, y2), (0, 0, (x2-x1)*scale, (y2-y1)*scale))
for lay in self.thanLayerTree.dilay.itervalues():
lay.thanPdfSet(than, self.thanTstyles)
for e in lay.thanQuad:
e.thanPlotPdf(than)
return than
def thanExpPil(self, filpath, mode, width, height, drwin):
"Exports the circle to a PIL raster image."
import p_ggen, Image, ImageDraw, ImageFont, ImageFilter, thanvar
from thandefs.thanatt import ThanAttCol
than = p_ggen.Struct("ThanCad PIL image and options container")
page = 19.5, 29.5
# dpi = 300.0 #120.0
# imsize = [int(p*dpi/2.54+0.5) for p in page]
# than.mode = "RGB"
than.mode = mode
imsize = width, height
dpi = imsize[1]/(page[1]/2.54)
print "thanExpPil(): dpi=", dpi
if than.mode == "1" or than.mode == "L": bcol = 255
else: bcol = 255,255,255
than.im = Image.new(than.mode, imsize, bcol)
ib, ih = than.im.size
than.viewPort = x1, y1, x2, y2 = self.__roundCenter(self.viewPort, (0, ih, ib, 0))
than.dc = ImageDraw.Draw(than.im)
than.ct = thanvar.ThanRectCoorTransf()
than.ct.set((x1, y1, x2, y2), (0, ih, ib, 0))
than.imageFrameOn = self.thanVar["imageframe"]
# than.fill = ThanAttCol("red").thanVal
# than.outline = ThanAttCol("yellow").thanVal
# than.width = int((9+1)/2) # for the bugged version of ThanLine.thanExpPil()
# than.width = 0
# f = ImageFont.load_path("/home/a12/work/tcadtree.23/grhelv-b-10.pil")
# than.font = f
lays = self.thanLayerTree.dilay.values()
lays = [(lay.thanAtts["draworder"].thanVal, lay) for lay in lays]
lays.sort()
lays = [lay for i,lay in lays]
for lay in lays:
if lay.thanAtts["frozen"].thanVal: continue
lay.thanPilSet(than, dpi, self.thanTstyles)
than.width = int(than.rwidth + 0.5)
than.widthline = int((than.width+1)/2) # Get around PIL bug for line width
i2 = int(than.width/2)
i1 = -i2
if i2-i1+1 > than.width: i1 += 1
than.widtharc = i1, i2+1 # Simulate widths in ars, circles
assert than.width == than.widtharc[1]-than.widtharc[0]
for e in lay.thanQuad:
e.thanExpPil(than)
del than.dc
x1, y1, x2, y2 = self.viewPort
ix1, iy1 = than.ct.global2Locali(x1, y2) # PIL need left,upper and ..
ix2, iy2 = than.ct.global2Locali(x2, y1) # ..right,lower
box = ix1, iy1, ix2, iy2
im1 = than.im.crop(box) # Crop lines etc. outside the user defined window.
# im1 = im1.filter(ImageFilter.SMOOTH)
than.im = Image.new(than.mode, imsize, bcol)
than.im.paste(im1, box)
than.im.save(filpath)
def __roundCenter(self, w, pixPort):
"Rounds an abstract window w, so that it fits exactly to the actual (GuiDependent) window."
xa, ya, xb, yb = pixPort
wpi = abs(xb - xa)
hpi = abs(yb - ya)
wun = w[2] - w[0]
hun = w[3] - w[1]
per = 6
if wpi < 10*per or hpi < 10*per: per = 0
per = 0
sx = float(wpi - per) / wun
sy = float(hpi - per) / hun
if sy < sx: sx = sy
dx = (wpi / sx - wun) * 0.5
dy = (hpi / sx - hun) * 0.5
return w[0]-dx, w[1]-dy, w[0]+wun+dx, w[1]+hun+dy
#MODULE LEVEL CODE. IT IS EXECUTED ONLY ONCE
if __name__ == "__main__":
print __doc__
|