##############################################################################
# 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 circular arc element.
"""
from math import sqrt,atan2,tan,pi,fabs,cos,sin,hypot
from itertools import izip
import bisect
import Tkinter
from p_ggen import prg
from p_gmath import PI2,thanintersect,thanNearx
import thanintall
from thanvar import Canc
from thanelem import ThanElement
from thantrans import T
############################################################################
############################################################################
class ThanArc(ThanElement):
"A circular arc."
def thanSet (self, cc, r, theta1, theta2):
"Sets the attributes of the circle."
self.setBoundBox([cc[0]-r, cc[1]-r, cc[0]+r, cc[1]+r])
self.cc = list(cc)
self.r = r
self.theta1 = theta1 % PI2 # radians assumed
self.theta2 = theta2 % PI2 # radians assumed
if self.theta2 < self.theta1: self.theta2 += PI2 # Ensure theta2>=theta1
# self.thanTags = () # thanTags is initialised in ThanElement
def thanIsNormal(self):
"Returns False if the arc is degenerate (either zero radius or identical thetas)."
if thanNearx(self.cc[0], self.cc[0]+self.r): return False # Degenerate arc # There is no degenerate image
if thanNearx(self.cc[1], self.cc[1]+self.r): return False # Degenerate arc # There is no degenerate image
return not thanNearx(self.theta1, self.theta2) # Degenerate arc
def thanClone(self):
"Makes a geometric clone of itself."
el = ThanArc()
el.thanSet(self.cc, self.r, self.theta1, self.theta2)
return el
def thanRotate(self):
"Rotates the element within XY-plane with predefined angle and rotation angle."
self.cc = self.thanRotateXy(self.cc)
self.theta1 += self.rotPhi
self.theta2 += self.rotPhi
self.theta1 %= PI2 # radians assumed
self.theta2 %= PI2 # radians assumed
if self.theta2 < self.theta1: self.theta2 += PI2 # Ensure theta2>=theta1
self.setBoundBox([self.cc[0]-self.r, self.cc[1]-self.r, self.cc[0]+self.r, self.cc[1]+self.r])
def thanMirror(self):
"Mirrors the element within XY-plane with predefined point and unit vector."
ca = list(self.cc)
ca[0] += self.r*cos(self.theta1)
ca[1] += self.r*sin(self.theta1)
cb = list(self.cc)
cb[0] += self.r*cos(self.theta2)
cb[1] += self.r*sin(self.theta2)
self.cc = self.thanMirrorXy(self.cc)
cb, ca = self.thanMirrorXy(ca), self.thanMirrorXy(cb)
self.theta1 = atan2(ca[1]-self.cc[1], ca[0]-self.cc[0]) % PI2
self.theta2 = atan2(cb[1]-self.cc[1], cb[0]-self.cc[0]) % PI2
if self.theta2 < self.theta1: self.theta2 += PI2 # Ensure theta2>=theta1
self.setBoundBox([self.cc[0]-self.r, self.cc[1]-self.r, self.cc[0]+self.r, self.cc[1]+self.r])
def thanScale(self, cs, scale):
"Scales the element in n-space with defined scale and center of scale."
self.cc = [cs1+(cc1-cs1)*scale for (cc1,cs1) in izip(self.cc, cs)]
self.r *= scale
self.setBoundBox([self.cc[0]-self.r, self.cc[1]-self.r, self.cc[0]+self.r, self.cc[1]+self.r])
def thanMove(self, dc):
"Moves the element with defined n-dimensional distance."
self.cc = [cc1+dd1 for (cc1,dd1) in izip(self.cc, dc)]
self.setBoundBox([self.cc[0]-self.r, self.cc[1]-self.r, self.cc[0]+self.r, self.cc[1]+self.r])
def thanOsnap(self, proj, otypes, ccu, eother, cori):
"Return a point of type otype nearest to point xcu, ycu."
if "ena" not in otypes: return None # Object snap is disabled
ps = []
if "end" in otypes:
for thet in self.theta1, self.theta2: self.thanOsnapAdd(ccu, ps, thet, "end")
if "mid" in otypes:
thet = ((self.theta2 - self.theta1)*0.5) % PI2
thet += self.theta1
self.thanOsnapAdd(ccu, ps, thet, "mid")
if "nea" in otypes:
cn, rn, thet = self.thanPntNearest2(ccu)
if thet != None and rn > self.r: # If we are getting near from the outside then "nea"
self.thanOsnapAdd(ccu, ps, thet, "nea")
if "qua" in otypes:
for thet in 0, 0.5*pi, pi, 1.5*pi: # If both "nea" and "cen" are active, "qua" does not have a chance
if not self.thanThetain(thet)[0]: continue
self.thanOsnapAdd(ccu, ps, thet, "qua")
if "cen" in otypes:
cn, rn, thet = self.thanPntNearest2(ccu)
if thet != None and rn < self.r: # If we are getting near from the inside then "cen"
ps.append((fabs(cn[0]-ccu[0])+fabs(cn[1]-ccu[1]), "cen", self.cc))
if cori != None and "per" in otypes:
for cn in self.thanPerpPoints(cori):
ps.append((fabs(cn[0]-ccu[0])+fabs(cn[1]-ccu[1]), "per", cn))
if cori != None and "tan" in otypes:
dx = (self.cc[0] - cori[0])*0.5
dy = (self.cc[1] - yori[1])*0.5
r = hypot(dx, dy)
c = cori[0]+dx, cori[1]+dy
for cp in thanintersect.thanCirCir(self.cc, self.r, c, r):
thet = atan2(cp[1]-self.cc[1], cp[0]-self.cc[0]) % PI2
if not self.thanThetain(th)[0]: continue
self.thanOsnapAdd(ccu, ps, thet, "tan")
if eother != None and "int" in otypes:
ps.extend(thanintall.thanIntsnap(self, eother, ccu, proj))
if len(ps) > 0: return min(ps)
return None
def thanOsnapAdd(self, ccu, ps, thet, snaptyp):
"Add a new point to osnap points."
cc = list(self.cc)
cc[0] += self.r*cos(thet)
cc[1] += self.r*sin(thet)
ps.append((fabs(cc[0]-ccu[0])+fabs(cc[1]-ccu[1]), snaptyp, cc))
def thanPntNearest(self, ccu):
"Finds the nearest point of this arc to a point."
return self.thanPntNearest2(ccu)[0]
def thanPntNearest2(self, ccu):
"Finds the nearest point of this arc to a point and its angle."
a = ccu[0]-self.cc[0], ccu[1]-self.cc[1]
aa = hypot(a[0], a[1])
if thanNearx(aa, 0.0): thet = 0.0
else: thet = atan2(a[1], a[0]) % PI2
in_, _ = self.thanThetain(thet)
if not in_: return None, None, None
c = list(self.cc)
c[0] += self.r*cos(thet)
c[1] += self.r*sin(thet)
return c, aa, thet
def thanPerpPoints(self, ccu):
"Finds the perpendicular points from ccu to the arc."
a = ccu[0]-self.cc[0], ccu[1]-self.cc[1]
aa = hypot(a[0], a[1])
if thanNearx(aa, 0.0): thet = 0.0
else: thet = atan2(a[1], a[0]) % PI2
ps = []
in_, thet1 = self.thanThetain(thet)
if in_:
c = list(self.cc)
c[0] += self.r*cos(thet1)
c[1] += self.r*sin(thet1)
ps.append(c)
thet += pi
in_, thet1 = self.thanThetain(thet)
if in_:
c = list(self.cc)
c[0] += self.r*cos(thet1)
c[1] += self.r*sin(thet1)
ps.append(c)
return ps
def thanLength(self):
"Returns the length of the arc."
return pi*(self.theta2-self.theta1)
def thanArea(self):
"Returns the area of the arc."
dth = self.theta2-self.theta1
assert dth >= 0.0
if dth <= pi:
asec = dth*0.5*self.r**2
atri = self.r*cos(dth*0.5)*self.r*sin(dth*0.5)
return asec-atri
else:
dth = 2*pi - dth
asec = dth*0.5*self.r**2
atri = self.r*cos(dth*0.5)*self.r*sin(dth*0.5)
return pi*self.r**2 - (asec - atri)
def thanTrim(self, ct, cnear):
"Breaks the line into multiple segments and deletes the segment nearest to cnear."
cp = []
for c in ct:
cn, i, t = self.thanPntNearest2(c)
cp.append((t, i, c))
assert cn != None, "It should have been checked (that ct are indeed near the arc)!"
cp.sort()
cn, i, t = self.thanPntNearest2(cnear)
cpnear = t, i, cn
assert cpnear[2] != None, "It should have been checked (that cnear are indeed near the arc)!"
i = bisect.bisect_right(cp, cpnear)
if i == 0:
c1 = list(self.cc)
c1[0] += self.r*cos(self.theta1)
c1[1] += self.r*sin(self.theta1)
return self.thanBreak(c1, cp[0][2]) # User selected the segment before the first intesection (ct)
elif i == len(cp):
c1 = list(self.cc)
c1[0] += self.r*cos(self.theta2)
c1[1] += self.r*sin(self.theta2)
return self.thanBreak(cp[-1][2], c1) # User selected the segment after the last intesection (ct)
else:
return self.thanBreak(cp[i-1][2], cp[i][2]) # User selected the segment between i-1 and i intesections (ct)
def thanBreak(self, c1=None, c2=None):
"Breaks an arc to 2 arcs."
if c1 == None: return True # Break IS implemented
cp1, r1, thet1 = self.thanPntNearest2(c1)
assert cp1 != None, "pntNearest should succeed (as in thancommod.__getNearPnt()"
cp2, r2, thet2 = self.thanPntNearest2(c2)
assert cp2 != None, "pntNearest should succeed (as in thancommod.__getNearPnt()"
if thet2 < thet1: thet2, thet1 = thet1, thet2 # Ensure thet1 < thet2
e1 = ThanArc()
e1.thanSet(self.cc, self.r, self.theta1, thet1)
if not e1.thanIsNormal(): e1 = None
e2 = ThanArc()
e2.thanSet(self.cc, self.r, thet2, self.theta2)
if not e2.thanIsNormal(): e2 = None
return e1, e2
def thanThetain(self, th):
"Finds if th is betweem self.theta1 and self.theta2."
if self.theta1 <= th <= self.theta2: return True, th
if self.theta1 <= th+PI2 <= self.theta2: return True, th+PI2
if thanNearx(0.0, self.r*tan(self.theta1-th)): return True, self.theta1
if thanNearx(0.0, self.r*tan(self.theta2-th)): return True, self.theta2
return False, th
def thanTkGet(self, proj):
"Gets the attributes of the arc interactively from a window."
than = proj[2].than
g2l = than.ct.global2Local
cc = proj[2].thanGudGetPoint(T["Center: "])
if cc == Canc: return Canc # Arc cancelled
r = proj[2].thanGudGetCircle(cc, T["Radius: "])
if r == Canc: return Canc # Arc cancelled
temp = than.dc.create_oval(g2l(cc[0]-r, cc[1]-r), g2l(cc[0]+r, cc[1]+r),
outline="blue", tags=("e0",), outlinestipple="gray50")
theta1 = proj[2].thanGudGetPolar(cc, r, T["First point angle: "])
than.dc.delete("e0")
if theta1 == Canc: return Canc # Arc cancelled
than.dc.create_line(g2l(cc[0], cc[1]), g2l(cc[0]+r*cos(theta1), cc[1]+r*sin(theta1)), fill="blue", tags=("e0",))
theta2 = proj[2].thanGudGetArc(cc, r, theta1, T["Last point angle: "])
than.dc.delete("e0")
if theta2 == Canc: return Canc # Arc cancelled
self.thanSet(cc, r, theta1, theta2)
return True # Arc OK
def thanTkDraw(self, than):
"Draws the arc to a Tk Canvas."
xc, yc = than.ct.global2Local(self.cc[0], self.cc[1])
r, temp = than.ct.global2LocalRel(self.r, self.r)
theta1 = self.theta1 * 180.0/pi
theta2 = self.theta2 * 180.0/pi
dth = (theta2-theta1) % 360.0
temp = than.dc.create_arc(xc-r, yc-r, xc+r, yc+r, start=theta1, extent=dth,
style=Tkinter.ARC, outline=than.outline, fill=than.fill, tags=self.thanTags)
def thanTkHiwin(self, than):
"Highlights with a (small) window very small elements so that they become visible."
ca = list(cc)
ca[0] += self.r*cos(self.theta1)
ca[1] += self.r*sin(self.theta1),
self.thanTkHiwinDo(than, self.thanLength(), ca)
def thanExpDxf(self, fDxf):
"Exports the arc to dxf file."
fDxf.thanDxfPlotArc3(self.cc[0], self.cc[1], self.cc[2], self.r,
self.theta1/pi*180.0, self.theta2/pi*180.0)
def thanExpPil(self, than):
"Exports the arc to a PIL raster image."
x1, y1 = than.ct.global2Locali(self.cc[0]-self.r, self.cc[1]+self.r) # PIL needs left,upper and ..
x2, y2 = than.ct.global2Locali(self.cc[0]+self.r, self.cc[1]-self.r) # ..right,lower
t2 = -int(self.theta1/pi*180.0+0.5)
t1 = -int(self.theta2/pi*180.0+0.5)
for i in xrange(*than.widtharc):
than.dc.arc((x1-i, y1-i, x2+i, y2+i), t1, t2, fill=than.outline)
def thanList(self, than):
"Shows information about the arc element."
than.writecom("Element: ARC")
than.write(" Layer: % s\n" % than.laypath)
than.write("Length: %s Area: %s\n" % (than.strdis(self.thanLength()), than.strdis(self.thanArea())))
t = ("Center: %s" % than.strcoo(self.cc),
"Radius: %s" % than.strdis(self.r),
"Span from: %s to: %s\n"% (than.strang(self.theta1), than.strang(self.theta2)),
)
than.write("\n".join(t))
if __name__ == "__main__":
prg(__doc__)
c = ThanArc()
c.thanSet([10.0, 20.0, -11.0], 3.0, 0.0, pi*0.5)
prg("arc=%s" % (c,))
prg("degenerate=%s" % bool(not c.thanIsNormal()))
|