Property Utilities

LayKit provides utilities for working with GDSII and OASIS properties, making it easier to add, query, and manage metadata.

Overview

Properties are metadata attached to layout elements. They're used for:

  • Design annotations
  • Manufacturing instructions
  • Verification markers
  • Tool-specific data
  • Custom metadata

Property Builder (GDSII)

The PropertyBuilder provides a fluent interface for creating properties:

#![allow(unused)]
fn main() {
use laykit::PropertyBuilder;

let properties = PropertyBuilder::new()
    .add(1, "DEVICE_TYPE=NMOS".to_string())
    .add(2, "WIDTH=10um".to_string())
    .add(3, "LENGTH=0.5um".to_string())
    .build();

// Use in an element
let boundary = Boundary {
    layer: 1,
    datatype: 0,
    xy: vec![(0, 0), (100, 0), (100, 100), (0, 100), (0, 0)],
    properties,
};
}

Property Manager

The PropertyManager helps query and manipulate existing properties:

#![allow(unused)]
fn main() {
use laykit::PropertyManager;

// Create from existing properties
let manager = PropertyManager::from_properties(&boundary.properties);

// Query properties
if let Some(value) = manager.get(1) {
    println!("Device type: {}", value);
}

// Check existence
if manager.has_property(2) {
    println!("Width property exists");
}

// Get all attributes
let attrs = manager.attributes();
println!("Property attributes: {:?}", attrs);

// Convert back to property list
let updated_props = manager.to_properties();
}

OASIS Properties

OASIS properties support multiple value types:

#![allow(unused)]
fn main() {
use laykit::OASISPropertyBuilder;

let properties = OASISPropertyBuilder::new()
    .add_string("name".to_string(), "MyDevice".to_string())
    .add_integer("count".to_string(), 42)
    .add_real("temperature".to_string(), 25.5)
    .add_boolean("verified".to_string(), true)
    .build();

// Use in OASIS element
let rectangle = Rectangle {
    layer: 1,
    datatype: 0,
    x: 0,
    y: 0,
    width: 1000,
    height: 500,
    repetition: None,
    properties,
};
}

Common Use Cases

Device Annotations

#![allow(unused)]
fn main() {
fn annotate_device(boundary: &mut Boundary, device_type: &str, params: &HashMap<String, String>) {
    let mut builder = PropertyBuilder::new();
    
    // Add device type
    builder = builder.add(1, format!("TYPE={}", device_type));
    
    // Add parameters
    for (i, (key, value)) in params.iter().enumerate() {
        builder = builder.add((i + 2) as i16, format!("{}={}", key, value));
    }
    
    boundary.properties = builder.build();
}
}

Manufacturing Instructions

#![allow(unused)]
fn main() {
fn add_manufacturing_notes(element: &mut GDSElement, notes: Vec<String>) {
    let mut builder = PropertyBuilder::new();
    
    for (i, note) in notes.iter().enumerate() {
        builder = builder.add((i + 100) as i16, note.clone());
    }
    
    match element {
        GDSElement::Boundary(b) => b.properties = builder.build(),
        GDSElement::Path(p) => p.properties = builder.build(),
        _ => {}
    }
}
}

Property Filtering

#![allow(unused)]
fn main() {
fn filter_by_property(structure: &GDSStructure, attr: i16, value: &str) -> Vec<&GDSElement> {
    structure.elements.iter()
        .filter(|elem| {
            let props = match elem {
                GDSElement::Boundary(b) => &b.properties,
                GDSElement::Path(p) => &p.properties,
                _ => return false,
            };
            
            let manager = PropertyManager::from_properties(props);
            manager.get(attr) == Some(value)
        })
        .collect()
}
}

Property Extraction

#![allow(unused)]
fn main() {
fn extract_all_properties(file: &GDSIIFile) -> HashMap<String, Vec<String>> {
    let mut property_map = HashMap::new();
    
    for structure in &file.structures {
        for element in &structure.elements {
            let props = match element {
                GDSElement::Boundary(b) => &b.properties,
                GDSElement::Path(p) => &p.properties,
                GDSElement::Text(t) => &t.properties,
                _ => continue,
            };
            
            let manager = PropertyManager::from_properties(props);
            for attr in manager.attributes() {
                if let Some(value) = manager.get(attr) {
                    property_map
                        .entry(format!("ATTR_{}", attr))
                        .or_insert_with(Vec::new)
                        .push(value.to_string());
                }
            }
        }
    }
    
    property_map
}
}

Property Best Practices

Attribute Numbering

Use consistent attribute numbering:

#![allow(unused)]
fn main() {
// Define constants for property attributes
const PROP_DEVICE_TYPE: i16 = 1;
const PROP_WIDTH: i16 = 2;
const PROP_LENGTH: i16 = 3;
const PROP_MANUFACTURER: i16 = 100;
const PROP_LOT_NUMBER: i16 = 101;

let props = PropertyBuilder::new()
    .add(PROP_DEVICE_TYPE, "NMOS".to_string())
    .add(PROP_WIDTH, "10".to_string())
    .add(PROP_MANUFACTURER, "ACME Corp".to_string())
    .build();
}

Value Formatting

Use consistent value formatting:

#![allow(unused)]
fn main() {
// Good: Structured format
builder.add(1, format!("DEVICE=NMOS WIDTH={}um LENGTH={}um", w, l));

// Good: Key-value pairs
builder.add(1, format!("device:NMOS"));
builder.add(2, format!("width:{}um", w));

// Avoid: Inconsistent formats
builder.add(1, format!("NMOS {} {}", w, l)); // Hard to parse
}

Property Validation

Validate properties after loading:

#![allow(unused)]
fn main() {
fn validate_properties(element: &GDSElement) -> Result<(), String> {
    let props = match element {
        GDSElement::Boundary(b) => &b.properties,
        _ => return Ok(()),
    };
    
    let manager = PropertyManager::from_properties(props);
    
    // Check required properties
    if !manager.has_property(PROP_DEVICE_TYPE) {
        return Err("Missing DEVICE_TYPE property".to_string());
    }
    
    // Validate values
    if let Some(width) = manager.get(PROP_WIDTH) {
        if width.parse::<f64>().is_err() {
            return Err("Invalid WIDTH value".to_string());
        }
    }
    
    Ok(())
}
}

Performance Notes

  • Properties are stored as vectors (O(n) lookup)
  • PropertyManager uses HashMap for fast access (O(1) average)
  • Use PropertyManager for frequent queries
  • Use PropertyBuilder for construction
  • Properties add minimal overhead to file size

GDSII vs OASIS Properties

FeatureGDSIIOASIS
AttributeInteger (i16)String name
Value TypeString onlyMultiple types
SizeFixed overheadVariable
LookupBy numberBy name

Future Enhancements

Planned improvements:

  • Property schema validation
  • Type-safe property accessors
  • Property template system
  • Conversion utilities between GDSII/OASIS properties