kopia lustrzana https://gitlab.com/gerbolyze/gerbonara
Add line wonkifier
rodzic
ba4cafa3a4
commit
3e47e7c2da
|
@ -630,10 +630,153 @@ class Schematic:
|
|||
return setup_svg(children, ((0, 0), (w, h)), pagecolor=colorscheme.background)
|
||||
|
||||
|
||||
|
||||
# From: https://jakevdp.github.io/blog/2012/10/07/xkcd-style-plots-in-matplotlib/
|
||||
#def xkcd_line(x, y, xlim=None, ylim=None, mag=1.0, f1=30, f2=0.05, f3=15):
|
||||
def xkcd_line(x, y, xlim=None, ylim=None, mag=1.0, f1=10, f2=0.10, f3=5):
|
||||
"""
|
||||
Mimic a hand-drawn line from (x, y) data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y : array_like
|
||||
arrays to be modified
|
||||
xlim, ylim : data range
|
||||
the assumed plot range for the modification. If not specified,
|
||||
they will be guessed from the data
|
||||
mag : float
|
||||
magnitude of distortions
|
||||
f1, f2, f3 : int, float, int
|
||||
filtering parameters. f1 gives the size of the window, f2 gives
|
||||
the high-frequency cutoff, f3 gives the size of the filter
|
||||
|
||||
Returns
|
||||
-------
|
||||
x, y : ndarrays
|
||||
The modified lines
|
||||
"""
|
||||
import numpy as np
|
||||
from scipy import interpolate, signal
|
||||
|
||||
x = np.asarray(x)
|
||||
y = np.asarray(y)
|
||||
|
||||
# get limits for rescaling
|
||||
if xlim is None:
|
||||
xlim = (x.min(), x.max())
|
||||
if ylim is None:
|
||||
ylim = (y.min(), y.max())
|
||||
|
||||
if xlim[1] == xlim[0]:
|
||||
xlim = ylim
|
||||
|
||||
if ylim[1] == ylim[0]:
|
||||
ylim = xlim
|
||||
|
||||
# scale the data
|
||||
x_scaled = (x - xlim[0]) * 1. / (xlim[1] - xlim[0])
|
||||
y_scaled = (y - ylim[0]) * 1. / (ylim[1] - ylim[0])
|
||||
|
||||
# compute the total distance along the path
|
||||
dx = x_scaled[1:] - x_scaled[:-1]
|
||||
dy = y_scaled[1:] - y_scaled[:-1]
|
||||
dist_tot = np.sum(np.sqrt(dx * dx + dy * dy))
|
||||
|
||||
# number of interpolated points is proportional to the distance
|
||||
Nu = int(50 * dist_tot)
|
||||
u = np.arange(-1, Nu + 1) * 1. / (Nu - 1)
|
||||
|
||||
# interpolate curve at sampled points
|
||||
k = min(3, len(x) - 1)
|
||||
res = interpolate.splprep([x_scaled, y_scaled], s=0, k=k)
|
||||
x_int, y_int = interpolate.splev(u, res[0])
|
||||
|
||||
# we'll perturb perpendicular to the drawn line
|
||||
dx = x_int[2:] - x_int[:-2]
|
||||
dy = y_int[2:] - y_int[:-2]
|
||||
dist = np.sqrt(dx * dx + dy * dy)
|
||||
|
||||
# create a filtered perturbation
|
||||
coeffs = mag * np.random.normal(0, 0.01, len(x_int) - 2)
|
||||
b = signal.firwin(f1, f2 * dist_tot, window=('kaiser', f3))
|
||||
response = signal.lfilter(b, 1, coeffs)
|
||||
|
||||
x_int[1:-1] += response * dy / dist
|
||||
y_int[1:-1] += response * dx / dist
|
||||
|
||||
# un-scale data
|
||||
x_int = x_int[1:-1] * (xlim[1] - xlim[0]) + xlim[0]
|
||||
y_int = y_int[1:-1] * (ylim[1] - ylim[0]) + ylim[0]
|
||||
|
||||
return x_int, y_int
|
||||
|
||||
def wonkify(path):
|
||||
out = []
|
||||
for segment in path.attrs['d'].split('M')[1:]:
|
||||
if 'A' in segment:
|
||||
out.append(segment)
|
||||
continue
|
||||
|
||||
points = segment.split('L')
|
||||
if points[-1].rstrip().endswith('Z'):
|
||||
closed = True
|
||||
points[-1] = points[-1].rstrip()[:-1].rstrip()
|
||||
points.append(points[0])
|
||||
else:
|
||||
closed = False
|
||||
|
||||
pts = []
|
||||
lx, ly = None, None
|
||||
for pt in points:
|
||||
x, y = pt.strip().split()
|
||||
x, y = float(x), float(y)
|
||||
if (x, y) == (lx, ly):
|
||||
continue
|
||||
|
||||
lx, ly = x, y
|
||||
pts.append((x, y))
|
||||
|
||||
if len(pts) == 2:
|
||||
segs = [pts]
|
||||
|
||||
else:
|
||||
seg = [pts[0]]
|
||||
segs = [seg]
|
||||
for p0, p1, p2 in zip(pts[0::], pts[1::], pts[2::]):
|
||||
dx1, dy1 = p1[0] - p0[0], p1[1] - p0[1]
|
||||
dx2, dy2 = p2[0] - p1[0], p2[1] - p1[1]
|
||||
a1, a2 = math.atan2(dy1, dx1), math.atan2(dy2, dx2)
|
||||
da = (a2 - a1 + math.pi) % (2*math.pi) - math.pi
|
||||
if abs(da) > math.pi/4:
|
||||
seg.append(p1)
|
||||
seg = [p1, p2]
|
||||
segs.append(seg)
|
||||
seg.append(p1)
|
||||
seg.append(p2)
|
||||
|
||||
for seg in segs:
|
||||
xs, ys = [x for x, y in seg], [y for x, y in seg]
|
||||
xs, ys = xkcd_line(xs, ys)
|
||||
d = ' L '.join(f'{x:.3f} {y:.3f}' for x, y in zip(xs, ys))
|
||||
if closed:
|
||||
d += ' Z'
|
||||
out.append(d)
|
||||
|
||||
path.attrs['d'] = ' '.join(f'M {seg}' for seg in out)
|
||||
|
||||
|
||||
def postprocess(tag):
|
||||
if tag.name == 'path':
|
||||
wonkify(tag)
|
||||
else:
|
||||
for child in tag.children:
|
||||
postprocess(child)
|
||||
return tag
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
from ...layers import LayerStack
|
||||
from .tmtheme import TmThemeSchematic
|
||||
from .tmtheme import *
|
||||
sch = Schematic.open(sys.argv[1])
|
||||
print('Loaded schematic with', len(sch.wires), 'wires and', len(sch.symbols), 'symbols.')
|
||||
for subsh in sch.subsheets:
|
||||
|
@ -641,5 +784,10 @@ if __name__ == '__main__':
|
|||
print('Loaded sub-sheet with', len(subsh.wires), 'wires and', len(subsh.symbols), 'symbols.')
|
||||
|
||||
sch.write('/tmp/test.kicad_sch')
|
||||
Path('/tmp/test.svg').write_text(str(sch.to_svg(TmThemeSchematic(Path('/tmp/witchhazelhypercolor.tmTheme').read_text()))))
|
||||
for p in Path('/tmp').glob('*.tmTheme'):
|
||||
cs = TmThemeSchematic(p.read_text())
|
||||
Path(f'/tmp/test-{p.stem}.svg').write_text(str(postprocess(sch.to_svg(cs))))
|
||||
for p in Path('/tmp').glob('*.sublime-color-scheme'):
|
||||
cs = SublimeSchematic(p.read_text())
|
||||
Path(f'/tmp/test-{p.stem}.svg').write_text(str(postprocess(sch.to_svg(cs))))
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
from xml.etree import ElementTree
|
||||
import base64
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
def _map_primitive(element):
|
||||
|
@ -31,17 +32,9 @@ def parse_shitty_json(data):
|
|||
root = ElementTree.fromstring(data)
|
||||
return _map_primitive(root[0])
|
||||
|
||||
class TmThemeSchematic:
|
||||
def __init__(self, data):
|
||||
self.theme = parse_shitty_json(data)
|
||||
s = self.theme['settings'][0]['settings']
|
||||
by_scope = {}
|
||||
for elem in self.theme['settings']:
|
||||
if 'scope' not in elem:
|
||||
continue
|
||||
for scope in elem['scope'].split(','):
|
||||
by_scope[scope.strip()] = elem.get('settings', {})
|
||||
|
||||
class _SublimeColorschemeSuper:
|
||||
def __init__(self, s, by_scope):
|
||||
def lookup(default, *scopes):
|
||||
for scope in scopes:
|
||||
if not (elem := by_scope.get(scope)):
|
||||
|
@ -54,16 +47,41 @@ class TmThemeSchematic:
|
|||
return default
|
||||
|
||||
self.background = s.get('background', 'white')
|
||||
self.bus = lookup('black', 'constant.other', 'storage.type')
|
||||
self.wire = self.lines = lookup('black', 'constant.other')
|
||||
self.no_connect = lookup('black', 'constant.language', 'variable')
|
||||
self.text = lookup('black', 'constant.numeric', 'constant.numeric.hex', 'storage.type.number')
|
||||
self.pin_numbers = lookup('black', 'constant.character', 'constant.other')
|
||||
self.pin_names = lookup('black', 'constant.character.format.placeholder', 'constant.other.placeholder')
|
||||
self.values = lookup('black', 'constant.character.format.placeholder', 'constant.other.placeholder')
|
||||
self.labels = lookup('black', 'constant.numeric', 'constant.numeric.hex', 'storage.type.number')
|
||||
fg = s.get('foreground', 'black')
|
||||
self.bus = lookup(fg, 'constant.other', 'storage.type')
|
||||
self.wire = self.lines = lookup(fg, 'constant.other')
|
||||
self.no_connect = lookup(fg, 'constant.language', 'variable')
|
||||
self.text = lookup(fg, 'constant.numeric', 'constant.numeric.hex', 'storage.type.number')
|
||||
self.pin_names = lookup(fg, 'constant.character', 'constant.other')
|
||||
self.pin_numbers = fg
|
||||
self.values = lookup(fg, 'constant.character.format.placeholder', 'constant.other.placeholder', 'entity.name.tag', 'support.type', 'support.class', 'entity.other.inherited-class')
|
||||
self.labels = lookup(fg, 'constant.numeric', 'constant.numeric.hex', 'storage.type.number')
|
||||
self.fill = s.get('background')
|
||||
print(f'{self.background=} {self.wire=} {self.bus=} {self.lines=} {self.no_connect=} {self.labels=} {self.fill=}')
|
||||
|
||||
|
||||
class TmThemeSchematic(_SublimeColorschemeSuper):
|
||||
def __init__(self, data):
|
||||
self.theme = parse_shitty_json(data)
|
||||
s = self.theme['settings'][0]['settings']
|
||||
by_scope = {}
|
||||
for elem in self.theme['settings']:
|
||||
if 'scope' not in elem:
|
||||
continue
|
||||
for scope in elem['scope'].split(','):
|
||||
by_scope[scope.strip()] = elem.get('settings', {})
|
||||
super().__init__(s, by_scope)
|
||||
|
||||
|
||||
class SublimeSchematic(_SublimeColorschemeSuper):
|
||||
def __init__(self, data):
|
||||
self.theme = json.loads(data)
|
||||
s = self.theme['globals']
|
||||
by_scope = {}
|
||||
for elem in self.theme['rules']:
|
||||
for scope in elem['scope'].split(','):
|
||||
by_scope[scope.strip()] = elem
|
||||
super().__init__(s, by_scope)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(parse_shitty_json(Path('/tmp/witchhazelhypercolor.tmTheme').read_text()))
|
||||
|
|
Ładowanie…
Reference in New Issue