Skip to content

Instantly share code, notes, and snippets.

@MayeulC
Created October 13, 2021 10:45
Show Gist options
  • Save MayeulC/485ce3fa16015c6227624deacc2ffb37 to your computer and use it in GitHub Desktop.
Save MayeulC/485ce3fa16015c6227624deacc2ffb37 to your computer and use it in GitHub Desktop.
Klayout svg export
class SVGWriter
# Based off https://www.klayout.de/forum/discussion/873/working-with-dxfs
def initialize(file)
@file = File.open(file, "w")
@shift = 0
end
def start(w, h)
@file.puts(<<"END")
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="#{'%.12g'%w}mm"
height="#{'%.12g'%h}mm"
viewBox="0 0 #{'%.12g'%w} #{'%.12g'%h}"
version="1.1">
END
end
def layerPattern(idx, color, stipple, name)
@shift= @shift+0.1 # TODO: make this better by only shifting the same
if @shift > 0.6
@shift = 0
end
color = '%06x' % (color & 0xffffff)
linePattern = '<line x1="0" y1="0" x2="0" y2="10" style="stroke-width:0.1" />'
angle = 0
scale = 2
pwidth=0.5
pheight=0.5
case stipple
when 0 # Full
pattern = '<rect x="0" y="0" width="0.5" height="0.5" style="fill:#'"#{color}"';stroke:none" />'
when 1 # empty
pattern = ''
when 2 # tiny squares close one to another
pwidth = 0.25
pheight = 0.25
pattern = '<rect x="0" y="0" width="0.125" height="0.125" style="fill:#'"#{color}"';stroke:none" />\n<rect x="0.125" y="0.125" width="0.125" height="0.125" style="fill:#'"#{color}"';stroke:none" />\n'
when 3 # tiny square, but spaced out
pattern = '<rect x="0.125" y="0" width="0.125" height="0.125" style="fill:#'"#{color}"';stroke:none" />'
when 5 # dash 1
angle = 135
scale = 2
pattern = linePattern
when 9 # dash 2
angle = 45
scale = 2
pattern = linePattern
when 13
pattern = '<line x1="0" y1="0" x2="0" y2="10" style="stroke-width:0.1" />\n<line x1="10" y1="0" x2="0" y2="0" style="stroke-width:0.1" />'
angle = 45
else
print("dither pattern #{stipple} not found\n")
pattern = ''
end
@file.puts(<<"END")
<!-- #{idx} / #{name} -->
<pattern
id="pattern-layer-#{idx}"
width="#{pwidth}"
height="#{pheight}"
stroke="##{color}"
stroke-linecap="square"
patternTransform="rotate(#{angle}) scale(#{scale}) translate(#{@shift})"
patternUnits="userSpaceOnUse">
#{pattern}
</pattern>
END
end
def prepareLayers(layers)
@file.puts("<defs>\n")
layers.each do |lp|
idx = lp.layer_index
col = lp.fill_color
stip = lp.dither_pattern
layerPattern(idx, col, stip, lp.name)
end
@file.puts("</defs>\n")
end
def finish
@file.puts("</svg>")
@file.close
end
def begin_layer(lp)
@stroke_color = lp.frame_color
@fill_color = lp.fill_color
# see https://www.klayout.de/doc-qt5/code/class_LayerProperties.html#method24
@stipple = lp.eff_dither_pattern
@index = lp.layer_index
@name = lp.name
@file.puts("<g>")
end
def end_layer(lp)
@file.puts("</g>")
end
def polygon(p, dbu)
pts = []
p.each_point_hull { |pt| pts << ("%.12g %.12g" % [pt.x * dbu, pt.y * dbu]) }
ctrs = [ "M " + pts.join(" L ") + " z" ]
p.holes.times do |h|
pts = []
p.each_point_hole(h) { |pt| pts << ("%.12g %.12g" % [pt.x * dbu, pt.y * dbu]) }
ctrs << [ "M " + pts.join(" L ") + " z" ]
end
d = ctrs.join(" ")
@file.puts(" <path style=\"fill:url(#pattern-layer-#{@index});stroke-width:0.2;stroke:##{'%06x' % (@stroke_color & 0xffffff)}\"")
@file.puts(" d=\"#{d}\"/>")
end
end
lv = RBA::LayoutView::current
lv || raise("No view opened")
cv = RBA::CellView::active
cv || raise("No layout loaded")
ly = cv.layout
cell = cv.cell
out = RBA::FileDialog::ask_save_file_name("Chose SVG file to write", ".", "SVG files (*.svg);;All files (*)")
if out
layers = []
li = lv.begin_layers
while !li.at_end?
if li.current.visible? && !li.current.has_children? && li.current.layer_index >= 0
layers << li.current
end
li.next
end
bbox = cell.bbox
dbu = ly.dbu*10
writer = SVGWriter::new(out)
writer.start(bbox.width * dbu, bbox.height * dbu)
canvas_tr = RBA::CplxTrans::new(1.0, 0.0, true, RBA::DPoint::new(-bbox.left, bbox.top))
writer.prepareLayers(layers)
layers.each do |lp|
writer.begin_layer(lp)
si = cell.begin_shapes_rec(lp.layer_index)
while !si.at_end?
s = si.shape
if s.is_path? || s.is_box? || s.is_polygon?
writer.polygon(s.polygon.transformed(canvas_tr * si.trans), dbu)
end
si.next
end
writer.end_layer(lp)
end
writer.finish
end
@MayeulC
Copy link
Author

MayeulC commented Oct 13, 2021

I'll create an actual repo or plugin a bit latter. I still have a lot of stipples to match, border patterns, etc. The code is quite hackish, lacks testing, and the layer shift is very hacky too. I need to make sure it works at different zoom levels.

Bear with me, it's my first time writing ruby :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment