diff --git a/.gitignore b/.gitignore index 567609b..252026b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build/ +*.pem diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..85f3da2 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome", "ziglang.vscode-zig"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7b6a3fe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "editor.defaultFormatter": "biomejs.biome", + "editor.formatOnSave": true, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[react]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..1e62364 --- /dev/null +++ b/biome.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": ["**/alpine-3.10.4.js", "**/path-data-polyfill.js"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 120, + "indentWidth": 4 + }, + "linter": { + "enabled": true, + "rules": { "recommended": true, "complexity": { "useLiteralKeys": "info" } } + }, + "javascript": { "formatter": { "quoteStyle": "double" } } +} diff --git a/native/.gitignore b/native/.gitignore index dc6b79a..b588143 100644 --- a/native/.gitignore +++ b/native/.gitignore @@ -1,5 +1,5 @@ -zig-cache/ -zig-out/ +*zig-cache/ +*zig-out/ *.o *.a *.dylib diff --git a/native/src/gingerbread.zig b/native/src/gingerbread.zig index e4e341a..e09bb66 100644 --- a/native/src/gingerbread.zig +++ b/native/src/gingerbread.zig @@ -11,7 +11,7 @@ const wasm = @import("wasm.zig"); const a = wasm.allocator; -fn trace(allocator: std.mem.Allocator, layer_name: []const u8, scale_factor: f64, image_pixels: [*]u8, image_width: usize, image_height: usize, writer: anytype) !void { +fn trace(allocator: std.mem.Allocator, layer_name: []const u8, scale_factor: f64, image_pixels: [*]u8, image_width: usize, image_height: usize, writer: anytype, width_mm: f64) !void { var bitmap = try potrace.Bitmap.from_image(allocator, .{ .pixels = image_pixels, .w = image_width, @@ -39,7 +39,7 @@ fn trace(allocator: std.mem.Allocator, layer_name: []const u8, scale_factor: f64 print("Polylist fractured\n", .{}); - try pcb.polylist_to_footprint(polylist, layer_name, scale_factor, writer); + try pcb.polylist_to_footprint(polylist, layer_name, scale_factor, writer, width_mm); } test "trace" { @@ -70,7 +70,7 @@ export fn conversion_start() void { pcb.start_pcb(conversion_buffer.?.writer()) catch @panic("memory"); } -export fn conversion_add_raster_layer(layer: u32, scale_factor: f64, image_pixels: [*]u8, image_width: u32, image_height: u32) void { +export fn conversion_add_raster_layer(layer: u32, scale_factor: f64, image_pixels: [*]u8, image_width: u32, image_height: u32, width_mm: f64) void { const layer_name = switch (layer) { 1 => "F.Cu", 2 => "B.Cu", @@ -81,7 +81,7 @@ export fn conversion_add_raster_layer(layer: u32, scale_factor: f64, image_pixel else => "Unknown", }; - trace(a, layer_name, scale_factor, image_pixels, image_width, image_height, conversion_buffer.?.writer()) catch @panic("memory"); + trace(a, layer_name, scale_factor, image_pixels, image_width, image_height, conversion_buffer.?.writer(), width_mm) catch @panic("memory"); } export fn conversion_start_poly() void { @@ -91,9 +91,21 @@ export fn conversion_start_poly() void { export fn conversion_add_poly_point( x: f64, y: f64, + layer_number: u32, scale_factor: f64, + width_mm: f64, ) void { - pcb.add_xx_poly_point(.{ .x = x, .y = y }, scale_factor, conversion_buffer.?.writer()) catch @panic("memory"); + const layer_name = switch (layer_number) { + 1 => "F.Cu", + 2 => "B.Cu", + 3 => "F.SilkS", + 4 => "B.SilkS", + 5 => "F.Mask", + 6 => "B.Mask", + else => "Unknown", + }; + + pcb.add_xx_poly_point(.{ .x = x, .y = y }, layer_name, scale_factor, conversion_buffer.?.writer(), width_mm) catch @panic("memory"); } export fn conversion_end_poly(layer: u32, width: f32, fill: bool) void { @@ -115,3 +127,7 @@ export fn conversion_finish() wasm.StringResult { pcb.end_pcb(&conversion_buffer.?.writer()) catch @panic("memory"); return wasm.return_string(conversion_buffer.?.toOwnedSlice() catch @panic("memory")); } + +export fn set_mirror_back_layers(val: bool) void { + pcb.mirror_back_layers = val; +} diff --git a/native/src/pcb.zig b/native/src/pcb.zig index 15cefb0..52308b5 100644 --- a/native/src/pcb.zig +++ b/native/src/pcb.zig @@ -6,6 +6,10 @@ const Poly = geometry.Poly; const PolyList = geometry.PolyList; const FauxUUID = @import("fauxuuid.zig").FauxUUID; +fn is_back_layer(layer: []const u8) bool { + return std.ascii.startsWithIgnoreCase(layer, "B."); +} + pub fn start_pcb(writer: anytype) !void { try writer.writeAll("(kicad_pcb (version 20211014) (generator pcbnew)\n"); try writer.writeAll("(layers\n"); @@ -29,31 +33,41 @@ pub fn start_xx_poly(kind: []const u8, writer: anytype) !void { try writer.print(" ({s}_poly\n", .{kind}); try writer.writeAll(" (pts\n"); } +pub var mirror_back_layers: bool = true; + +pub fn add_xx_poly_point(pt: geometry.Point, layer_name: []const u8, scale_factor: f64, writer: anytype, width_mm: f64) !void { + const scaled_x = pt.x * scale_factor; + const scaled_y = pt.y * scale_factor; + + // For back layers, mirror around y=0 then translate back + const final_x = if (is_back_layer(layer_name) and mirror_back_layers) + -scaled_x + width_mm + else + scaled_x; -pub fn add_xx_poly_point(pt: geometry.Point, scale_factor: f64, writer: anytype) !void { - try writer.print(" (xy {d:.3} {d:.3})\n", .{ pt.x * scale_factor, pt.y * scale_factor }); + try writer.print(" (xy {d:.3} {d:.3})\n", .{ final_x, scaled_y }); } -pub fn end_xx_poly(layer: []const u8, width: f64, fill: bool, writer: anytype) !void { +pub fn end_xx_poly(layer_name: []const u8, line_width: f64, fill: bool, writer: anytype) !void { try writer.writeAll(" )\n"); - try writer.print(" (layer \"{s}\")\n", .{layer}); - try writer.print(" (width {d:.3})\n", .{width}); + try writer.print(" (layer \"{s}\")\n", .{layer_name}); + try writer.print(" (width {d:.3})\n", .{line_width}); try writer.print(" (fill {s})\n", .{if (fill) "solid" else "none"}); try writer.print(" (tstamp \"{s}\")\n", .{FauxUUID.init()}); try writer.writeAll(" )\n"); } -pub fn points_to_xx_poly(kind: []const u8, pts: []geometry.Point, scale_factor: f64, layer: []const u8, width: f64, fill: bool, writer: anytype) !void { +pub fn points_to_xx_poly(kind: []const u8, pts: []geometry.Point, scale_factor: f64, layer_name: []const u8, line_width: f64, fill: bool, writer: anytype, width_mm: f64) !void { try start_xx_poly(kind, writer); for (pts) |pt| { - try add_xx_poly_point(pt, scale_factor, writer); + try add_xx_poly_point(pt, layer_name, scale_factor, writer, width_mm); } - try end_xx_poly(layer, width, fill, writer); + try end_xx_poly(layer_name, line_width, fill, writer); } -pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor: f64, writer: anytype) !void { +pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor: f64, writer: anytype, width_mm: f64) !void { try writer.writeAll("(footprint \"Graphics\"\n"); try writer.print(" (layer \"{s}\")\n", .{layer}); try writer.writeAll(" (at 0 0)\n"); @@ -62,7 +76,7 @@ pub fn polylist_to_footprint(polylist: PolyList, layer: []const u8, scale_factor try writer.print(" (tedit \"{s}\")\n", .{FauxUUID.init()}); for (polylist.items) |poly| { - try points_to_xx_poly("fp", poly.outline, scale_factor, layer, 0, true, writer); + try points_to_xx_poly("fp", poly.outline, scale_factor, layer, 0, true, writer, width_mm); } try writer.writeAll(")\n"); diff --git a/web/help.html b/web/help.html index 4974ef9..b26f0c0 100644 --- a/web/help.html +++ b/web/help.html @@ -49,6 +49,11 @@

Drills

Affinity and KiCAD.

+

Mirror back layers

+

Gingerbread allows you to mirror the B.SilkS, B.Mask, and B.Cu layers so that the it is correctly mirrored in the output. This can be switched + off if you don't want this behavior.

+ +

Exporting your design

When exporting you design to an SVG for Gingerbread, click the More button and setup the export parameters as shown below so that "Rasterize" is set to "Nothing", "Export text as curves" is checked, and "Flatten transforms" is checked.

diff --git a/web/index.html b/web/index.html index 29948a3..2fe7e05 100644 --- a/web/index.html +++ b/web/index.html @@ -126,6 +126,13 @@ +
+ +
+