##############################################################################
# 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 polyline element.
"""
from itertools import izip,islice
from math import fabs,hypot,pi,atan2
import weakref, bisect
from p_ggen import iterby2
from p_gmath import dpt,PI2,thanNearx,thanNear2,thanNear3
import p_ggeom
import thanintall
from thanelem import ThanElement
from thanvar import Canc,thanCleanLine3
from thanvar.thanoffset import thanOffsetLine
from thantrans import T
try: import pyx
except ImportError: pass
class ThanLine(ThanElement):
"A Basic simple 2d line."
def thanSet (self, cp):
"""Sets the attributes of the line.
If len(cp) == 0 or == 1, all the methods work (they produce nothing)
except the thanBreak, thanSegNearest, thanTkContinue, thanTkDraw and others.
Thus it is imperative to call thanIsNormal() to see if the line is degenerate
before attempting any of the above functions.
"""
self.cp = thanCleanLine3(cp) #FIXME: thanCleanLine3 cleans 3dimensional degenerate segments;..
if len(self.cp) > 0: #..
xp = [c1[0] for c1 in self.cp] #Thus 2d algorithms may fail if deltax==deltay==0 CHECK ALL THE OPERATIONS..
yp = [c1[1] for c1 in self.cp] #Is thanCleanLine still necessary (it is used in ThanRoad only)???
self.setBoundBox([min(xp), min(yp), max(xp), max(yp)])
# self.thanTags = () # thanTags is initialised in ThanElement
def thanIsNormal(self):
"Returns False if the line is degenerate (it has only one point)."
# if len(cp) == 2 and it is closed then it is degenerate and this function returns false
# if len(cp) == 3 and it is closed then it is degenerate polygon but
# a good line, so this function returns True
if len(self.cp) < 2: return False # Return False if line is degenerate
cp = iter(self.cp)
c1 = cp.next()
for c2 in cp:
if not thanNear3(c1, c2): return True
return False # All line points are close together
def thanClean(self):
"Clean zero lengthed segments."
self.cp = thanCleanLine3(self.cp)
def thanClone(self):
"Makes a geometric clone of itself."
el = self.__class__()
el.thanSet(self.cp)
return el
def thanRotate(self):
"Rotates the element within XY-plane with predefined angle and rotation angle."
self.thanRotateXyn(self.cp)
xp = [c1[0] for c1 in self.cp]
yp = [c1[1] for c1 in self.cp]
self.setBoundBox([min(xp), min(yp), max(xp), max(yp)])
def thanMirror(self):
"Mirrors the element within XY-plane with predefined point and unit vector."
self.thanMirrorXyn(self.cp)
xp = [c1[0] for c1 in self.cp]
yp = [c1[1] for c1 in self.cp]
self.setBoundBox([min(xp), min(yp), max(xp), max(yp)])
def thanScale(self, cs, scale):
"Scales the ele`ment in n-space with defined scale and center of scale."
for cc in self.cp:
cc[:] = [cs1+(cc1-cs1)*scale for (cc1,cs1) in izip(cc, cs)]
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."
for cc in self.cp:
cc[:] = [cc1+dd1 for (cc1,dd1) in izip(cc, 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 ccu."
if "ena" not in otypes: return None # Object snap is disabled
ps = []
if "end" in otypes: # type "end" without type "int"
for c in self.cp:
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "end", c))
if "mid" in otypes:
for ca, cb in iterby2(self.cp):
c = [(ca1+cb1)*0.5 for (ca1,cb1) in izip(ca, cb)]
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "mid", c))
if "nea" in otypes:
c = self.thanPntNearest(ccu)
if c != None:
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "nea", c))
if cori != None and "per" in otypes:
for c in self.thanPerpPoints(cori):
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "per", c))
if eother != None and "int" in otypes:
ps.extend(thanintall.thanIntsnap(self, eother, ccu, proj))
if len(ps) < 1: return None
return min(ps)
def thanPntNearest(self, ccu):
"Finds the nearest point of this line to a point."
return thanPntNearest2(self.cp, ccu)[0]
def thanPntNearest2(self, ccu):
"Finds the nearest point of this line to a point."
return thanPntNearest2(self.cp, ccu)
def thanPerpPoints(self, ccu):
"Finds perpendicular point from ccu to polyline."
return thanPerpPoints(self.cp, ccu)
def thanSegNearest(self, ccu):
"""Finds the nearest segment of this line to a point."""
return thanSegNearest(self.cp, ccu)
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 line)!"
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 line)!"
i = bisect.bisect_right(cp, cpnear)
if i == 0:
return self.thanBreak(self.cp[0], cp[0][2]) # User selected the segment before the first intesection (ct)
elif i == len(cp):
return self.thanBreak(cp[-1][2], self.cp[-1])# 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 a line to 2 pieces; c1 and c2 may be identical."
if c1 == None: return True # Report that break IS implemented
cp1, i1, tcp1 = self.thanPntNearest2(c1)
cp2, i2, tcp2 = self.thanPntNearest2(c2)
if tcp2 < tcp1:
cp1, i1, cp2, i2 = cp2, i2, cp1, i1
assert cp1 != None and cp2 != None, "It should have been checked (that c1 and c2 are indeed near line)!"
cs1 = self.cp[:i1]
if len(cs1) > 0:
cs1.append(cp1)
e1 = ThanLine()
e1.thanSet(cs1)
if not e1.thanIsNormal(): e1 = None # A tiny segment was left
else:
e1 = None
cs1 = self.cp[i2:]
if len(cs1) > 0:
cs1.insert(0, cp2)
e2 = ThanLine()
e2.thanSet(cs1)
if not e2.thanIsNormal(): e2 = None # A tiny segment was left
else:
e2 = None
return e1, e2 # If both are None, then "break" left nothing
def thanOffset(self, dis):
"Offset line by distance dis; to the right if dis>0 and to the left otherwise."
if dis == None: return True # Offset is implemented
cs = thanOffsetLine(self.cp, dis)
e = ThanLine()
e.thanSet(cs)
if e.thanIsNormal(): return e
return None
def thanLength(self):
"Returns the length of the polyline."
al = 0.0
for ca, cb in iterby2(self.cp):
al += hypot(cb[0]-ca[0], cb[1]-ca[1])
return al
def thanArea(self):
"Returns the area of the polyline."
return p_ggeom.area(self.cp)
#===========================================================================
def thanTkGet(self, proj):
"Gets the attributes of the line interactively from a window."
cp = []
while True:
c1 = proj[2].thanGudGetPoint(T["First line point (c=continue "
"previous/e=extend other): "], options=("continue", "extend"))
if c1 == Canc: return Canc # Line cancelled
if c1 == "c":
proj[2].thanGudCommandBegin("continueline")
return Canc
elif c1 == "e":
proj[2].thanGudCommandBegin("extendline")
return Canc
break
cp.append(c1)
res = self.__tkGetn(proj, cp)
if res == Canc: return Canc
proj[1].thanPrevLine = weakref.ref(self)
def thanTkContinue(self, proj):
"Gets the attributes of the line interactively from a window."
dc = proj[2].than.dc
dc.delete(self.thanTags[0])
g2l = proj[2].than.ct.global2Local
cl = proj[2].than.dc.create_line
fi = proj[2].than.outline
cp = list(self.cp)
x1, y1 = g2l(cp[0][0], cp[0][1])
for i in xrange(1, len(cp)):
tags = "e0", "e"+str(i+1)
x2, y2 = g2l(cp[i][0], cp[i][1])
temp = cl(x1, y1, x2, y2, fill=fi, tags=tags)
x1 = x2; y1 = y2
res = self.__tkGetn(proj, cp)
if res == Canc: return Canc
self.thanTkDraw(proj[2].than)
return res
def __tkGetn(self, proj, cp):
"Gets the following points of the line interactively from a window."
cla = proj[1].thanLayerTree.thanCur
g2l = proj[2].than.ct.global2Local
cl = proj[2].than.dc.create_line
delete = proj[2].than.dc.delete
fi = proj[2].than.outline
wpix = proj[2].than.tkThick
c1 = cp[-1]
while True:
res = self.__getPointLin(proj[2], c1, len(cp))
if res == Canc and len(cp) < 2: return Canc # Line cancelled
if res == Canc or res == "" or res == "c": break # Line ended (we know that line has more than 1 point)
if res == "u":
delete("e"+str(len(cp)))
c1 = cp[-2]
del cp[-1]
else:
c1 = res
cp.append(c1)
tags = "e0", cla.thanTag, "e"+str(len(cp))
temp = cl((g2l(cp[-2][0], cp[-2][1]), g2l(c1[0], c1[1])), fill=fi, width=wpix, tags=tags)
# print "line:thantkget:fi=", fi
if res == "c":
cp.append(list(cp[0]))
# Note that if we did: cp.append(cp[0])
# then if we changed the coordinates of cp[0], the coordinates of cp[-1]
# would also change. This means that the line would always be closed.
# Also, thanClone should check for this, since the cloned line would have
# independent coordinates in cp[0] and cp[1]. Also, all the modifications
# (move, rotate etc.) should be careful not to apply the modification
# to both cp[0] and cp[-1], as it would make the modification twice.
delete("e0")
self.thanSet(cp)
return True # Line OK (note that it will have at least 2 points)
def __getPointLin(win, c1, np):
"Gets a point from the user, with possible options."
if np == 1:
stat1 = T["Next line point: "]
return win.thanGudGetLine(c1, stat1)
elif np == 2:
stat1 = T["Next line point (undo/<enter>): "]
return win.thanGudGetLine(c1, stat1, options=("", "undo"))
else:
stat1 = T["Next line point (undo/close/<enter>): "]
return win.thanGudGetLine(c1, stat1, options=("", "undo", "close"))
__getPointLin = staticmethod(__getPointLin)
#===========================================================================
def thanTkDraw(self, than):
"Draws the line to a Tk Canvas."
g2l = than.ct.global2Local
w = than.tkThick
xy1 = [g2l(c1[0], c1[1]) for c1 in self.cp]
if thanNear2(self.cp[0], self.cp[-1]): # Even if fill="", which means no fill
# temp = than.dc.create_polygon(xy1, outline=than.outline, fill=than.fill, tags=self.thanTags, width=w)
temp = than.dc.create_line( xy1, fill=than.outline, tags=self.thanTags, width=w)
else:
temp = than.dc.create_line( xy1, fill=than.outline, tags=self.thanTags, width=w)
def thanTkHiwin(self, than):
"Highlights with a (small) window very small elements so that they become visible."
self.thanTkHiwinDo(than, self.thanLength(), self.cp[0])
def thanExpDxf(self, fDxf):
"Exports the line to dxf file."
#FIXME: report width of polyline
xp = [c1[0] for c1 in self.cp]
yp = [c1[1] for c1 in self.cp]
zp = [c1[2] for c1 in self.cp]
fDxf.thanDxfPlotPolyline3(xp, yp, zp)
def thanExpSyk(self, than):
"Exports the line to syk file."
than.write("%15.3f %s\n" % (self.cp[0][2], than.layname))
for cp1 in self.cp:
than.write("%15.3f%15.3f\n" % (cp1[0], cp1[1]))
than.write("$\n")
def thanExpBrk(self, than):
"Exports the line to brk file."
for cp1 in self.cp:
than.ibr += 1
than.write(than.form % (than.ibr, cp1[0], cp1[1], cp1[2]))
than.write("$\n")
def thanExpPil(self, than):
"""Exports the line to a PIL raster image.
There seems to be a bug in the PIL which chaotically affects the width
of the line. Thus, this code is not used. But it should be checked
with future versions of PIL.
"""
g2l = than.ct.global2Local
xy1 = [g2l(c1[0], c1[1]) for c1 in self.cp]
if thanNear2(self.cp[0], self.cp[-1]) and not than.fill:
than.dc.polygon(xy1, outline=than.outline, fill=than.fill) # width=than.width)
else:
than.dc.line(xy1, fill=than.outline, width=than.width)
def thanExpPilworkaround(self, than):
"Exports the line to a PIL raster image; 2008_10_07:no longer valid."
w, _ = than.ct.global2LocalRel(self.width, self.width)
if w < than.rwidth: w = than.rwidth
if w <= 1.5:
g2l = than.ct.global2Locali
xy1 = [g2l(c1[0], c1[1]) for c1 in self.cp]
than.dc.line(xy1, fill=than.outline)
return
w2, _ = than.ct.local2GlobalRel(w*0.5, w)
for i in xrange(1, len(self.cp)):
x1, y1 = self.cp[i-1][0], self.cp[i-1][1]
x2, y2 = self.cp[i][0], self.cp[i][1]
dx, dy = x2-x1, y2-y1
r = hypot(dx, dy)
if r <= 0.0: continue
tx, ty = dx/r, dy/r
nx, ny = -ty*w2, tx*w2
cr = (x1-nx,y1-ny), (x2-nx,y2-ny), (x2+nx,y2+ny), (x1+nx,y1+ny)
xy1 = [than.ct.global2Locali(x1, y1) for x1,y1 in cr]
than.dc.polygon(xy1, outline=than.outline, fill=than.outline)
def thanPlotPdf(self, than):
"Plots the line to a pdf file."
#FIXME: report width of polyline
if len(self.cp) < 2: return
g2l = than.ct.global2Local
if len(self.cp) == 2:
ca = g2l(self.cp[0][0], self.cp[0][1])
cb = g2l(self.cp[1][0], self.cp[1][1])
p = pyx.path.line(ca[0], ca[1], cb[0], cb[1])
else:
lineto = pyx.path.lineto
moveto = pyx.path.moveto
xy1 = [lineto(*g2l(c1[0], c1[1])) for c1 in islice(self.cp, 1, None)]
xy1.insert(0, moveto(*g2l(self.cp[0][0], self.cp[0][1])))
if thanNear2(self.cp[0], self.cp[-1]): xy1[-1] = pyx.path.closepath()
p = pyx.path.path(*xy1)
than.dc.stroke(p)
def thanList(self, than):
"Shows information about the line element."
than.writecom("Element: LINE")
if thanNear2(self.cp[0], self.cp[-1]): oc = "closed"
else: oc = "open"
than.write(" (%s) Layer: %s\n" % (oc, than.laypath))
than.write("Length: %s Area: %s\n" % (than.strdis(self.thanLength()), than.strdis(self.thanArea())))
than.write("Vertices (%d):\n" % len(self.cp))
for i,c1 in enumerate(self.cp):
than.write(" %s\n" % than.strcoo(c1))
if i % 20 == 19:
c = than.read("Press enter to continue..")
if c == Canc: break
def __del__(self):
print "ThanLine", self, "is deleted"
##############################################################################
##############################################################################
class ThanLineFilled(ThanLine):
"A closed polyline which is filled with colour."
def thanTkDraw(self, than):
"Draws the filled line to a Tk Canvas."
g2l = than.ct.global2Local
w = than.tkThick
xy1 = [g2l(c1[0], c1[1]) for c1 in self.cp]
fill = than.fill
if not fill: fill = than.outline
temp = than.dc.create_polygon(xy1, outline=than.outline, fill=fill, tags=self.thanTags, width=w)
def thanExpPil(self, than):
"""Exports the filled line to a PIL raster image."""
g2l = than.ct.global2Local
xy1 = [g2l(c1[0], c1[1]) for c1 in self.cp]
fill = than.fill
if not fill: fill = than.outline
than.dc.polygon(xy1, outline=than.outline, fill=fill) # width=than.width)
def thanExpDxf(self, fDxf):
"Exports the filled line to dxf file; if it has 3 or 4 points then as a solid."
if not thanNear2(self.cp[0], self.cp[-1]):
ThanLine.thanExpDxf(self, fDxf)
elif len(self.cp) == 4:
c1, c2, c3 = self.cp[:3]
fDxf.thanDxfPlotSolid3(c1[0], c1[1], c2[0], c2[1], c3[0], c3[1])
elif len(self.cp) == 5:
c1, c2, c3, c4 = self.cp[:4]
fDxf.thanDxfPlotSolid4(c1[0], c1[1], c2[0], c2[1], c3[0], c3[1], c4[0], c4[1])
else:
ThanLine.thanExpDxf(self, fDxf)
def thanList(self, than):
"Shows information about the filled line element."
than.writecom("Element: LINE FILLED (SOLID)")
than.write(" Layer: %s\n" % than.laypath)
than.write("Length: %s Area: %s\n" % (than.strdis(self.thanLength()), than.strdis(self.thanArea())))
than.write("Vertices (%d):\n" % len(self.cp))
for i,c1 in enumerate(self.cp):
than.write(" %s\n" % than.strcoo(c1))
if i % 20 == 19:
c = than.read("Press enter to continue..")
if c == Canc: break
##############################################################################
##############################################################################
class ThanCurve(ThanLine):
"""A curve which is simulated by a large number of cosecutive linear segments.
Basically an ordinary polyline, with the difference that osnap does not snap
to the end points and midpoints of the linear segments.
For the tangent osnap to work, it is supposed that the curve is continuous (it has
to be, noncontinuous can not be represented by a ThanLine) and that the line segments
which simulate the curve are dense enough to show the smooth character of the curve,
(where the curve is indeed smooth). If the curve has some real corners, these are taken
into account by the threshold of the difference between tagents.
"""
def thanMidPoint(self):
"Finds the midpoint of the curve."
alm = self.thanLength()*0.5
ala = 0.0
for ca, cb in iterby2(self.cp):
alb = ala + hypot(cb[0]-ca[0], cb[1]-ca[1])
if alb >= alm: break
ala = alb
else:
assert False, "What? Half length is bigger than length?!"
if thanNearx(ala, alb): # In case of zero length segment
c = [(za+zb)*0.5 for (za, zb) in zip(ca, cb)]
else:
c = [za+(zb-za)/(alb-ala)*(alm-ala) for (za, zb) in zip(ca, cb)]
return c
def thanTanPoints(self, cori):
"""Finds the tangent to the curve from cori to the curve.
If more than 1 tangents are found, all are returned.
"""
thtol = 0.5*pi/180.0 # Tolerance is 0.5 degrees
ps = []
for ca, cb in iterby2(self.cp):
thcurv = atan2(cb[1]-ca[1], cb[0]-ca[0])
thtan = atan2(ca[1]-cori[1], ca[0]-cori[0])
dth = dpt(thcurv-thtan)
if dth < thtol or PI2-dth < thtol or fabs(dth-pi) < thtol: ps.append(ca)
return ps
def thanCenterPoint(self, ccu):
"Finds the center and the radius of the circle which is tangent to point nearest to ccu."
from p_gvec import Vector2
thtol = 2.0*pi/180.0 # Tolerance is 2.0 degrees
cp1, i, tcp1 = self.thanPntNearest2(ccu)
if cp1 == None: return None, None, None
if i+1 >= len(self.cp): return None, None, None
ca, cb, cc = self.cp[i-1:i+2]
a = hypot(cb[0]-ca[0], cb[1]-ca[1])
th1 = atan2(cb[1]-ca[1], cb[0]-ca[0])
th2 = atan2(cc[1]-cb[1], cc[0]-cb[0])
dth = dpt(th2-th1)
if dth > pi: dth = fabs(dth-PI2)
if dth > thtol: return None, None, None # not smooth; it is a corner
if thanNearx(dth, 0.0): return None, None, None # Straight line, radius is infinite
r = a / dth
va = Vector2(ca[0], ca[1])
vb = Vector2(cb[0], cb[1])
vc = Vector2(cc[0], cc[1])
t1 = (va-vb).unit()
t2 = (vc-vb).unit()
t = (t1+t2).unit()
vcen = vb + r*t
ccen = list(cb)
ccen[:2] = vcen.x, vcen.y
return ccen, r, cb
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
ps = []
if "end" in otypes: # type "end" without type "int"
for c in self.cp[0], self.cp[-1]:
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "end", c))
if "mid" in otypes:
c = self.thanMidPoint()
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "mid", c))
if "tan" in otypes:
if cori != None:
for c in self.thanTanPoints(cori):
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "tan", c))
if "cen" in otypes:
c, r, ctang = self.thanCenterPoint(ccu)
print c, r
if c != None:
ps.append((fabs(ctang[0]-ccu[0])+fabs(ctang[1]-ccu[1]), "cen", c))
if "nea" in otypes:
c = self.thanPntNearest(ccu)
if c != None:
ps.append((fabs(c[0]-ccu[0])+fabs(c[1]-ccu[1]), "nea", c))
if eother != None and "int" in otypes:
ps.extend(thanintall.thanIntsnap(self, eother, ccu, proj))
if len(ps) < 1: return None
return min(ps)
def thanList(self, than):
"Shows information about the curve element."
than.writecom("Element: CURVE")
than.write(" Layer: %s\n" % than.laypath)
than.write("Length: %s Area: %s\n" % (than.strdis(self.thanLength()), than.strdis(self.thanArea())))
##############################################################################
##############################################################################
def thanPntNearest(cp, ccu):
"Finds the nearest point of this line to a point."
return thanPntNearest2(cp, ccu)[0]
def thanPntNearest2(cp, ccu):
"Finds the nearest point of this line to a point."
dmin=1e100; cp1 = None; iseg = -1; tcp1 = -1.0
tall = 0.0
for i in xrange(1, len(cp)):
a = cp[i][0]-cp[i-1][0], cp[i][1]-cp[i-1][1]
aa = hypot(*a)
tall += aa
if thanNearx(aa, 0.0): continue # Segment has zero length
ta = a[0]/aa, a[1]/aa
b = ccu[0]-cp[i-1][0], ccu[1]-cp[i-1][1]
dt = ta[0]*b[0]+ta[1]*b[1]
if thanNearx(dt, 0.0) : dt = 0.0
elif thanNearx(dt, aa) : dt = aa
elif dt < 0.0 or dt > aa: continue
dn = fabs(-ta[1]*b[0]+ta[0]*b[1])
if dn < dmin:
cp1 = [e+(f-e)*dt/aa for (e,f) in izip(cp[i-1], cp[i])]
dmin = dn
iseg = i
tcp1 = tall - aa + dt
return cp1, iseg, tcp1
def thanSegNearest(cp, ccu):
"""Finds the nearest segment of this line to a point.
It is an optimisation of thanPntNearest2(), if the nearest point is
not needed.
This function is used in thanintall in order to find the intersection
of a line and another ThanCad element, when object snap intersection
is enabled. We take advantage of the fact that the mouse coordinates
ccu are already very near the line (and the other element). Thus, we
don't check if the projection of the ccu to the line segment is indeed
between the end of the line segment. However thanintall will ensure
that the intersection point will belong to both elements.
"""
dmax=1e100
for i in xrange(1, len(cp)):
a = cp[i][0]-cp[i-1][0], cp[i][1]-cp[i-1][1]
aa = hypot(*a)
if aa == 0.0: continue # Segment has zero length
b = ccu[0]-cp[i-1][0], ccu[1]-cp[i-1][1]
dn = fabs(a[0]*b[1]-a[1]*b[0]) / aa
if dn < dmax: imax=i; dmax=dn
return cp[imax-1], cp[imax]
def thanPerpPoints(cp, ccu):
"Finds perpendicular point from ccu to polyline."
ps = []
tall = 0.0
for i in xrange(1, len(cp)):
a = cp[i][0]-cp[i-1][0], cp[i][1]-cp[i-1][1]
aa = hypot(*a)
tall += aa
if thanNearx(aa, 0.0): continue # Segment has zero length
ta = a[0]/aa, a[1]/aa
b = ccu[0]-cp[i-1][0], ccu[1]-cp[i-1][1]
dt = ta[0]*b[0]+ta[1]*b[1]
if thanNearx(dt, 0.0) : dt = 0.0
elif thanNearx(dt, aa) : dt = aa
elif dt < 0.0 or dt > aa: continue
dn = fabs(-ta[1]*b[0]+ta[0]*b[1])
cp1 = [e+(f-e)*dt/aa for (e,f) in izip(cp[i-1], cp[i])]
ps.append(cp1)
return ps
if __name__ == "__main__":
print __doc__
|