use klayout_core::{
clip_cell, flatten, remap_layers, replace_instances, Angle90, Bbox, BuildCtx, CellBuilder,
Component, Instance, LayerInfo, LayerMap, Library, Point, Port, Rect, Rot4, Trans, Vec2,
};
use std::hash::Hash;
#[derive(Hash)]
struct Rectangle {
width_dbu: i64,
height_dbu: i64,
}
impl Component for Rectangle {
fn build(&self, ctx: &BuildCtx<'_>) -> CellBuilder {
let wg = ctx.lib.layer(LayerInfo::named("WG", 1, 0));
let mut b = CellBuilder::new("");
b.add_shape(
wg,
Rect::new(Bbox::new(
Point::new(0, 0),
Point::new(self.width_dbu, self.height_dbu),
)),
);
b.add_port(Port::new(
"o1",
wg,
Point::new(0, self.height_dbu / 2),
Angle90::W,
self.height_dbu,
));
b.add_port(Port::new(
"o2",
wg,
Point::new(self.width_dbu, self.height_dbu / 2),
Angle90::E,
self.height_dbu,
));
b
}
}
#[derive(Hash)]
struct PairOfRects {
width_dbu: i64,
height_dbu: i64,
spacing_dbu: i64,
}
impl Component for PairOfRects {
fn build(&self, ctx: &BuildCtx<'_>) -> CellBuilder {
let r = ctx.lib.build(&Rectangle {
width_dbu: self.width_dbu,
height_dbu: self.height_dbu,
});
let mut b = CellBuilder::new("pair");
b.add_instance(Instance::new(r, Trans::IDENTITY));
b.add_instance(Instance::new(
r,
Trans::translate(Vec2::new(self.width_dbu + self.spacing_dbu, 0)),
));
b
}
}
#[test]
fn build_and_dedup() {
let lib = Library::new("test", 1000);
let a = lib.build(&Rectangle {
width_dbu: 100,
height_dbu: 50,
});
let b = lib.build(&Rectangle {
width_dbu: 100,
height_dbu: 50,
});
assert_eq!(a, b, "param dedup should reuse the same CellId");
assert_eq!(lib.cell_count(), 1);
let c = lib.build(&Rectangle {
width_dbu: 200,
height_dbu: 50,
});
assert_ne!(a, c);
assert_eq!(lib.cell_count(), 2);
}
#[test]
fn content_dedup_across_paths() {
let lib = Library::new("test", 1000);
let direct = {
let wg = lib.layer(LayerInfo::named("WG", 1, 0));
let mut b = CellBuilder::new("manual");
b.add_shape(
wg,
Rect::new(Bbox::new(Point::new(0, 0), Point::new(100, 50))),
);
b.add_port(Port::new(
"o1",
wg,
Point::new(0, 25),
Angle90::W,
50,
));
b.add_port(Port::new(
"o2",
wg,
Point::new(100, 25),
Angle90::E,
50,
));
lib.insert(b)
};
let via_component = lib.build(&Rectangle {
width_dbu: 100,
height_dbu: 50,
});
assert_eq!(
direct, via_component,
"different code paths producing identical content should dedup"
);
}
#[test]
fn hierarchy_full_bbox() {
let lib = Library::new("test", 1000);
let pair = lib.build(&PairOfRects {
width_dbu: 100,
height_dbu: 50,
spacing_dbu: 30,
});
let cell = lib.get(pair);
let bbox = cell.full_bbox(&lib);
assert_eq!(bbox.min, Point::new(0, 0));
assert_eq!(bbox.max, Point::new(230, 50));
}
#[test]
fn content_hash_is_deterministic() {
let make = || {
let lib = Library::new("test", 1000);
let id = lib.build(&Rectangle {
width_dbu: 100,
height_dbu: 50,
});
lib.get(id).content_hash()
};
assert_eq!(make(), make());
}
#[test]
fn content_hash_independent_of_insertion_order() {
let lib1 = Library::new("a", 1000);
let lib2 = Library::new("b", 1000);
let wg1 = lib1.layer(LayerInfo::named("WG", 1, 0));
let wg2 = lib2.layer(LayerInfo::named("WG", 1, 0));
let mut b1 = CellBuilder::new("c");
b1.add_shape(wg1, Bbox::new(Point::new(0, 0), Point::new(10, 10)));
b1.add_shape(wg1, Bbox::new(Point::new(20, 20), Point::new(30, 30)));
let mut b2 = CellBuilder::new("c");
b2.add_shape(wg2, Bbox::new(Point::new(20, 20), Point::new(30, 30)));
b2.add_shape(wg2, Bbox::new(Point::new(0, 0), Point::new(10, 10)));
let id1 = lib1.insert(b1);
let id2 = lib2.insert(b2);
assert_eq!(
lib1.get(id1).content_hash(),
lib2.get(id2).content_hash(),
"shape order should not affect content hash"
);
}
#[test]
fn port_transform_under_rotation() {
let lib = Library::new("test", 1000);
let wg = lib.layer(LayerInfo::named("WG", 1, 0));
let p = Port::new("o1", wg, Point::new(10, 0), Angle90::E, 50);
let t = Trans::rotate(Rot4::R90);
let p2 = p.transform(t);
assert_eq!(p2.center, Point::new(0, 10));
assert_eq!(p2.angle, Angle90::N);
}
#[test]
fn clip_cell_restricts_to_bbox() {
let lib = Library::new("t", 1000);
let l = lib.layer(LayerInfo::gds(1, 0));
let mut cb = CellBuilder::new("c");
cb.add_shape(l, Rect::new(Bbox::new(Point::new(0, 0), Point::new(10, 10))));
cb.add_shape(l, Rect::new(Bbox::new(Point::new(50, 50), Point::new(60, 60))));
cb.add_shape(l, Rect::new(Bbox::new(Point::new(5, 5), Point::new(55, 55))));
let id = lib.insert(cb);
let clipped = clip_cell(&lib, id, Bbox::new(Point::new(0, 0), Point::new(20, 20)), "clipped");
let new_id = lib.insert(clipped);
let cell = lib.get(new_id);
let shapes: Vec<_> = cell.layers().flat_map(|l| cell.shapes_on(l)).collect();
assert_eq!(shapes.len(), 2);
}
#[test]
fn clip_cell_drops_instances_outside() {
let lib = Library::new("t", 1000);
let l = lib.layer(LayerInfo::gds(1, 0));
let mut leaf = CellBuilder::new("u");
leaf.add_shape(l, Rect::new(Bbox::new(Point::new(0, 0), Point::new(5, 5))));
let leaf_id = lib.insert(leaf);
let mut top = CellBuilder::new("top");
top.add_instance(Instance::new(leaf_id, Trans::IDENTITY));
top.add_instance(Instance::new(leaf_id, Trans::translate(Vec2::new(50, 0))));
let top_id = lib.insert(top);
let clipped = clip_cell(&lib, top_id, Bbox::new(Point::new(0, 0), Point::new(20, 20)), "c");
let cid = lib.insert(clipped);
let cell = lib.get(cid);
assert_eq!(cell.instances().len(), 1, "instance at (50,0) is outside the clip");
}
#[test]
fn flatten_inlines_instance_shapes() {
let lib = Library::new("t", 1000);
let l = lib.layer(LayerInfo::named("WG", 1, 0));
let mut leaf = CellBuilder::new("leaf");
leaf.add_shape(l, Rect::new(Bbox::new(Point::new(0, 0), Point::new(10, 10))));
let leaf_id = lib.insert(leaf);
let mut top = CellBuilder::new("top");
top.add_instance(Instance::new(leaf_id, Trans::IDENTITY));
top.add_instance(Instance::new(leaf_id, Trans::translate(Vec2::new(50, 0))));
let top_id = lib.insert(top);
let flat = flatten(&lib, top_id, usize::MAX, "top_flat");
let flat_id = lib.insert(flat);
let cell = lib.get(flat_id);
let shape_count: usize = cell.layers().map(|l| cell.shapes_on(l).count()).sum();
assert_eq!(shape_count, 2);
assert_eq!(cell.instances().len(), 0);
}
#[test]
fn remap_layers_translates_shapes() {
let lib = Library::new("t", 1000);
let from = lib.layer(LayerInfo::named("WG", 1, 0));
let to = lib.layer(LayerInfo::named("METAL1", 10, 0));
let mut cb = CellBuilder::new("c");
cb.add_shape(from, Rect::new(Bbox::new(Point::new(0, 0), Point::new(10, 10))));
let id = lib.insert(cb);
let map = LayerMap::new().map(from, to);
let remapped = remap_layers(&lib, id, &map);
let new_id = lib.insert(remapped);
let cell = lib.get(new_id);
assert_eq!(cell.shapes_on(from).count(), 0);
assert_eq!(cell.shapes_on(to).count(), 1);
}
#[test]
fn replace_instances_swaps_target() {
let lib = Library::new("t", 1000);
let l = lib.layer(LayerInfo::gds(1, 0));
let mut leaf_a = CellBuilder::new("a");
leaf_a.add_shape(l, Rect::new(Bbox::new(Point::new(0, 0), Point::new(10, 10))));
let a_id = lib.insert(leaf_a);
let mut leaf_b = CellBuilder::new("b");
leaf_b.add_shape(l, Rect::new(Bbox::new(Point::new(0, 0), Point::new(20, 20))));
let b_id = lib.insert(leaf_b);
let mut top = CellBuilder::new("top");
top.add_instance(Instance::new(a_id, Trans::IDENTITY));
top.add_instance(Instance::new(a_id, Trans::translate(Vec2::new(50, 0))));
let top_id = lib.insert(top);
let new_top = replace_instances(&lib, top_id, |id| id == a_id, b_id);
let new_top_id = lib.insert(new_top);
let cell = lib.get(new_top_id);
assert_eq!(cell.instances().len(), 2);
for inst in cell.instances() {
assert_eq!(inst.cell, b_id);
}
}
#[test]
fn top_cells_excludes_children() {
let lib = Library::new("test", 1000);
let _pair = lib.build(&PairOfRects {
width_dbu: 100,
height_dbu: 50,
spacing_dbu: 30,
});
let tops = lib.top_cells();
assert_eq!(tops.len(), 1);
assert_eq!(lib.get(tops[0]).name().as_str(), "pair");
}