This commit is contained in:
jaseg 2023-07-21 17:56:24 +02:00
parent 09c9d26728
commit 91b99a0452
4 changed files with 69 additions and 96 deletions

View file

@ -333,6 +333,10 @@ class TextMixin:
def h_align(self):
return 'left' if self.effects.justify.h else 'center'
@property
def mirrored(self):
return False, False
def to_svg(self, color='black', variables={}):
if not self.effects or self.effects.hide or not self.effects.font:
return
@ -344,12 +348,13 @@ class TextMixin:
print(text, self.rotation, self.at, self.effects)
yield font.render_svg(text,
size=self.size or 1.27,
rotation=self.rotation,
h_align=self.h_align,
v_align=self.effects.justify.v or self.default_v_align,
stroke=color,
stroke_width=f'{self.line_width:.3f}',
scale=(1,1),
rotation=self.rotation,
mirror=self.mirrored,
transform=f'translate({self.at.x:.3f} {self.at.y:.3f})',
)

View file

@ -3,6 +3,7 @@ Library for handling KiCad's schematic files (`*.kicad_sch`).
"""
import math
import string
from pathlib import Path
from dataclasses import field, KW_ONLY
from itertools import chain
@ -296,7 +297,10 @@ class DrawnProperty(TextMixin):
# Alias value for text mixin
@property
def text(self):
return self.value
if self.key == 'Reference' and self.parent.unit > 0:
return f'{self.value}{string.ascii_uppercase[self.parent.unit-1]}'
else:
return self.value
@text.setter
def text(self, value):
@ -308,25 +312,20 @@ class DrawnProperty(TextMixin):
@property
def h_align(self):
j = self.effects.justify.h_str
if False: #self.at.rotation in (270):
return {'left': 'right', 'right': 'left'}.get(j, j)
else:
return j
return self.effects.justify.h_str
@property
def rotation(self):
rot = -self.at.rotation
rot += getattr(self.parent.at, 'rotation', 0)
if getattr(self.parent, 'reference', None) == 'C13':
print(self.value, self.at, self.parent.at, self.parent.mirror)
if hasattr(self.parent, 'mirror'):
if self.parent.mirror.y and rot in (90, 270):
rot = (rot+180)%360
if self.parent.mirror.x and rot in (0, 180):
rot = (rot+180)%360
return rot%360
@property
def mirrored(self):
if hasattr(self.parent, 'mirror'):
return self.parent.mirror.x, self.parent.mirror.y
return False, False
def to_svg(self, colorscheme=Colorscheme.KiCad):
if not self.hide:
yield from TextMixin.to_svg(self, colorscheme.text)
@ -396,28 +395,29 @@ class SymbolInstance:
sym = self.schematic.lookup_symbol(self.lib_name, self.lib_id)
name = f'{sym.name}_0_1'
if name in sym.global_units.get(1, {}):
for elem in sym.global_units[1][name].graphical_elements:
children += elem.to_svg(colorscheme)
units = [unit for unit in sym.units if unit.unit_global or unit.unit_index == self.unit]
name = f'{sym.name}_{self.unit}_1'
if name in sym.styles.get(1, {}):
for elem in sym.styles[1][name].graphical_elements:
children += elem.to_svg(colorscheme)
if self.reference in ('U18',):
print(self.reference, self.unit, self.at, self.mirror, units)
xform = f'translate({self.at.x:.3f} {self.at.y:.3f})'
if rot:
xform += f'rotate({-rot})'
if self.mirror.x:
if self.mirror.y:
xform += f'scale(-1 -1)'
elif self.mirror.x:
xform += f'scale(-1 1)'
if not self.mirror.y:
else:
xform += f'scale(1 -1)'
if rot:
xform += f'rotate({rot})'
children = [foo for unit in units for elem in unit.graphical_elements for foo in elem.to_svg(colorscheme)]
yield Tag('g', children=children, transform=xform, fill=colorscheme.fill, stroke=colorscheme.lines)
for prop in self.properties:
yield from prop.to_svg()
children = [foo for unit in units for pin in unit.pins for foo in pin.to_svg(colorscheme, self.mirror, rot)]
yield Tag('g', children=children, transform=xform, fill=colorscheme.fill, stroke=colorscheme.lines)
#for prop in self.properties:
# yield from prop.to_svg()
@sexp_type('path')

View file

@ -100,16 +100,20 @@ class Pin:
return (x1, y1), (x2, y2)
def to_svg(self, colorscheme=Colorscheme.KiCad):
def to_svg(self, colorscheme, p_mirror, p_rotation):
if self.hide:
return
x1, y1 = 0, 0
x2, y2 = self.length, 0
if self.name.value in ('PA3', 'QA'):
print(self.name.value, self.at, p_rotation)
psx, psy = (-1 if p_mirror.x else 1), (-1 if p_mirror.y else 1)
x1, y1 = self.at.x, self.at.y
x2, y2 = self.at.x+self.length, self.at.y
xform = {'transform': f'translate({self.at.x:.3f} {self.at.y:.3f}) rotate({self.at.rotation})'}
style = {'stroke_width': 0.254, 'stroke': colorscheme.lines, 'stroke_linecap': 'round'}
yield Tag('path', **xform, **style, d=f'M 0 0 L {self.length:.3f} 0')
return
eps = 1
for tag in {
@ -403,7 +407,6 @@ class Unit:
pins: List(Pin) = field(default_factory=list)
unit_name: Named(str) = None
_ : SEXP_END = None
global_units: list = field(default_factory=list)
unit_global: Flag() = False
style_global: Flag() = False
demorgan_style: int = 1
@ -420,7 +423,7 @@ class Unit:
raise FormatError(f'Unit name "{self.name}" does not match symbol name "{self.symbol.name}"')
self.demorgan_style = int(demorgan_style)
self.unit_index = int(unit_index)
self.style_global = self._demorgan_style == 0
self.style_global = self.demorgan_style == 0
self.unit_global = self.unit_index == 0
@property
@ -430,16 +433,10 @@ class Unit:
yield from self.polylines
yield from self.rectangles
yield from self.texts
yield from self.pins
def __before_sexp__(self):
self.name = f'{self.symbol.name}_{self.unit_index}_{self.demorgan_style}'
def __getattr__(self, name):
if name.startswith('all_'):
name = name[4:]
return itertools.chain(getattr(self.global_units, name, []), getattr(self, name, []))
def pin_stacks(self):
stacks = defaultdict(lambda: set())
for pin in self.all_pins():
@ -457,10 +454,8 @@ class Symbol:
in_bom: Named(YesNoAtom()) = True
on_board: Named(YesNoAtom()) = True
properties: List(Property) = field(default_factory=list)
raw_units: List(Unit) = field(default_factory=list)
units: List(Unit) = field(default_factory=list)
_ : SEXP_END = None
styles: {str: {str: Unit}} = None
global_units: {str: {str: Unit}} = None
library = None
name: str = None
library_name: str = None
@ -469,8 +464,6 @@ class Symbol:
self.library = parent
self.library_name, _, self.name = self.raw_name.rpartition(':')
self.global_units = {}
self.styles = {}
if self.extends:
self.in_bom = None
@ -480,29 +473,7 @@ class Symbol:
if (prop := self.properties.get('ki_fp_filters')):
prop.value = prop.value.split() if prop.value else []
for unit in self.raw_units:
if unit.unit_global or unit.style_global:
d = self.global_units.get(unit.demorgan_style, {})
d[unit.name] = unit
self.global_units[unit.demorgan_style] = d
for other in self.raw_units:
if other.unit_global or other.style_global or other == unit:
continue
if not (unit.unit_global or other.name == unit.name):
continue
if not (unit.style_global or other.demorgan_style == unit.demorgan_style):
continue
other.global_units.append(unit)
else:
d = self.styles.get(unit.demorgan_style, {})
d[unit.name] = unit
self.styles[unit.demorgan_style] = d
def __before_sexp__(self):
self.raw_units = ([unit for style in self.global_units.values() for unit in style.values()] +
[unit for style in self.styles.values() for unit in style.values()])
if (prop := self.properties.get('ki_fp_filters')):
if not isinstance(prop.value, str):
prop.value = ' '.join(prop.value)
@ -521,30 +492,11 @@ class Symbol:
]):
self.properties[name] = Property(name=name, value=value, id=i, effects=TextEffect(hide=hide))
def units(self, demorgan_style=None):
def resolve(self):
if self.extends:
return self.library[self.extends].units(demorgan_style)
return self.library[self.extends]
else:
return self.styles.get(demorgan_style or 'default', {})
def get_center_rectangle(self, units):
# return a polyline for the requested unit that is a rectangle
# and is closest to the center
candidates = {}
# building a dict with floats as keys.. there needs to be a rule against that^^
pl_rects = [i.to_polyline() for i in self.rectangles]
pl_rects.extend(pl for pl in self.polylines if pl.is_rectangle())
for pl in pl_rects:
if pl.unit in units:
# extract the center, calculate the distance to origin
(x, y) = pl.get_center_of_boundingbox()
dist = math.sqrt(x * x + y * y)
candidates[dist] = pl
if candidates:
# sort the list return the first (smallest) item
return candidates[sorted(candidates.keys())[0]]
return None
return self
def is_graphic_symbol(self):
return self.extends is None and (
@ -565,10 +517,6 @@ class Symbol:
pins[pin.number].add(pin)
return pins
def __getattr__(self, name):
if name.startswith('all_'):
return itertools.chain(getattr(unit, name) for unit in self.raw_units)
def filter_pins(self, name=None, direction=None, electrical_type=None):
for pin in self.all_pins:
if name and not fnmatch(pin.name, name):

View file

@ -31,12 +31,13 @@ class Newstroke:
def load(kls):
return kls()
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)):
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), mirror=(False, False)):
text = unicodedata.normalize('NFC', text)
missing_glyph = self.glyphs['?']
sx, sy = scale
mx, my = mirror
x = 0
if text in ('VDDA', 'PA9', 'VSS'):
if text in ('VDDA', 'PA9', 'VSS', 'FB3'):
print(text, x0, y0, rotation, h_align, v_align, scale)
if rotation >= 180:
@ -44,6 +45,25 @@ class Newstroke:
h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align)
x0, y0 = -x0, y0
if scale == (1, 1) and rotation == 90:
rotation = 270
h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align)
v_align = {'top': 'bottom', 'bottom': 'top'}.get(v_align, v_align)
#if mx:
# x0 = -x0
# if rotation == 90:
# v_align = {'top': 'bottom', 'bottom': 'top'}.get(v_align, v_align)
# else:
# h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align)
if my:
y0 = -y0
if rotation == 0:
v_align = {'top': 'bottom', 'bottom': 'top'}.get(v_align, v_align)
else:
h_align = {'left': 'right', 'right': 'left'}.get(h_align, h_align)
x0, y0 = rotate_point(x0, y0, math.radians(-rotation))
alx, aly = 0, 0
@ -77,7 +97,7 @@ class Newstroke:
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, scale=(1, -1), **svg_attrs):
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, scale=(1, -1), mirror=(False, False), **svg_attrs):
if 'stroke_linecap' not in svg_attrs:
svg_attrs['stroke_linecap'] = 'round'
if 'stroke_linejoin' not in svg_attrs:
@ -88,7 +108,7 @@ class Newstroke:
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,
v_align=v_align, mirror=mirror, space_width=space_width, char_gap=char_gap,
scale=scale)]
return Tag('path', d=' '.join(strokes), **svg_attrs)