Microfluidic device design.

edited January 12 in General

kLayout is a great tool for designing microfluidic designs (MFD) to be transferred to a photomask for further SU8 work. However, I now have access to a nanoscribe ultra hi-res 3D printer and this would allow the MFD's to be written directly on to a carrier substrate thus removing the need for photolithography.
Can a 2d GDS file be extruded in to an .stl file and the height of the channels varied to suit the application?

Just for clarity - I'm an old tecky and not a coder! I'd really appreciate any advice as this would save me so much time.

Comments

  • @Jamboneil I did not know that there such a thing as a ultra hi-res printer ... do you have some more details about this?

    Anyway, I wrote a STL producer script myself some time ago - although for low-res printing :) It can take layers from a layout and stack the layers with a given height for each layer and export the result to STL. The script is actually very simple.

    Problem is: it will work only with a future version of KLayout which is not released yet. I noticed that most Slicing tools need a quality tesselation, so it came handy I included a Delaunay triangulator in KLayout. Only with that, the slicers I am using were able to process my STLs without crashing.

    I can provide a preview version soon I hope.

    Matthias

  • Hi Matthias, Thanks for replying.
    The 3D printer is here... https://www.nanoscribe.com/en/products/photonic-professional-gt2/

    Not exactly a home or hobby machine at around 500K Euros :) but very cool.

    kLayout is great for designing the microfluidics but making them is tricky as the pattern needs to be transfered in to SU-8 resin. This is no problem with resin thicknesses of up to 100µm but very difficult in thicknesses of 200µm which is what I'm trying to do.
    The slicing software for the nanoscribe will not accept any 2d files so I have to figure out a way of giving them some height and then converting them to stl format.

    Neil

  • edited January 18

    Understood and thanks for the link!

    Give me a little time for the grown-up KLayout version that supports quality tessellation.

    For now, KLayout can offer trapezoid decomposition which in my experience can be digested in many cases but some slicers choke on small slivers.

    Here is a script I used myself. It needs to be executed as DRC (!) script. Please see the comments for details. It is made of a generic class and a small payload part that demonstrates how to use it:


    # A KLayout DRC (+ extensions) script which supports # printing of DRC-generated layers to STL. # The STL producer class # # Usage # # 1. Instantiate STLProducer # 2. Register Region or DRC::Layer objects with z information with register # 3. Call emit to write the STL file # # Units: µm are turned into mm class STLProducer def initialize(dbu) @name = nil @file = nil @stack = [] @dbu = dbu end def dbu @dbu end def emit(path) open(path) write finish end def register(z, height, data) if data.respond_to?(:data) data = data.data end if !data.is_a?(RBA::Region) raise("Expected a RBA::Region or DRC::Layer object for data argument in STLProducer#register") end # We calculate with 1/prec unit to have integers @stack << [ (z.to_f / dbu + 0.5).floor, ((z.to_f + height.to_f) / dbu + 0.5).floor, data ] end private def open(path) @name = File.basename(path) @file = File.open(path, "w") @file.puts("solid " + @name) end def write z_values = (@stack.collect { |s| s[0] } + @stack.collect { |s| s[1] }).sort if z_values.size < 2 return end data_sets = z_values.collect do |z| @stack.select { |s| s[0] <= z && s[1] > z }.collect { |s| s[2] } end data_before = RBA::Region::new data = nil (z_values.size - 1).times do |i| if !data data = data_sets[i] data = data.empty? ? RBA::Region::new : data.inject(:+).merged end data_after = data_sets[i + 1] data_after = data_after.empty? ? RBA::Region::new : data_after.inject(:+).merged z1 = z_values[i] z2 = z_values[i + 1] write_layer(data_before, data, data_after, z1, z2) data = data_after data_before = data end end def write_layer(data_before, data, data_after, z, zz) # vertical wall data.each do |poly| poly.each_edge do |e| emit_quadrangle( [ e.p1.x * dbu, e.p1.y * dbu, zz * dbu ], [ e.p2.x * dbu, e.p2.y * dbu, zz * dbu ], [ e.p2.x * dbu, e.p2.y * dbu, z * dbu ], [ e.p1.x * dbu, e.p1.y * dbu, z * dbu ] ) end end # bottom new fracture(data - data_before).each do |s| v = s.each_point_hull.collect do |p| [ p.x * dbu, p.y * dbu, z * dbu ] end emit_array(v) end # bottom removed fracture(data_before - data).each do |s| v = s.each_point_hull.collect do |p| [ p.x * dbu, p.y * dbu, z * dbu ] end v.reverse! emit_array(v) end # top new fracture(data_after - data).each do |s| v = s.each_point_hull.collect do |p| [ p.x * dbu, p.y * dbu, zz * dbu ] end emit_array(v) end # top removed fracture(data - data_after).each do |s| v = s.each_point_hull.collect do |p| [ p.x * dbu, p.y * dbu, zz * dbu ] end v.reverse! emit_array(v) end end def fracture(data) if data.respond_to?(:delaunay) data.delaunay else # KLayout <0.29 uses trapezoid decomposition data.decompose_trapezoids_to_region end end def emit_triangle(v1, v2, v3) @file.puts("facet normal 0 0 0") @file.puts(" outer loop") @file.puts(" vertex %.12g %.12g %.12g" % v1) @file.puts(" vertex %.12g %.12g %.12g" % v2) @file.puts(" vertex %.12g %.12g %.12g" % v3) @file.puts(" endloop") @file.puts("endfacet") end def emit_quadrangle(v1, v2, v3, v4) emit_triangle(v1, v2, v3) emit_triangle(v3, v4, v1) end def emit_array(v) if v.size == 4 emit_quadrangle(*v) elsif v.size == 3 emit_triangle(*v) end end def finish @file.puts("endsolid " + @name) @file.close end end # A sample use case stl_producer = STLProducer::new(1.dbu) l1 = input(1, 0) l2 = input(2, 0) l3 = input(3, 0) # Create a layer of 2µm height (z=0, 2µm thickness) from "(l1 OR l2) NOT l3" stl_producer.register(0, 2.0, l1 + l2 - l3) # Stack l2 atop at z=+2µm with thickness 1µm stl_producer.register(2.0, 1.0, l2) # writes the output (TODO: change name) stl_producer.emit("/home/matthias/test.stl")

    With the following layout:

    this STL is produced:

    and it prints (on mm scale) :)

    (well, the A and O inner pieces fell out, because I did not connect them ...)

    Matthias

  • There's also:
    https://github.com/dteal/gdsiistl

    though maybe Matthias' method avoids the potential issue that the Github repo calls out in this "NOTE":

    Due to a limitation of the library used to triangulate the polygonal boundaries of the GDSII geometry, the polygon borders (i.e., all geometry) are shifted slightly (by a hardcoded delta of about 0.01 units, or 10 nanometers in standard micron units) before export. Furthermore, due to another related limitation/bug (not yet completely understood; see source code comments), extra triangles are sometimes created covering holes in polygons.
    
    So the output mesh is not guaranteed to be watertight, perfectly dimensioned, or retain all polygon holes, but it should be arbitrarily close and err on the side of extra triangles, so a program (e.g., Blender) can edit the mesh by deleting faces and produce a negligibly-far-from perfect visualization.
    
Sign In or Register to comment.