Skip to main content

klayout_core/
edit.rs

1//! Hierarchy editing primitives.
2//!
3//! All operations return new `CellBuilder`s — frozen cells in the
4//! `Library` are never mutated. The caller chooses whether to insert the
5//! result into the same library, a new library, or just inspect it.
6//!
7//! Available operations:
8//! * [`flatten`] — produce a single cell containing all shapes from the
9//!   instance subtree, transforms composed.
10//! * [`remap_layers`] — return a new `CellBuilder` with shapes remapped to
11//!   different layers per a `LayerMap`.
12//! * [`replace_instances`] — rewrite instances of one cell to another by
13//!   a user-supplied predicate.
14
15use crate::{
16    Bbox, CellBuilder, CellId, CellName, Instance, LayerIndex, Library, Polygon, Rect, Repetition,
17    Shape, Trans, Vec2,
18};
19use std::collections::HashMap;
20
21/// Flatten a cell's instance hierarchy `max_depth` levels deep into a
22/// single cell. `max_depth = 0` is a no-op (no children flattened);
23/// `max_depth = usize::MAX` flattens everything.
24pub fn flatten(
25    lib: &Library,
26    cell: CellId,
27    max_depth: usize,
28    new_name: impl Into<CellName>,
29) -> CellBuilder {
30    let mut out = CellBuilder::new(new_name);
31    walk(lib, cell, Trans::IDENTITY, max_depth, &mut out);
32    out
33}
34
35fn walk(
36    lib: &Library,
37    cell: CellId,
38    trans: Trans,
39    depth_remaining: usize,
40    out: &mut CellBuilder,
41) {
42    let c = lib.get(cell);
43    // Copy shapes with the composed trans applied.
44    for layer in c.layers() {
45        for shape in c.shapes_on(layer) {
46            let s = shape.transform(trans);
47            out.add_shape(layer, s);
48        }
49    }
50    if depth_remaining == 0 {
51        // Keep instances as-is (with composed trans).
52        for inst in c.instances() {
53            let mut new_inst = inst.clone();
54            new_inst.trans = trans.compose(inst.trans);
55            out.add_instance(new_inst);
56        }
57        return;
58    }
59    for inst in c.instances() {
60        for placement in expand_repetition(inst) {
61            let composed = trans.compose(placement);
62            walk(lib, inst.cell, composed, depth_remaining - 1, out);
63        }
64    }
65}
66
67fn expand_repetition(inst: &Instance) -> Vec<Trans> {
68    match &inst.repetition {
69        None => vec![inst.trans],
70        Some(Repetition::Regular {
71            col,
72            row,
73            n_cols,
74            n_rows,
75        }) => {
76            let mut out = Vec::with_capacity((*n_cols as usize) * (*n_rows as usize));
77            for j in 0..*n_rows {
78                for i in 0..*n_cols {
79                    let extra = Trans::translate(Vec2::new(
80                        col.x * i as i64 + row.x * j as i64,
81                        col.y * i as i64 + row.y * j as i64,
82                    ));
83                    out.push(extra.compose(inst.trans));
84                }
85            }
86            out
87        }
88        Some(Repetition::Irregular { offsets }) => {
89            let mut out = Vec::with_capacity(offsets.len() + 1);
90            out.push(inst.trans);
91            for o in offsets {
92                let extra = Trans::translate(*o);
93                out.push(extra.compose(inst.trans));
94            }
95            out
96        }
97    }
98}
99
100/// A layer remapping table — `from` → `to` rewrites every shape on
101/// `from` to land on `to`. Layers not in the map pass through unchanged.
102#[derive(Default, Clone, Debug)]
103pub struct LayerMap {
104    inner: HashMap<LayerIndex, LayerIndex>,
105}
106
107impl LayerMap {
108    pub fn new() -> Self {
109        Self::default()
110    }
111
112    pub fn map(mut self, from: LayerIndex, to: LayerIndex) -> Self {
113        self.inner.insert(from, to);
114        self
115    }
116
117    pub fn translate(&self, layer: LayerIndex) -> LayerIndex {
118        self.inner.get(&layer).copied().unwrap_or(layer)
119    }
120
121    pub fn is_empty(&self) -> bool {
122        self.inner.is_empty()
123    }
124}
125
126/// Rebuild `cell` with all shapes' layers translated through `map`.
127/// Instances are preserved unchanged (the caller can also remap layers
128/// on each child by walking the hierarchy themselves).
129pub fn remap_layers(lib: &Library, cell: CellId, map: &LayerMap) -> CellBuilder {
130    let c = lib.get(cell);
131    let mut out = CellBuilder::new(c.name().clone());
132    for layer in c.layers() {
133        let dst = map.translate(layer);
134        for shape in c.shapes_on(layer) {
135            out.add_shape(dst, shape.clone());
136        }
137    }
138    for inst in c.instances() {
139        out.add_instance(inst.clone());
140    }
141    out
142}
143
144/// Clip a cell to `bbox` — return a `CellBuilder` containing only shapes
145/// whose bbox intersects `bbox`. Boxes are clipped to `bbox`; polygons
146/// and paths whose bbox extends past it are kept whole (proper polygon
147/// clipping needs `klayout-geom`'s boolean ops — call those if exact
148/// edge clipping matters). Instances are kept if their placement bbox
149/// (`trans.disp`) lies inside `bbox` and dropped otherwise.
150pub fn clip_cell(
151    lib: &Library,
152    cell: CellId,
153    bbox: Bbox,
154    new_name: impl Into<CellName>,
155) -> CellBuilder {
156    let c = lib.get(cell);
157    let mut out = CellBuilder::new(new_name);
158    if bbox.is_empty() {
159        return out;
160    }
161    for layer in c.layers() {
162        for shape in c.shapes_on(layer) {
163            let sbb = shape.bbox();
164            if !sbb.intersects(&bbox) {
165                continue;
166            }
167            match shape {
168                Shape::Box(r) => {
169                    let clipped = bbox_intersection(r.bbox, bbox);
170                    if !clipped.is_empty() {
171                        out.add_shape(layer, Rect::new(clipped));
172                    }
173                }
174                Shape::Polygon(p) => {
175                    // Approximate: keep the polygon as-is. Exact clipping
176                    // would use `klayout_geom::intersection`.
177                    out.add_shape(layer, p.clone());
178                }
179                Shape::Path(p) => out.add_shape(layer, p.clone()),
180                Shape::Text(t) => {
181                    if bbox.contains(t.anchor) {
182                        out.add_shape(layer, t.clone());
183                    }
184                }
185            }
186        }
187    }
188    for inst in c.instances() {
189        // Approximate inclusion: keep instance if its anchor (trans.disp)
190        // is inside the clip bbox. This is a coarse filter; users who
191        // need precise instance clipping should flatten first.
192        let anchor = klayout_core_alias::Point::new(inst.trans.disp.x, inst.trans.disp.y);
193        if bbox.contains(anchor) {
194            out.add_instance(inst.clone());
195        }
196    }
197    out
198}
199
200mod klayout_core_alias {
201    pub use crate::Point;
202}
203
204fn bbox_intersection(a: Bbox, b: Bbox) -> Bbox {
205    if a.is_empty() || b.is_empty() {
206        return Bbox::EMPTY;
207    }
208    let min = klayout_core_alias::Point::new(a.min.x.max(b.min.x), a.min.y.max(b.min.y));
209    let max = klayout_core_alias::Point::new(a.max.x.min(b.max.x), a.max.y.min(b.max.y));
210    if min.x > max.x || min.y > max.y {
211        Bbox::EMPTY
212    } else {
213        Bbox::new(min, max)
214    }
215}
216
217/// Bbox of the cell restricted to its top-level shapes (no descent into
218/// instance children). Convenience for clip-at-top workflows.
219pub fn local_bbox(lib: &Library, cell: CellId) -> Bbox {
220    lib.get(cell).local_bbox()
221}
222
223#[allow(dead_code)]
224fn _polygon_marker(_: &Polygon) {}
225
226/// Rewrite instances of cells matching `predicate` to point at
227/// `replacement`. The `predicate` is called with the original instance's
228/// `CellId` and returns `true` to swap. Shapes are preserved.
229pub fn replace_instances<F: Fn(CellId) -> bool>(
230    lib: &Library,
231    cell: CellId,
232    predicate: F,
233    replacement: CellId,
234) -> CellBuilder {
235    let c = lib.get(cell);
236    let mut out = CellBuilder::new(c.name().clone());
237    for layer in c.layers() {
238        for shape in c.shapes_on(layer) {
239            out.add_shape(layer, shape.clone());
240        }
241    }
242    for inst in c.instances() {
243        let mut new_inst = inst.clone();
244        if predicate(inst.cell) {
245            new_inst.cell = replacement;
246        }
247        out.add_instance(new_inst);
248    }
249    out
250}
251