mirror of https://github.com/VLSIDA/OpenRAM.git
409 lines
16 KiB
Python
409 lines
16 KiB
Python
|
|
# -*- coding: ISO-8859-1 -*-
|
||
|
|
#
|
||
|
|
#
|
||
|
|
# Copyright (C) 2003-2006 Michael Schindler <m-schindler@users.sourceforge.net>
|
||
|
|
#
|
||
|
|
# This file is part of PyX (http://pyx.sourceforge.net/).
|
||
|
|
#
|
||
|
|
# PyX 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.
|
||
|
|
#
|
||
|
|
# PyX 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.
|
||
|
|
#
|
||
|
|
# You should have received a copy of the GNU General Public License
|
||
|
|
# along with PyX; if not, write to the Free Software
|
||
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||
|
|
|
||
|
|
|
||
|
|
import math
|
||
|
|
from math import pi, sin, cos, atan2, tan, hypot, acos, sqrt
|
||
|
|
import path, unit, mathutils, normpath
|
||
|
|
try:
|
||
|
|
from math import radians, degrees
|
||
|
|
except ImportError:
|
||
|
|
# fallback implementation for Python 2.1 and below
|
||
|
|
def radians(x): return x*pi/180
|
||
|
|
def degrees(x): return x*180/pi
|
||
|
|
|
||
|
|
|
||
|
|
#########################
|
||
|
|
## helpers
|
||
|
|
#########################
|
||
|
|
|
||
|
|
class connector_pt(normpath.normpath):
|
||
|
|
|
||
|
|
def omitends(self, box1, box2):
|
||
|
|
"""intersects a path with the boxes' paths"""
|
||
|
|
|
||
|
|
# cut off the start of self
|
||
|
|
# XXX how can decoration of this box1.path() be handled?
|
||
|
|
sp = self.intersect(box1.path())[0]
|
||
|
|
if sp:
|
||
|
|
self.normsubpaths = self.split(sp[-1:])[1].normsubpaths
|
||
|
|
|
||
|
|
# cut off the end of self
|
||
|
|
sp = self.intersect(box2.path())[0]
|
||
|
|
if sp:
|
||
|
|
self.normsubpaths = self.split(sp[:1])[0].normsubpaths
|
||
|
|
|
||
|
|
def shortenpath(self, dists):
|
||
|
|
"""shortens a path by the given distances"""
|
||
|
|
|
||
|
|
# XXX later, this should be done by extended boxes instead of intersecting with circles
|
||
|
|
# cut off the start of self
|
||
|
|
center = self.atbegin_pt()
|
||
|
|
cutpath = path.circle_pt(center[0], center[1], dists[0])
|
||
|
|
try:
|
||
|
|
cutpath = cutpath.normpath()
|
||
|
|
except normpath.NormpathException:
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
sp = self.intersect(cutpath)[0]
|
||
|
|
self.normsubpaths = self.split(sp[-1:])[1].normsubpaths
|
||
|
|
|
||
|
|
# cut off the end of self
|
||
|
|
center = self.atend_pt()
|
||
|
|
cutpath = path.circle_pt(center[0], center[1], dists[1])
|
||
|
|
try:
|
||
|
|
cutpath = cutpath.normpath()
|
||
|
|
except normpath.NormpathException:
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
sp = self.intersect(cutpath)[0]
|
||
|
|
if sp:
|
||
|
|
self.normsubpaths = self.split(sp[:1])[0].normsubpaths
|
||
|
|
|
||
|
|
|
||
|
|
################
|
||
|
|
## classes
|
||
|
|
################
|
||
|
|
|
||
|
|
|
||
|
|
class line_pt(connector_pt):
|
||
|
|
|
||
|
|
def __init__(self, box1, box2, boxdists=[0,0]):
|
||
|
|
|
||
|
|
self.box1 = box1
|
||
|
|
self.box2 = box2
|
||
|
|
|
||
|
|
connector_pt.__init__(self,
|
||
|
|
[path.normsubpath([path.normline_pt(self.box1.center[0], self.box1.center[1],
|
||
|
|
self.box2.center[0], self.box2.center[1])], closed=0)])
|
||
|
|
|
||
|
|
self.omitends(box1, box2)
|
||
|
|
self.shortenpath(boxdists)
|
||
|
|
|
||
|
|
|
||
|
|
class arc_pt(connector_pt):
|
||
|
|
|
||
|
|
def __init__(self, box1, box2, relangle=45,
|
||
|
|
absbulge=None, relbulge=None, boxdists=[0,0]):
|
||
|
|
|
||
|
|
# the deviation of arc from the straight line can be specified:
|
||
|
|
# 1. By an angle between a straight line and the arc
|
||
|
|
# This angle is measured at the centers of the box.
|
||
|
|
# 2. By the largest normal distance between line and arc: absbulge
|
||
|
|
# or, equivalently, by the bulge relative to the length of the
|
||
|
|
# straight line from center to center.
|
||
|
|
# Only one can be used.
|
||
|
|
|
||
|
|
self.box1 = box1
|
||
|
|
self.box2 = box2
|
||
|
|
|
||
|
|
tangent = (self.box2.center[0] - self.box1.center[0],
|
||
|
|
self.box2.center[1] - self.box1.center[1])
|
||
|
|
distance = hypot(*tangent)
|
||
|
|
tangent = tangent[0] / distance, tangent[1] / distance
|
||
|
|
|
||
|
|
if relbulge is not None or absbulge is not None:
|
||
|
|
# usage of bulge overrides the relangle parameter
|
||
|
|
bulge = 0
|
||
|
|
if absbulge is not None:
|
||
|
|
bulge += absbulge
|
||
|
|
if relbulge is not None:
|
||
|
|
bulge += relbulge*distance
|
||
|
|
else:
|
||
|
|
# otherwise use relangle, which should be present
|
||
|
|
bulge = 0.5 * distance * math.tan(0.5*radians(relangle))
|
||
|
|
|
||
|
|
if abs(bulge) < normpath._epsilon:
|
||
|
|
# fallback solution for too straight arcs
|
||
|
|
connector_pt.__init__(self,
|
||
|
|
[path.normsubpath([path.normline_pt(*(self.box1.center+self.box2.center))], closed=0)])
|
||
|
|
else:
|
||
|
|
radius = abs(0.5 * (bulge + 0.25 * distance**2 / bulge))
|
||
|
|
centerdist = mathutils.sign(bulge) * (radius - abs(bulge))
|
||
|
|
center = (0.5 * (self.box1.center[0] + self.box2.center[0]) + tangent[1]*centerdist,
|
||
|
|
0.5 * (self.box1.center[1] + self.box2.center[1]) - tangent[0]*centerdist)
|
||
|
|
angle1 = atan2(self.box1.center[1] - center[1], self.box1.center[0] - center[0])
|
||
|
|
angle2 = atan2(self.box2.center[1] - center[1], self.box2.center[0] - center[0])
|
||
|
|
|
||
|
|
if bulge > 0:
|
||
|
|
connectorpath = path.path(path.moveto_pt(*self.box1.center),
|
||
|
|
path.arcn_pt(center[0], center[1], radius, degrees(angle1), degrees(angle2)))
|
||
|
|
connector_pt.__init__(self, connectorpath.normpath().normsubpaths)
|
||
|
|
else:
|
||
|
|
connectorpath = path.path(path.moveto_pt(*self.box1.center),
|
||
|
|
path.arc_pt(center[0], center[1], radius, degrees(angle1), degrees(angle2)))
|
||
|
|
connector_pt.__init__(self, connectorpath.normpath().normsubpaths)
|
||
|
|
|
||
|
|
self.omitends(box1, box2)
|
||
|
|
self.shortenpath(boxdists)
|
||
|
|
|
||
|
|
|
||
|
|
class curve_pt(connector_pt):
|
||
|
|
|
||
|
|
def __init__(self, box1, box2,
|
||
|
|
relangle1=45, relangle2=45,
|
||
|
|
absangle1=None, absangle2=None,
|
||
|
|
absbulge=0, relbulge=0.39, boxdists=[0,0]):
|
||
|
|
|
||
|
|
# The deviation of the curve from a straight line can be specified:
|
||
|
|
# A. By an angle at each center
|
||
|
|
# These angles are either absolute angles with origin at the positive x-axis
|
||
|
|
# or the relative angle with origin at the straight connection line
|
||
|
|
# B. By the (expected) largest normal distance between line and arc: absbulge
|
||
|
|
# and/or by the (expected) bulge relative to the length of the
|
||
|
|
# straight line from center to center.
|
||
|
|
# Here, we need both informations.
|
||
|
|
#
|
||
|
|
# a curve with relbulge=0.39 and relangle1,2=45 leads
|
||
|
|
# approximately to the arc with angle=45
|
||
|
|
|
||
|
|
self.box1 = box1
|
||
|
|
self.box2 = box2
|
||
|
|
|
||
|
|
rel = (self.box2.center[0] - self.box1.center[0],
|
||
|
|
self.box2.center[1] - self.box1.center[1])
|
||
|
|
distance = hypot(*rel)
|
||
|
|
# absolute angle of the straight connection
|
||
|
|
dangle = atan2(rel[1], rel[0])
|
||
|
|
|
||
|
|
# calculate the armlength and absolute angles for the control points:
|
||
|
|
# absolute and relative bulges are added
|
||
|
|
bulge = abs(distance*relbulge + absbulge)
|
||
|
|
|
||
|
|
if absangle1 is not None:
|
||
|
|
angle1 = radians(absangle1)
|
||
|
|
else:
|
||
|
|
angle1 = dangle + radians(relangle1)
|
||
|
|
if absangle2 is not None:
|
||
|
|
angle2 = radians(absangle2)
|
||
|
|
else:
|
||
|
|
angle2 = dangle + radians(relangle2)
|
||
|
|
|
||
|
|
# get the control points
|
||
|
|
control1 = (cos(angle1), sin(angle1))
|
||
|
|
control2 = (cos(angle2), sin(angle2))
|
||
|
|
control1 = (self.box1.center[0] + control1[0] * bulge, self.box1.center[1] + control1[1] * bulge)
|
||
|
|
control2 = (self.box2.center[0] - control2[0] * bulge, self.box2.center[1] - control2[1] * bulge)
|
||
|
|
|
||
|
|
connector_pt.__init__(self,
|
||
|
|
[path.normsubpath([path.normcurve_pt(*(self.box1.center +
|
||
|
|
control1 +
|
||
|
|
control2 + self.box2.center))], 0)])
|
||
|
|
|
||
|
|
self.omitends(box1, box2)
|
||
|
|
self.shortenpath(boxdists)
|
||
|
|
|
||
|
|
|
||
|
|
class twolines_pt(connector_pt):
|
||
|
|
|
||
|
|
def __init__(self, box1, box2,
|
||
|
|
absangle1=None, absangle2=None,
|
||
|
|
relangle1=None, relangle2=None, relangleM=None,
|
||
|
|
length1=None, length2=None,
|
||
|
|
bezierradius=None, beziersoftness=1,
|
||
|
|
arcradius=None,
|
||
|
|
boxdists=[0,0]):
|
||
|
|
|
||
|
|
# The connection with two lines can be done in the following ways:
|
||
|
|
# 1. an angle at each box-center
|
||
|
|
# 2. two armlengths (if they are long enough)
|
||
|
|
# 3. angle and armlength at the same box
|
||
|
|
# 4. angle and armlength at different boxes
|
||
|
|
# 5. one armlength and the angle between the arms
|
||
|
|
#
|
||
|
|
# Angles at the box-centers can be relative or absolute
|
||
|
|
# The angle in the middle is always relative
|
||
|
|
# lengths are always absolute
|
||
|
|
|
||
|
|
self.box1 = box1
|
||
|
|
self.box2 = box2
|
||
|
|
|
||
|
|
begin = self.box1.center
|
||
|
|
end = self.box2.center
|
||
|
|
rel = (self.box2.center[0] - self.box1.center[0],
|
||
|
|
self.box2.center[1] - self.box1.center[1])
|
||
|
|
distance = hypot(*rel)
|
||
|
|
dangle = atan2(rel[1], rel[0])
|
||
|
|
|
||
|
|
# find out what arguments are given:
|
||
|
|
if relangle1 is not None: relangle1 = radians(relangle1)
|
||
|
|
if relangle2 is not None: relangle2 = radians(relangle2)
|
||
|
|
if relangleM is not None: relangleM = radians(relangleM)
|
||
|
|
# absangle has priority over relangle:
|
||
|
|
if absangle1 is not None: relangle1 = dangle - radians(absangle1)
|
||
|
|
if absangle2 is not None: relangle2 = math.pi - dangle + radians(absangle2)
|
||
|
|
|
||
|
|
# check integrity of arguments
|
||
|
|
no_angles, no_lengths=0,0
|
||
|
|
for anangle in (relangle1, relangle2, relangleM):
|
||
|
|
if anangle is not None: no_angles += 1
|
||
|
|
for alength in (length1, length2):
|
||
|
|
if alength is not None: no_lengths += 1
|
||
|
|
|
||
|
|
if no_angles + no_lengths != 2:
|
||
|
|
raise NotImplementedError, "Please specify exactly two angles or lengths"
|
||
|
|
|
||
|
|
# calculate necessary angles and armlengths
|
||
|
|
# always length1 and relangle1
|
||
|
|
|
||
|
|
# the case with two given angles
|
||
|
|
# use the "sine-theorem" for calculating length1
|
||
|
|
if no_angles == 2:
|
||
|
|
if relangle1 is None: relangle1 = math.pi - relangle2 - relangleM
|
||
|
|
elif relangle2 is None: relangle2 = math.pi - relangle1 - relangleM
|
||
|
|
elif relangleM is None: relangleM = math.pi - relangle1 - relangle2
|
||
|
|
length1 = distance * abs(sin(relangle2)/sin(relangleM))
|
||
|
|
middle = self._middle_a(begin, dangle, length1, relangle1)
|
||
|
|
# the case with two given lengths
|
||
|
|
# uses the "cosine-theorem" for calculating length1
|
||
|
|
elif no_lengths == 2:
|
||
|
|
relangle1 = acos((distance**2 + length1**2 - length2**2) / (2.0*distance*length1))
|
||
|
|
middle = self._middle_a(begin, dangle, length1, relangle1)
|
||
|
|
# the case with one length and one angle
|
||
|
|
else:
|
||
|
|
if relangle1 is not None:
|
||
|
|
if length1 is not None:
|
||
|
|
middle = self._middle_a(begin, dangle, length1, relangle1)
|
||
|
|
elif length2 is not None:
|
||
|
|
length1 = self._missinglength(length2, distance, relangle1)
|
||
|
|
middle = self._middle_a(begin, dangle, length1, relangle1)
|
||
|
|
elif relangle2 is not None:
|
||
|
|
if length1 is not None:
|
||
|
|
length2 = self._missinglength(length1, distance, relangle2)
|
||
|
|
middle = self._middle_b(end, dangle, length2, relangle2)
|
||
|
|
elif length2 is not None:
|
||
|
|
middle = self._middle_b(end, dangle, length2, relangle2)
|
||
|
|
elif relangleM is not None:
|
||
|
|
if length1 is not None:
|
||
|
|
length2 = self._missinglength(distance, length1, relangleM)
|
||
|
|
relangle1 = acos((distance**2 + length1**2 - length2**2) / (2.0*distance*length1))
|
||
|
|
middle = self._middle_a(begin, dangle, length1, relangle1)
|
||
|
|
elif length2 is not None:
|
||
|
|
length1 = self._missinglength(distance, length2, relangleM)
|
||
|
|
relangle1 = acos((distance**2 + length1**2 - length2**2) / (2.0*distance*length1))
|
||
|
|
middle = self._middle_a(begin, dangle, length1, relangle1)
|
||
|
|
else:
|
||
|
|
raise NotImplementedError, "I found a strange combination of arguments"
|
||
|
|
|
||
|
|
connectorpath = path.path(path.moveto_pt(*self.box1.center),
|
||
|
|
path.lineto_pt(*middle),
|
||
|
|
path.lineto_pt(*self.box2.center))
|
||
|
|
connector_pt.__init__(self, connectorpath.normpath().normsubpaths)
|
||
|
|
|
||
|
|
self.omitends(box1, box2)
|
||
|
|
self.shortenpath(boxdists)
|
||
|
|
|
||
|
|
def _middle_a(self, begin, dangle, length1, angle1):
|
||
|
|
a = dangle - angle1
|
||
|
|
dir = cos(a), sin(a)
|
||
|
|
return begin[0] + length1*dir[0], begin[1] + length1*dir[1]
|
||
|
|
|
||
|
|
def _middle_b(self, end, dangle, length2, angle2):
|
||
|
|
# a = -math.pi + dangle + angle2
|
||
|
|
return self._middle_a(end, -math.pi+dangle, length2, -angle2)
|
||
|
|
|
||
|
|
def _missinglength(self, lenA, lenB, angleA):
|
||
|
|
# calculate lenC, where side A and angleA are opposite
|
||
|
|
tmp1 = lenB * cos(angleA)
|
||
|
|
tmp2 = sqrt(tmp1**2 - lenB**2 + lenA**2)
|
||
|
|
if tmp1 > tmp2: return tmp1 - tmp2
|
||
|
|
return tmp1 + tmp2
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
class line(line_pt):
|
||
|
|
|
||
|
|
"""a line is the straight connector between the centers of two boxes"""
|
||
|
|
|
||
|
|
def __init__(self, box1, box2, boxdists=(0,0)):
|
||
|
|
line_pt.__init__(self, box1, box2, boxdists=map(unit.topt, boxdists))
|
||
|
|
|
||
|
|
|
||
|
|
class curve(curve_pt):
|
||
|
|
|
||
|
|
"""a curve is the curved connector between the centers of two boxes.
|
||
|
|
The constructor needs both angle and bulge"""
|
||
|
|
|
||
|
|
|
||
|
|
def __init__(self, box1, box2,
|
||
|
|
relangle1=45, relangle2=45,
|
||
|
|
absangle1=None, absangle2=None,
|
||
|
|
absbulge=0, relbulge=0.39,
|
||
|
|
boxdists=[0,0]):
|
||
|
|
curve_pt.__init__(self, box1, box2,
|
||
|
|
relangle1=relangle1, relangle2=relangle2,
|
||
|
|
absangle1=absangle1, absangle2=absangle2,
|
||
|
|
absbulge=unit.topt(absbulge), relbulge=relbulge,
|
||
|
|
boxdists=map(unit.topt, boxdists))
|
||
|
|
|
||
|
|
class arc(arc_pt):
|
||
|
|
|
||
|
|
"""an arc is a round connector between the centers of two boxes.
|
||
|
|
The constructor gets
|
||
|
|
either an angle in (-pi,pi)
|
||
|
|
or a bulge parameter in (-distance, distance)
|
||
|
|
(relbulge and absbulge are added)"""
|
||
|
|
|
||
|
|
def __init__(self, box1, box2, relangle=45,
|
||
|
|
absbulge=None, relbulge=None, boxdists=[0,0]):
|
||
|
|
if absbulge is not None:
|
||
|
|
absbulge = unit.topt(absbulge)
|
||
|
|
arc_pt.__init__(self, box1, box2,
|
||
|
|
relangle=relangle,
|
||
|
|
absbulge=absbulge, relbulge=relbulge,
|
||
|
|
boxdists=map(unit.topt, boxdists))
|
||
|
|
|
||
|
|
|
||
|
|
class twolines(twolines_pt):
|
||
|
|
|
||
|
|
"""a twolines is a connector consisting of two straight lines.
|
||
|
|
The construcor takes a combination of angles and lengths:
|
||
|
|
either two angles (relative or absolute)
|
||
|
|
or two lenghts
|
||
|
|
or one length and one angle"""
|
||
|
|
|
||
|
|
def __init__(self, box1, box2,
|
||
|
|
absangle1=None, absangle2=None,
|
||
|
|
relangle1=None, relangle2=None, relangleM=None,
|
||
|
|
length1=None, length2=None,
|
||
|
|
bezierradius=None, beziersoftness=1,
|
||
|
|
arcradius=None,
|
||
|
|
boxdists=[0,0]):
|
||
|
|
if length1 is not None:
|
||
|
|
length1 = unit.topt(length1)
|
||
|
|
if length2 is not None:
|
||
|
|
length2 = unit.topt(length2)
|
||
|
|
if bezierradius is not None:
|
||
|
|
bezierradius = unit.topt(bezierradius)
|
||
|
|
if arcradius is not None:
|
||
|
|
arcradius = unit.topt(arcradius)
|
||
|
|
twolines_pt.__init__(self, box1, box2,
|
||
|
|
absangle1=absangle1, absangle2=absangle2,
|
||
|
|
relangle1=relangle1, relangle2=relangle2,
|
||
|
|
relangleM=relangleM,
|
||
|
|
length1=length1, length2=length2,
|
||
|
|
bezierradius=bezierradius, beziersoftness=1,
|
||
|
|
arcradius=arcradius,
|
||
|
|
boxdists=map(unit.topt, boxdists))
|
||
|
|
|
||
|
|
|
||
|
|
|