##############################################################################
# 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 the text element.
"""
from math import sin,cos,fabs,pi,atan2
from itertools import izip,islice
from thanelem import ThanElement
from thanvar import Canc
from p_gmath import PI2
from thantrans import T
try: import pyx
except ImportError: pass
class ThanText(ThanElement):
"A simple line of horizontal text."
thanTkCompound = 100 # The number of Tkinter objects that make the element. 100=compound (lines etc.)
def thanSet(self, text, c1, size, theta):
"Sets the attributes of the text."
self.size = self.h1 = float(size) # Temporary ..
self.w1 = len(text)*self.size # ..dimensions
self.setBoundBoxRect(c1[0], c1[1], self.w1, self.h1, theta)
self.tempSize = True # In some (all?) GUIs, the GUI must be active in order..
# ..to determine the dimensions of the text
self.text = text
self.c1 = list(c1)
self.theta = theta % PI2 # Radians assumed
# self.thanTags = () # thanTags is initialised in ThanElement
def thanIsNormal(self):
"Returns False if the text is degenerate (it is blank)."
return len(self.text.strip()) > 0 # Degenerate text (if blank, it can not be selected)
def thanClone(self):
"Makes a geometric clone of itself."
el = ThanText()
el.thanSet(self.text, self.c1, self.size, self.theta)
return el
def thanRotate(self):
"Rotates the element within XY-plane with predefined angle and rotation angle."
self.c1 = self.thanRotateXy(self.c1)
self.theta += self.rotPhi
self.theta %= PI2
self.setBoundBoxRect(self.c1[0], self.c1[1], self.w1, self.h1, self.theta)
def thanMirror(self):
"Mirrors the element within XY-plane with predefined point and unit vector."
cbig = list(self.c1)
cbig[0] += self.w1*cos(self.theta)
cbig[1] += self.w1*sin(self.theta)
cbig = self.thanMirrorXy(cbig)
self.c1 = self.thanMirrorXy(self.c1)
self.theta = atan2(cbig[1]-self.c1[1], cbig[0]-self.c1[0]) % PI2
self.setBoundBoxRect(self.c1[0], self.c1[1], self.w1, self.h1, self.theta)
def thanScale(self, cs, scale):
"Scales the element in n-space with defined scale and center of scale."
self.c1 = [cs1+(cc1-cs1)*scale for (cc1,cs1) in izip(self.c1, cs)]
self.size *= scale
self.w1 *= scale
self.h1 *= scale
cscs = [cs[0], cs[1], cs[0], cs[1]]
self.thanXymm[:] = [cs1+(cc1-cs1)*scale for (cc1,cs1) in izip(self.thanXymm, cscs)]
def thanMove(self, dc):
"Moves the element with defined n-dimensional distance."
self.c1 = [cc1+dd1 for (cc1,dd1) in izip(self.c1, dc)]
dcdc = [dc[0], dc[1], dc[0], dc[1]]
self.thanXymm[:] = [cc1+dd1 for (cc1,dd1) in izip(self.thanXymm, dcdc)]
def thanOsnap(self, proj, otypes, ccu, eother, cori):
"Return a point of type otype nearest to xcu, ycu."
if "ena" not in otypes: return None # Object snap is disabled
if "end" in otypes:
return fabs(self.c1[0]-ccu[0])+fabs(self.c1[1]-ccu[1]), "end", self.c1
return None
def thanLength(self):
"""Returns the "length" of the text."""
return max((self.w1, self.h1))
def thanArea(self):
"""Returns the area of the polyline."""
return self.w1*self.h1
def thanTkGet(self, proj):
"Gets the attributes of the text interactively from a window."
un = proj[1].thanUnits
size = proj[2].thanGudGetPosFloat(T["Text Size: "], 10.00)
if size == Canc: return Canc # text cancelled
st = "%s(%s): " % (T["Rotation angle"], un.anglunit)
theta = proj[2].thanGudGetFloat(st, 0.00)
if theta == Canc: return Canc # text cancelled
c1 = proj[2].thanGudGetPoint(T["Text location: "])
if c1 == Canc: return Canc # Text cancelled
text = proj[2].thanGudGetText(T["Text to be drawn: "], "")
if text == Canc: return Canc # text cancelled
self.thanSet(text, c1, size, un.unit2rad(theta))
return True # Text OK
def thanTkDraw(self, than):
"Draws rotated text in ThanCad line font."
if self.tempSize: # The GUI is active: time to compute the dimension of the text accurately
self.w1, self.h1 = than.font.thanCalcSizexy(self.text, self.size)
self.tempSize = False
self.setBoundBoxRect(self.c1[0], self.c1[1], self.w1, self.h1, self.theta)
# FIXME: When user changes font of a layer, then all texts of this layer should have tempsize=True
x1, y1 = than.ct.global2Local(self.c1[0], self.c1[1])
w, h = than.ct.global2LocalRel(self.w1, -self.h1) # Ensure h>0 (local y-axis is positive downwards)
if h < 4: # Size too small to be seen; draw rectangle instead
tags = self.thanTags + ("nocomp",)
h = -h
cost = cos(-self.theta) # Opposite y-axis changes the sign of the angle
sint = sin(-self.theta)
xb = x1 + w*cost
yb = y1 + w*sint
g2l = than.ct.global2Local
wpList = [ (x1, y1),
(xb, yb),
(xb - h*sint, yb + h*cost),
(x1 - h*sint, y1 + h*cost),
]
than.dc.create_polygon(wpList, outline=than.outline, fill="", tags=tags)
else: # Draw text - ThanFont does not distinguish between theta==0 and theta!=zero
than.font.thanTkPaint(than, x1, y1, h, self.text, self.theta, self.thanTags)
def thanTkHiwin(self, than):
"Highlights with a (small) window very small elements so that they become visible."
self.thanTkHiwinDo(than, self.thanLength(), self.c1)
def thanExpDxf(self, fDxf):
"Exports the text to dxf file."
fDxf.thanDxfPlotSymbol3(self.c1[0], self.c1[1], self.c1[2], self.size, self.text, self.theta/pi*180.0)
def thanExpPilother(self, than):
"Exports the text to a PIL raster image."
x1, y1 = than.ct.global2Locali(self.x1, self.y1)
than.dc.text((x1, y1), self.text, font=than.font, fill=than.outline)
def thanExpPil(self, than):
"Exports rotated text (in ThanCad line font) into a PIL raster image."
xa, ya = than.ct.global2Local(self.c1[0], self.c1[1])
w, h = than.ct.global2LocalRel(self.w1, -self.h1) # Ensure h>0 (local y-axis is positive downwards)
if h < 1: return # Size too small to be seen; draw rectangle instead
than.thanFont.thanPilPaint(than, xa, ya, h, self.text, self.theta)
def thanPlotPdf(self, than):
"Plots rotated text (in ThanCad line font) into a PDF file."
xa, ya = than.ct.global2Local(self.c1[0], self.c1[1])
w, h = than.ct.global2LocalRel(self.w1, self.h1) # Ensure h>0 (local y-axis is positive upwards)
if h < 0.05: return # Size too small to be seen; draw rectangle instead
lines = than.thanFont.than2lines(xa, ya, h, self.text, self.theta, mirrory=True)
lineto = pyx.path.lineto
moveto = pyx.path.moveto
closepath = pyx.path.closepath
for cp in lines:
if len(cp) < 2: continue
if len(cp) == 2:
ca = cp[0][0], cp[0][1]
cb = cp[1][0], cp[1][1]
p = pyx.path.line(ca[0], ca[1], cb[0], cb[1])
else:
xy1 = [lineto(c1[0], c1[1]) for c1 in islice(cp, 1, None)]
xy1.insert(0, moveto(cp[0][0], cp[0][1]))
if cp[0] == cp[-1]: xy1[-1] = closepath()
p = pyx.path.path(*xy1)
print p
than.dc.stroke(p)
def thanList(self, than):
"Shows information about the text element."
than.writecom("Element: TEXT")
than.write(" Layer: % s\n" % than.laypath)
than.write("Length: %s Area: %s\n" % (than.strdis(self.thanLength()), than.strdis(self.thanArea())))
t = ('Value: "%s"' % self.text,
"Insertion point: %s" % than.strcoo(self.c1),
"size: %s angle: %s\n"% (than.strdis(self.size), than.strang(self.theta)),
)
than.write("\n".join(t))
if __name__ == "__main__":
print __doc__
|