Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/vcad-gdsii/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ publish = false
[features]
default = ["vcad-ir"]
## Enables the GDS-layer → vcad-ir document bridge (`bridge` module).
vcad-ir = ["dep:vcad-ir"]
vcad-ir = ["dep:vcad-ir", "dep:geo"]

[dependencies]
thiserror = { workspace = true }
vcad-ir = { workspace = true, optional = true }
# 2D polygon booleans for merging each layer's polygons before extrusion
# (pure-Rust BooleanOps — same crate vcad-kernel-cam uses; WASM-safe).
geo = { version = "0.28", optional = true }
126 changes: 126 additions & 0 deletions crates/vcad-gdsii/examples/flat_import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! THROWAWAY: like sky130_import but emits one scene root per polygon
//! (no unions at all) — trades document tidiness for render speed.
//! Usage: flat_import <in.gds> <out.vcad> [top]

use vcad_gdsii::{flatten, read_library};
use vcad_ir::{
CsgOp, Document, MaterialDef, Node, NodeId, SceneEntry, SketchSegment2D, Vec2, Vec3,
};

fn main() {
let mut args = std::env::args().skip(1);
let gds_path = args.next().expect("arg 1: in.gds");
let vcad_path = args.next().expect("arg 2: out.vcad");
let top = args.next().expect("arg 3: top cell");

// Optional µm-space crop window: WINDOW="x0,y0,x1,y1" (µm)
let window: Option<[f64; 4]> = std::env::var("WINDOW").ok().map(|w| {
let v: Vec<f64> = w.split(',').map(|s| s.parse().expect("WINDOW")).collect();
[v[0], v[1], v[2], v[3]]
});

let bytes = std::fs::read(&gds_path).expect("read gds");
let lib = read_library(&bytes).expect("parse gds");
let flat = flatten(&lib, &top).expect("flatten");
let db_to_mm = lib.db_unit_in_meters * 1e6; // 1 µm = 1 mm view scale

// (gds layer, z_bottom_mm, thickness_mm, name, rgb)
let stack: [(i16, f64, f64, &str, [f64; 3]); 5] = [
(65, 0.00, 0.12, "diff", [0.85, 0.35, 0.25]),
(66, 0.30, 0.18, "poly", [0.30, 0.65, 0.35]),
(67, 0.94, 0.10, "li1", [0.60, 0.35, 0.75]),
(68, 1.38, 0.36, "met1", [0.30, 0.45, 0.85]),
(69, 2.00, 0.36, "met2", [0.80, 0.70, 0.25]),
];

let mut doc = Document::new();
let mut next_id: NodeId = 1;
for &(layer, z, t, name, color) in &stack {
let Some(lp) = flat.iter().find(|lp| lp.layer == layer) else {
continue;
};
let key = format!("gds_{name}");
doc.materials.insert(
key.clone(),
MaterialDef {
name: key.clone(),
color,
metallic: 0.3,
roughness: 0.6,
..Default::default()
},
);
for polygon in &lp.polygons {
let mut polygon = polygon.clone();
if let Some([x0, y0, x1, y1]) = window {
let inside = polygon.iter().any(|p| {
let (x, y) = (p[0] * db_to_mm, p[1] * db_to_mm);
x >= x0 && x <= x1 && y >= y0 && y <= y1
});
if !inside {
continue;
}
// Clamp crossing polygons to the window (cleaved-die edge);
// drop anything that degenerates to zero area.
for p in &mut polygon {
p[0] = p[0].clamp(x0 / db_to_mm, x1 / db_to_mm);
p[1] = p[1].clamp(y0 / db_to_mm, y1 / db_to_mm);
}
let area2: f64 = polygon
.iter()
.zip(polygon.iter().cycle().skip(1))
.map(|(a, b)| a[0] * b[1] - b[0] * a[1])
.sum();
if area2.abs() < 1e-6 {
continue;
}
}
let polygon = &polygon;
let segments: Vec<SketchSegment2D> = polygon
.iter()
.zip(polygon.iter().cycle().skip(1))
.map(|(a, b)| SketchSegment2D::Line {
start: Vec2::new(a[0] * db_to_mm, a[1] * db_to_mm),
end: Vec2::new(b[0] * db_to_mm, b[1] * db_to_mm),
})
.collect();
let sketch = next_id;
doc.nodes.insert(
sketch,
Node {
id: sketch,
name: None,
op: CsgOp::Sketch2D {
origin: Vec3::new(0.0, 0.0, z),
x_dir: Vec3::new(1.0, 0.0, 0.0),
y_dir: Vec3::new(0.0, 1.0, 0.0),
segments,
},
},
);
let extrude = sketch + 1;
next_id += 2;
doc.nodes.insert(
extrude,
Node {
id: extrude,
name: None,
op: CsgOp::Extrude {
sketch,
direction: Vec3::new(0.0, 0.0, t),
twist_angle: None,
scale_end: None,
},
},
);
doc.roots.push(SceneEntry {
root: extrude,
material: key.clone(),
visible: None,
});
}
}
println!("roots: {}", doc.roots.len());
std::fs::write(&vcad_path, doc.to_json().expect("json")).expect("write");
println!("wrote {vcad_path}");
}
98 changes: 98 additions & 0 deletions crates/vcad-gdsii/examples/sky130_import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! THROWAWAY: import an OpenLane/LibreLane sky130 GDS, flatten the top
//! cell, print per-layer polygon counts, and emit a .vcad document.
//!
//! ```sh
//! cargo run -p vcad-gdsii --example sky130_import -- input.gds output.vcad [TOP]
//! ```

use vcad_gdsii::{flatten, read_library, to_vcad_document, DEFAULT_VIEW_SCALE};

fn main() {
let mut args = std::env::args().skip(1);
let gds_path = args
.next()
.expect("usage: sky130_import <in.gds> <out.vcad> [top-cell]");
let vcad_path = args
.next()
.expect("usage: sky130_import <in.gds> <out.vcad> [top-cell]");
let top_arg = args.next();

let bytes = std::fs::read(&gds_path).expect("read gds file");
println!("read {} bytes from {gds_path}", bytes.len());

let lib = read_library(&bytes).expect("parse gds");
println!(
"library `{}`: {} cells, db_unit = {} m",
lib.name,
lib.cells.len(),
lib.db_unit_in_meters
);

// Top cell: explicit arg, else the cell no other cell references.
let top = top_arg.unwrap_or_else(|| {
let referenced: std::collections::HashSet<&str> = lib
.cells
.iter()
.flat_map(|c| c.elements.iter())
.filter_map(|e| match e {
vcad_gdsii::Element::Sref { sname, .. } => Some(sname.as_str()),
vcad_gdsii::Element::Aref { sname, .. } => Some(sname.as_str()),
_ => None,
})
.collect();
let tops: Vec<&str> = lib
.cells
.iter()
.map(|c| c.name.as_str())
.filter(|n| !referenced.contains(n))
.collect();
println!("unreferenced (top) cells: {tops:?}");
tops.first().expect("no top cell found").to_string()
});
println!("flattening top cell `{top}`");

let flat = match flatten(&lib, &top) {
Ok(f) => f,
Err(e) => {
eprintln!("FLATTEN ERROR: {e}");
eprintln!("debug: {e:?}");
std::process::exit(1);
}
};
println!("flattened layers (gds layer -> polygons / vertices):");
let mut total = 0usize;
for lp in &flat {
let verts: usize = lp.polygons.iter().map(|p| p.len()).sum();
total += lp.polygons.len();
println!(
" layer {:>3}: {:>6} polygons, {:>7} vertices",
lp.layer,
lp.polygons.len(),
verts
);
}
println!("total polygons: {total}");

// sky130-ish film stack: (gds layer, z_bottom_um, thickness_um, name).
// Z heights approximate the sky130A metal stack.
let stack = [
(65, 0.00, 0.12, "diff"),
(66, 0.30, 0.18, "poly"),
(67, 0.94, 0.10, "li1"),
(68, 1.38, 0.36, "met1"),
(69, 2.00, 0.36, "met2"),
(70, 2.79, 0.85, "met3"),
(71, 4.02, 0.85, "met4"),
(72, 5.37, 1.26, "met5"),
];
let doc = match to_vcad_document(&lib, &top, &stack, DEFAULT_VIEW_SCALE) {
Ok(d) => d,
Err(e) => {
eprintln!("BRIDGE ERROR: {e}");
eprintln!("debug: {e:?}");
std::process::exit(1);
}
};
std::fs::write(&vcad_path, doc.to_json().expect("json")).expect("write vcad");
println!("wrote {vcad_path}");
}
Loading