summaryrefslogtreecommitdiff
path: root/gerbonara
diff options
context:
space:
mode:
Diffstat (limited to 'gerbonara')
-rw-r--r--gerbonara/cad/kicad/base_types.py15
-rw-r--r--gerbonara/cad/kicad/symbols.py90
-rw-r--r--gerbonara/newstroke.py72
3 files changed, 123 insertions, 54 deletions
diff --git a/gerbonara/cad/kicad/base_types.py b/gerbonara/cad/kicad/base_types.py
index d29586e..3d0b0c4 100644
--- a/gerbonara/cad/kicad/base_types.py
+++ b/gerbonara/cad/kicad/base_types.py
@@ -270,6 +270,20 @@ class Justify:
v: AtomChoice(Atom.top, Atom.bottom) = None
mirror: Flag() = False
+ @property
+ def h_str(self):
+ if self.h is None:
+ return 'center'
+ else:
+ return str(self.h)
+
+ @property
+ def v_str(self):
+ if self.v is None:
+ return 'middle'
+ else:
+ return str(self.v)
+
@sexp_type('effects')
class TextEffect:
@@ -277,6 +291,7 @@ class TextEffect:
hide: Flag() = False
justify: OmitDefault(Justify) = field(default_factory=Justify)
+
class TextMixin:
@property
def size(self):
diff --git a/gerbonara/cad/kicad/symbols.py b/gerbonara/cad/kicad/symbols.py
index 4767b88..94d30d3 100644
--- a/gerbonara/cad/kicad/symbols.py
+++ b/gerbonara/cad/kicad/symbols.py
@@ -54,6 +54,11 @@ class Pin:
name: Rename(StyledText) = field(default_factory=StyledText)
number: Rename(StyledText) = field(default_factory=StyledText)
alternates: List(AltFunction) = field(default_factory=list)
+ _: SEXP_END = None
+ unit: object = None
+
+ def __after_parse__(self, parent=None):
+ self.unit = parent
@property
def direction(self):
@@ -96,6 +101,9 @@ class Pin:
return (x1, y1), (x2, y2)
def to_svg(self, colorscheme=Colorscheme.KiCad):
+ if self.hide:
+ return
+
x1, y1 = 0, 0
x2, y2 = self.length, 0
xform = {'transform': f'translate({self.at.x:.3f} {self.at.y:.3f}) rotate({self.at.rotation})'}
@@ -109,68 +117,52 @@ class Pin:
'inverted': [
Tag('circle', **xform, **style, cx=x2-eps/3-0.2, cy=y2, r=eps/3)],
'clock': [
- Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')],
'inverted_clock': [
Tag('circle', **xform, **style, cx=x2-eps/3-0.2, cy=y2, r=eps/3),
- Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')],
'input_low': [
- Tag('path', **xform, **style, d=f'M {x2} {y2} L {x2-eps} {y2-eps} L {x2-eps} {y2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2} {y2} L {x2-eps} {y2-eps} L {x2-eps} {y2}')],
'clock_low': [
- Tag('path', **xform, **style, d=f'M {x2} {y2} L {x2-eps} {y2-eps} L {x2-eps} {y2}'), # NOQA: E501
- Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2} {y2} L {x2-eps} {y2-eps} L {x2-eps} {y2}'),
+ Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')],
'output_low': [
- Tag('path', **xform, **style, d=f'M {x2} {y2-eps} L {x2-eps} {y2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2} {y2-eps} L {x2-eps} {y2}')],
'edge_clock_high': [
- Tag('path', **xform, **style, d=f'M {x2} {y2} L {x2-eps} {y2-eps} L {x2-eps} {y2}'), # NOQA: E501
- Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2} {y2} L {x2-eps} {y2-eps} L {x2-eps} {y2}'),
+ Tag('path', **xform, **style, d=f'M {x2} {y2-eps/2} L {x2+eps/2} {y2} L {x2} {y2+eps/2}')],
'non_logic': [
- Tag('path', **xform, **style, d=f'M {x2-eps/2} {y2-eps/2} L {x2+eps/2} {y2+eps/2}'), # NOQA: E501
- Tag('path', **xform, **style, d=f'M {x2-eps/2} {y2+eps/2} L {x2+eps/2} {y2-eps/2}')], # NOQA: E501
+ Tag('path', **xform, **style, d=f'M {x2-eps/2} {y2-eps/2} L {x2+eps/2} {y2+eps/2}'),
+ Tag('path', **xform, **style, d=f'M {x2-eps/2} {y2+eps/2} L {x2+eps/2} {y2-eps/2}')],
# FIXME...
}.get(self.style, []):
yield tag
- if self.at.rotation in (90, 270):
- t_rot = 90
- else:
- t_rot = 0
-
- size = self.name.effects.font.size.y or 1.27
font = Newstroke.load()
- strokes = list(font.render(self.name.value, size=size))
- min_x = min(x for st in strokes for x, y in st) if strokes else 0
- min_y = min(y for st in strokes for x, y in st) if strokes else 0
- max_x = max(x for st in strokes for x, y in st) if strokes else 0
- max_y = max(y for st in strokes for x, y in st) if strokes else 0
- w = max_x - min_x
- h = max_y - min_y
-
- if self.at.rotation == 0:
- offx = -min_x + self.length + 0.2
- offy = h/2
- elif self.at.rotation == 180:
- offx = min_x - self.length - 0.2 - w
- offy = h/2
- elif self.at.rotation == 90:
- offx = -h/2
- offy = min_x - self.length - 0.2 - w
- elif self.at.rotation == 270:
- offx = -h/2
- offy = -min_x + self.length + 0.2
- else:
- raise ValueError(f'Invalid pin rotation {self.at.rotation}')
+ if self.name.value != '~' and not self.unit.symbol.pin_names.hide:
+ yield font.render_svg(self.name.value,
+ size=self.name.effects.font.size.y or 1.27,
+ x0=self.length + 0.2,
+ y0=0,
+ h_align='left',
+ v_align='middle',
+ rotation=self.at.rotation,
+ stroke=colorscheme.text,
+ transform=f'translate({self.at.x:.3f} {self.at.y:.3f})',
+ )
+
+ if self.number.value != '~' and not self.unit.symbol.pin_numbers.hide:
+ yield font.render_svg(self.number.value,
+ size=self.number.effects.font.size.y or 1.27,
+ x0=self.length-0.2,
+ y0=0.4,
+ h_align='right',
+ v_align='bottom',
+ rotation=self.at.rotation,
+ stroke=colorscheme.text,
+ transform=f'translate({self.at.x:.3f} {self.at.y:.3f})',
+ )
- d = []
- for stroke in strokes:
- points = []
- for x, y in stroke:
- x, y = x+offx, y+offy
- x, y = rotate_point(x, y, math.radians(self.at.rotation or 0))
- x, y = x+self.at.x, y+self.at.y
- points.append(f'{x:.3f} {y:.3f}')
- d.append('M '+ ' L '.join(points) + ' ')
- yield Tag('path', d=' '.join(d), fill='none', stroke=colorscheme.text, stroke_width='0.254', stroke_linecap='round', stroke_linejoin='round')
- print('name', self.name.value)
@sexp_type('fill')
diff --git a/gerbonara/newstroke.py b/gerbonara/newstroke.py
index b48476a..247b674 100644
--- a/gerbonara/newstroke.py
+++ b/gerbonara/newstroke.py
@@ -5,9 +5,11 @@ import unicodedata
import re
import ast
from functools import lru_cache
+import math
from importlib.resources import files
from . import data
+from .utils import rotate_point, Tag
STROKE_FONT_SCALE = 1/21
@@ -29,10 +31,39 @@ class Newstroke:
def load(kls):
return kls()
- def render(self, text, size=1.0, space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP):
+ def render(self, text, size=1.0, x0=0, y0=0, rotation=0, h_align='left', v_align='bottom', space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP, scale=(1, 1)):
text = unicodedata.normalize('NFC', text)
missing_glyph = self.glyphs['?']
+ sx, sy = scale
x = 0
+ if text in ('VDDA', 'PA9', 'VSS'):
+ print(text, x0, y0, rotation, h_align, v_align, scale)
+
+ if rotation >= 180:
+ rotation -= 180
+ h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align)
+ x0, y0 = -x0, y0
+
+ x0, y0 = rotate_point(x0, y0, math.radians(-rotation))
+
+ alx, aly = 0, 0
+ if h_align != 'left':
+ (minx, miny), (maxx, maxy) = bbox = self.bounding_box(text, size, space_width, char_gap)
+ w = maxx - minx
+ if h_align == 'right':
+ alx = -w
+ elif h_align == 'center':
+ alx = -w/2
+ else:
+ raise ValueError(f'Invalid h_align value "{h_align}"')
+
+ if v_align == 'top':
+ aly = -1.2*size
+ elif v_align == 'middle':
+ aly = -1.2*size/2
+ elif v_align != 'bottom':
+ raise ValueError(f'Invalid v_align value "{v_align}"')
+
for c in text:
if c == ' ':
x += space_width*size
@@ -42,15 +73,46 @@ class Newstroke:
glyph_w = max(width, max(x for st in strokes for x, _y in st))
for st in strokes:
- yield self.transform_stroke(st, translate=(x, 0), scale=(size, size))
+ yield self.transform_stroke(st, translate=(x0, y0), offset=(x+alx, aly), rotation=math.radians(-rotation), scale=(sx*size, sy*size))
x += glyph_w*size
+ def render_svg(self, text, size=1.0, x0=0, y0=0, rotation=0, h_align='left', v_align='bottom', space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP, **svg_attrs):
+ if 'stroke_linecap' not in svg_attrs:
+ svg_attrs['stroke_linecap'] = 'round'
+ if 'stroke_linejoin' not in svg_attrs:
+ svg_attrs['stroke_linejoin'] = 'round'
+ if 'stroke_width' not in svg_attrs:
+ svg_attrs['stroke_width'] = f'{0.2*size:.3f}'
+ svg_attrs['fill'] = 'none'
+
+ strokes = ['M ' + ' L '.join(f'{x:.3f} {y:.3f}' for x, y in stroke)
+ for stroke in self.render(text, size=size, x0=x0, y0=y0, rotation=rotation, h_align=h_align,
+ v_align=v_align, space_width=space_width, char_gap=char_gap,
+ scale=(1, -1))]
+ return Tag('path', d=' '.join(strokes), **svg_attrs)
+
+ def bounding_box(self, text, size=1.0, space_width=DEFAULT_SPACE_WIDTH, char_gap=DEFAULT_CHAR_GAP):
+ text = unicodedata.normalize('NFC', text)
+ missing_glyph = self.glyphs['?']
+ x = 0
+ for c in text:
+ if c == ' ':
+ x += space_width*size
+ continue
+
+ width, strokes = self.glyphs.get(c, missing_glyph)
+ glyph_w = max(width, max(x for st in strokes for x, _y in st))
+ x += glyph_w*size
+
+ return (0, -0.2*size), (x, 1.2*size)
+
@classmethod
- def transform_stroke(kls, stroke, translate, scale):
- dx, dy = translate
+ def transform_stroke(kls, stroke, translate, offset, scale, rotation=0):
+ x0, y0 = translate
sx, sy = scale
- return [(x*sx+dx, y*sy+dy) for x, y in stroke]
+ dx, dy = offset
+ return [rotate_point(x*sx+dx+x0, y*sy+dy+y0, rotation, x0, y0) for x, y in stroke]
def load_font(self, newstroke_cpp):