summaryrefslogtreecommitdiff
path: root/gerbonara/cad/kicad/graphical_primitives.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerbonara/cad/kicad/graphical_primitives.py')
-rw-r--r--gerbonara/cad/kicad/graphical_primitives.py91
1 files changed, 57 insertions, 34 deletions
diff --git a/gerbonara/cad/kicad/graphical_primitives.py b/gerbonara/cad/kicad/graphical_primitives.py
index 94a61a4..f86fcc7 100644
--- a/gerbonara/cad/kicad/graphical_primitives.py
+++ b/gerbonara/cad/kicad/graphical_primitives.py
@@ -9,7 +9,7 @@ from .primitives import *
from ... import graphic_objects as go
from ... import apertures as ap
from ...newstroke import Newstroke
-from ...utils import rotate_point, MM
+from ...utils import rotate_point, MM, arc_bounds
@sexp_type('layer')
class TextLayer:
@@ -18,10 +18,11 @@ class TextLayer:
@sexp_type('gr_text')
-class Text(TextMixin):
+class Text(TextMixin, BBoxMixin):
text: str = ''
at: AtPos = field(default_factory=AtPos)
layer: TextLayer = field(default_factory=TextLayer)
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
effects: TextEffect = field(default_factory=TextEffect)
render_cache: RenderCache = None
@@ -31,14 +32,15 @@ class Text(TextMixin):
@sexp_type('gr_text_box')
-class TextBox:
+class TextBox(BBoxMixin):
locked: Flag() = False
text: str = ''
start: Named(XYCoord) = None
end: Named(XYCoord) = None
- pts: PointList = field(default_factory=PointList)
+ pts: PointList = field(default_factory=list)
angle: OmitDefault(Named(float)) = 0.0
layer: Named(str) = ""
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
effects: TextEffect = field(default_factory=TextEffect)
stroke: Stroke = field(default_factory=Stroke)
@@ -53,7 +55,7 @@ class TextBox:
raise ValueError('Vector font text with empty render cache')
for poly in render_cache.polygons:
- reg = go.Region([(p.x, -p.y) for p in poly.pts.xy], unit=MM)
+ reg = go.Region([(p.x, -p.y) for p in poly.pts], unit=MM)
if self.stroke:
if self.stroke.type not in (None, Atom.default, Atom.solid):
@@ -69,13 +71,14 @@ class TextBox:
@sexp_type('gr_line')
-class Line:
+class Line(WidthMixin):
start: Rename(XYCoord) = None
end: Rename(XYCoord) = None
angle: Named(float) = None # wat
layer: Named(str) = None
width: Named(float) = None
stroke: Stroke = field(default_factory=Stroke)
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
def rotate(self, angle, cx=None, cy=None):
@@ -98,6 +101,12 @@ class Line:
self.start = self.start.with_offset(x, y)
self.end = self.end.with_offset(x, y)
+ def bounding_box(self, unit=MM):
+ x_min, x_max = min(self.start.x, self.end.x), max(self.start.x, self.end.x)
+ y_min, y_max = min(self.start.y, self.end.y), max(self.start.y, self.end.y)
+ w = self.stroke.width if self.stroke else self.width
+ return (x_min-w, y_max-w), (x_max+w, y_max+w)
+
@sexp_type('fill')
class FillMode:
@@ -113,13 +122,14 @@ class FillMode:
yield [Atom.fill, Atom.solid if value else Atom.none]
@sexp_type('gr_rect')
-class Rectangle:
+class Rectangle(BBoxMixin, WidthMixin):
start: Rename(XYCoord) = None
end: Rename(XYCoord) = None
layer: Named(str) = None
width: Named(float) = None
stroke: Stroke = field(default_factory=Stroke)
fill: FillMode = False
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
def render(self, variables=None):
@@ -130,9 +140,9 @@ class Rectangle:
if self.fill:
yield rect
- if self.width:
+ if (w := self.stroke.width if self.stroke else self.width):
# FIXME stroke support
- yield from rect.outline_objects(aperture=ap.CircleAperture(self.width, unit=MM))
+ yield from rect.outline_objects(aperture=ap.CircleAperture(w, unit=MM))
@property
def top_left(self):
@@ -145,21 +155,23 @@ class Rectangle:
@sexp_type('gr_circle')
-class Circle:
+class Circle(BBoxMixin, WidthMixin):
center: Rename(XYCoord) = None
end: Rename(XYCoord) = None
layer: Named(str) = None
width: Named(float) = None
stroke: Stroke = field(default_factory=Stroke)
fill: FillMode = False
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
def render(self, variables=None):
r = math.dist((self.center.x, -self.center.y), (self.end.x, -self.end.y))
- aperture = ap.CircleAperture(self.width or 0, unit=MM)
+ w = self.stroke.width if self.stroke else self.width
+ aperture = ap.CircleAperture(w or 0, unit=MM)
arc = go.Arc.from_circle(self.center.x, -self.center.y, r, aperture=aperture, unit=MM)
- if self.width:
+ if w:
# FIXME stroke support
yield arc
@@ -170,15 +182,20 @@ class Circle:
self.center = self.center.with_offset(x, y)
self.end = self.end.with_offset(x, y)
+ def rotate(self, angle, cx=0, cy=0):
+ self.center = self.center.with_rotation(angle, cx, cy)
+ self.end = self.end.with_rotation(angle, cx, cy)
+
@sexp_type('gr_arc')
-class Arc:
+class Arc(WidthMixin, BBoxMixin):
start: Rename(XYCoord) = None
mid: Rename(XYCoord) = None
end: Rename(XYCoord) = None
layer: Named(str) = None
width: Named(float) = None
stroke: Stroke = field(default_factory=Stroke)
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
_: SEXP_END = None
center: XYCoord = None
@@ -192,35 +209,35 @@ class Arc:
self.mid = center_arc_to_kicad_mid(XYCoord(self.center), self.start, self.end)
self.center = None
- def rotate(self, angle, cx=None, cy=None):
- self.start.x, self.start.y = rotate_point(self.start.x, self.start.y, angle, cx, cy)
- self.mid.x, self.mid.y = rotate_point(self.mid.x, self.mid.y, angle, cx, cy)
- self.end.x, self.end.y = rotate_point(self.end.x, self.end.y, angle, cx, cy)
-
def render(self, variables=None):
- # FIXME stroke support
- if not self.width:
+ if not (w := self.stroke.width if self.stroke else self.width):
return
- aperture = ap.CircleAperture(self.width, unit=MM)
+ aperture = ap.CircleAperture(w, unit=MM)
x1, y1 = self.start.x, self.start.y
x2, y2 = self.end.x, self.end.y
- (cx, cy), _r = kicad_mid_to_center_arc(self.mid, self.start, self.end)
- yield go.Arc(x1, -y1, x2, -y2, cx-x1, -(cy-y1), aperture=aperture, clockwise=False, unit=MM)
+ (cx, cy), _r, clockwise = kicad_mid_to_center_arc(self.mid, self.start, self.end)
+ yield go.Arc(x1, -y1, x2, -y2, cx-x1, -(cy-y1), aperture=aperture, clockwise=not clockwise, unit=MM)
def offset(self, x=0, y=0):
self.start = self.start.with_offset(x, y)
self.mid = self.mid.with_offset(x, y)
self.end = self.end.with_offset(x, y)
+ def rotate(self, angle, cx=None, cy=None):
+ self.start.x, self.start.y = rotate_point(self.start.x, self.start.y, angle, cx, cy)
+ self.mid.x, self.mid.y = rotate_point(self.mid.x, self.mid.y, angle, cx, cy)
+ self.end.x, self.end.y = rotate_point(self.end.x, self.end.y, angle, cx, cy)
+
@sexp_type('gr_poly')
-class Polygon:
+class Polygon(BBoxMixin, WidthMixin):
pts: ArcPointList = field(default_factory=list)
layer: Named(str) = None
width: Named(float) = None
stroke: Stroke = field(default_factory=Stroke)
fill: FillMode = True
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
def render(self, variables=None):
@@ -236,35 +253,40 @@ class Polygon:
else: # base_types.Arc
points.append((point_or_arc.start.x, -point_or_arc.start.y))
points.append((point_or_arc.end.x, -point_or_arc.end.y))
- (cx, cy), _r = kicad_mid_to_center_arc(point_or_arc.mid, point_or_arc.start, point_or_arc.end)
- centers.append((False, (cx, -cy)))
+ (cx, cy), _r, clockwise = kicad_mid_to_center_arc(point_or_arc.mid, point_or_arc.start, point_or_arc.end)
+ centers.append((not clockwise, (cx, -cy)))
reg = go.Region(points, centers, unit=MM)
reg.close()
+ w = self.stroke.width if self.stroke else self.width
# FIXME stroke support
- if self.width and self.width >= 0.005 or self.stroke.width and self.stroke.width > 0.005:
- yield from reg.outline_objects(aperture=ap.CircleAperture(self.width, unit=MM))
+ if w and w >= 0.005:
+ yield from reg.outline_objects(aperture=ap.CircleAperture(w, unit=MM))
if self.fill:
yield reg
def offset(self, x=0, y=0):
- self.pts = PointList([pt.with_offset(x, y) for pt in self.pts])
+ self.pts = [pt.with_offset(x, y) for pt in self.pts]
+
+ def rotate(self, angle, cx=0, cy=0):
+ self.pts = [pt.with_rotation(angle, cx, cy) for pt in self.pts]
@sexp_type('gr_curve')
-class Curve:
- pts: PointList = field(default_factory=PointList)
+class Curve(BBoxMixin, WidthMixin):
+ pts: PointList = field(default_factory=list)
layer: Named(str) = None
width: Named(float) = None
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = None
def render(self, variables=None):
raise NotImplementedError('Bezier rendering is not yet supported. Please raise an issue and provide an example file.')
def offset(self, x=0, y=0):
- self.pts = PointList([pt.with_offset(x, y) for pt in self.pts])
+ self.pts =[pt.with_offset(x, y) for pt in self.pts]
@sexp_type('gr_bbox')
@@ -319,8 +341,9 @@ class Dimension:
locked: Flag() = False
dimension_type: Named(AtomChoice(Atom.aligned, Atom.leader, Atom.center, Atom.orthogonal, Atom.radial), name='type') = Atom.aligned
layer: Named(str) = 'Dwgs.User'
+ uuid: UUID = field(default_factory=UUID)
tstamp: Timestamp = field(default_factory=Timestamp)
- pts: PointList = field(default_factory=PointList)
+ pts: PointList = field(default_factory=list)
height: Named(float) = None
orientation: Named(int) = None
leader_length: Named(float) = None
@@ -332,5 +355,5 @@ class Dimension:
raise NotImplementedError('Dimension rendering is not yet supported. Please raise an issue.')
def offset(self, x=0, y=0):
- self.pts = PointList([pt.with_offset(x, y) for pt in self.pts])
+ self.pts = [pt.with_offset(x, y) for pt in self.pts]