WIP: split DRC into multiple files, bug fixed from lym management.

This commit is contained in:
Matthias Koefferlein 2019-06-23 09:25:16 +02:00
parent 717e7ca0ab
commit 04f0edc814
8 changed files with 2490 additions and 2473 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,368 @@
# $autorun-early
module DRC
# The netter object
# %DRC%
# @scope
# @name Netter
# @brief DRC Reference: Netter object
# The Netter object provides services related to network extraction
# from a layout. The relevant methods of this object are available
# as global functions too where they act on a default incarnation
# of the netter. Usually it's not required to instantiate a Netter
# object, but it serves as a container for this functionality.
#
# An individual netter object can be created, if the netter results
# need to be kept for multiple extractions. If you really need
# a Netter object, use the global \netter function:
#
# @code
# # create a new Netter object:
# nx = netter
# nx.connect(poly, contact)
# ...
# @/code
#
# Network formation:
#
# A basic Service the Netter object provides is the formation of
# connected networks of conductive shapes. To do so, the Netter
# must be given a connection specification. This happens by calling
# "connect" with two polygon layers. The Netter will then regard all
# overlaps of shapes on these layers as connections between the
# respective materials. Networks are the basis for netlist extraction,
# network geometry deduction and the antenna check.
#
# Connections can be cleared with "clear_connections". If not,
# connections add atop of the already defined ones. Here is an
# example for the antenna check:
#
# @code
# # build connction of poly+gate to metal1
# connect(gate, poly)
# connect(poly, contact)
# connect(contact, metal1)
#
# # runs an antenna check for metal1 with a ratio of 50
# m1_antenna_errors = antenna_check(gate, metal1, 50.0)
#
# # add connections to metal2
# connect(metal1, via1)
# connect(via1, metal2)
#
# # runs an antenna check for metal2 with a ratio of 70.0
# m2_antenna_errors = antenna_check(gate, metal2, 70.0)
#
# # this will remove all connections made
# clear_connections
# ...
# @/code
#
# Further functionality of the Netter object:
#
# More methods will be added in the future to support network-related features.
class DRCNetter
def initialize(engine)
@engine = engine
clear_connections
end
# %DRC%
# @name connect
# @brief Specifies a connection between two layers
# @synopsis connect(a, b)
# a and b must be polygon layers. After calling this function, the
# Netter regards all overlapping or touching shapes on these layers
# to form an electrical connection between the materials formed by
# these layers. This also implies intra-layer connections: shapes
# on these layers touching or overlapping other shapes on these
# layers will form bigger, electrically connected areas.
#
# Multiple connect calls must be made to form larger connectivity
# stacks across multiple layers. Such stacks may include forks and
# joins.
#
# Connections are accumulated. The connections defined so far
# can be cleared with \clear_connections.
def connect(a, b)
a.is_a?(DRC::DRCLayer) || raise("First argument of Netter#connect must be a layer")
b.is_a?(DRC::DRCLayer) || raise("Second argument of Netter#connect must be a layer")
a.requires_region("Netter#connect (first argument)")
b.requires_region("Netter#connect (second argument)")
[ a, b ].each { |l| @layers[l.data.data_id] = l.data }
@connections << [ a, b ].collect { |l| l.data.data_id }
modified
end
# %DRC%
# @name connect_global
# @brief Connects a layer with a global net
# @synopsis connect_global(l, name)
# Connects the shapes from the given layer l to a global net with the given name.
# Global nets are common to all cells. Global nets automatically connect to parent
# cells throughs implied pins. An example is the substrate (bulk) net which connects
# to shapes belonging to tie-down diodes.
def connect_global(l, name)
l.is_a?(DRC::DRCLayer) || raise("Layer argument of Netter#connect_global must be a layer")
l.requires_region("Netter#connect_global (layer argument)")
@layers[l.data.data_id] = l.data
@global_connections << [ l.data.data_id, name.to_s ]
end
# %DRC%
# @name extract_devices
# @brief Extracts devices based on the given extractor class, name and device layer selection
# @synopsis extract_devices(extractor, layer_hash)
# Runs the device extraction for given device extractor class.
#
# The device extractor is either an instance of one of the predefined extractor
# classes (e.g. RBA::DeviceExtractorMOS4Transistor) or a custom class. It provides the
# algorithms for deriving the device parameters from the device geometry. It needs
# several device recognition layers which are passed in the layer hash.
#
# Each device class (e.g. n-MOS/p-MOS or high Vt/low Vt) needs it's own instance
# of device extractor. The device extractor beside the algorithm and specific
# extraction settings defines the name of the device to be built.
#
# The layer hash is a map of device type specific functional names (key) and
# polygon layers (value). Here is an example:
#
# @code
# deep
#
# nwell = input(1, 0)
# active = input(2, 0)
# poly = input(3, 0)
# bulk = make_layer # renders an empty layer used for putting the terminals on
#
# nactive = active - nwell # active area of NMOS
# nsd = nactive - poly # source/drain area
# gate = nactive & poly # gate area
#
# mos4_ex = RBA::DeviceExtractorMOS4Transistor::new("NMOS4")
# extract_devices(mos4_ex, { :SD => nsd, :G => gate, :P => poly, :W => bulk })
# @/code
def extract_devices(devex, layer_selection)
devex.is_a?(RBA::DeviceExtractorBase) || raise("First argument of Netter#extract_devices must be a device extractor instance")
layer_selection.is_a?(Hash) || raise("Second argument of Netter#extract_devices must be a hash")
ls = {}
layer_selection.each do |n,l|
l.requires_region("Netter#extract_devices (#{n} layer)")
@layers[l.data.data_id] = l.data
ls[n.to_s] = l.data
end
@devices_to_extract << [ devex, ls ]
modified
end
# %DRC%
# @name clear_connections
# @brief Clears all connections stored so far
# @synopsis clear_connections
# See \connect for more details.
def clear_connections
@devices_to_extract = []
@connections = []
@global_connections = []
@layers = {}
@join_nets = ""
modified
end
# %DRC%
# @name join_nets
# @brief Specifies a search pattern for labels which create implicit net connections
# @synopsis join_nets(label_pattern)
# Use this method to supply a glob pattern for labels which create implicit net connections
# on the top level circuit. This feature is useful to connect identically labelled nets
# while a component isn't integrated yet. If the component is integrated, net may be connected
# on a higher hierarchy level - e.g. by a power mesh. Inside the component this net consists
# of individual islands. To properly perform netlist extraction and comparison, these islands
# need to be connected even though there isn't a physical connection. "join_nets" can
# achive this if these islands are labelled with the same text on the top level of the
# component.
#
# Glob pattern are used which resemble shell file pattern: "*" is for all labels, "VDD"
# for all "VDD" labels (pattern act case sensitive). "VDD*" is for all labels beginning
# with "VDD" (still different labels will be connected to different nets!). "{VDD,VSS}"
# is either "VDD" or "VSS".
#
# The search pattern is applied on the next net extraction. The search pattern is cleared
# on "clear_connections".
def join_nets(arg)
@join_nets = arg
modified
end
# %DRC%
# @brief Performs an antenna check
# @name antenna_check
# @synopsis antenna_check(gate, metal, ratio, [ diode_specs ... ])
#
# The antenna check is used to avoid plasma induced damage. Physically,
# the damage happes if during the manufacturing of a metal layer with
# plasma etching charge accumulates on the metal islands. On reaching a
# certain threshold, this charge may discarge over gate oxide attached of
# devices attached to such metal areas hence damaging it.
#
# Antenna checks are performed by collecting all connected nets up to
# a certain metal layer and then computing the area of all metal shapes
# and all connected gates of a certain kind (e.g. thin and thick oxide gates).
# The ratio of metal area divided by the gate area must not exceed a certain
# threshold.
#
# A simple antenna check is this:
#
# @code
# poly = ... # poly layer
# diff = ... # diffusion layer
# contact = ... # contact layer
# metal1 = ... # metal layer
#
# # compute gate area
# gate = poly & diff
#
# # note that gate and poly have to be included - gate is
# # a subset of poly, but forms the sensitive area
# connect(gate, poly)
# connect(poly, contact)
# connect(contact, metal1)
# errors = antenna_check(gate, metal1, 50.0)
# @/code
#
# Plasma induced damage can be rectified by including diodes
# which create a safe current path for discharging the metal
# islands. Such diodes can be identified with a recognition layer
# (usually the diffusion area of a certain kind). You can include
# such diode recognition layers in the antenna check. If a connection
# is detected to a diode, the respective network is skipped:
#
# @code
# ...
# diode = ... # diode recognition layer
#
# connect(diode, contact)
# errors = antenna_check(gate, metal1, 50.0, diode)
# @/code
#
# You can also make diode connections decreases the
# sensitivity of the antenna check depending on the size
# of the diode. The following specification makes
# diode connections increase the ratio threshold by
# 10 per square micrometer of diode area:
#
# @code
# ...
# diode = ... # diode recognition layer
#
# connect(diode, contact)
# # each square micrometer of diode area connected to a network
# # will add 10 to the ratio:
# errors = antenna_check(gate, metal1, 50.0, [ diode, 10.0 ])
# @/code
#
# Multiple diode specifications are allowed. Just add them
# to the antenna_check call.
#
# The error shapes produced by the antenna check are copies
# of the metal shapes on the metal layers of each network
# violating the antenna rule.
def antenna_check(gate, metal, ratio, *diodes)
gate.is_a?(DRC::DRCLayer) || raise("gate argument of Netter#antenna_check must be a layer")
gate.requires_region("Netter#antenna_check (gate argument)")
metal.is_a?(DRC::DRCLayer) || raise("metal argument of Netter#antenna_check must be a layer")
metal.requires_region("Netter#antenna_check (metal argument)")
if !ratio.is_a?(1.class) && !ratio.is_a?(Float)
raise("ratio argument Netter#antenna_check is not a number")
end
dl = diodes.collect do |d|
if d.is_a?(Array)
d.size == 2 || raise("diode specification pair expects two elements")
d[0].requires_region("Netter#antenna_check (diode layer)")
[ d[0].data, d[1].to_f ]
else
d.requires_region("Netter#antenna_check (diode layer)")
[ d.data, 0.0 ]
end
end
@l2n || make_l2n
DRC::DRCLayer::new(@engine, @engine._cmd(@l2n, :antenna_check, gate.data, metal.data, ratio, dl))
end
# %DRC%
# @name l2n_data
# @brief Gets the internal RBA::LayoutToNetlist object
# @synopsis l2n_data
# The RBA::LayoutToNetlist object provides access to the internal details of
# the netter object.
def l2n_data
@l2n || make_l2n
@l2n
end
def _finish
clear_connections
# cleans up the L2N object
modified
end
private
def modified
@l2n && @l2n._destroy
@l2n = nil
end
def make_l2n
if @engine._dss
# TODO: check whether all layers are deep and come from the dss and layout index,
# then use this layout index. This will remove the need for this check:
@engine._dss.is_singular? || raise("The DRC script features more than one or no layout source - network extraction cannot be performed in such configurations")
@l2n = RBA::LayoutToNetlist::new(@engine._dss)
else
layout = @engine.source.layout
@l2n = RBA::LayoutToNetlist::new(layout.top_cell.name, layout.dbu)
end
@layers.each { |id,l| @l2n.register(l, "l" + id.to_s) }
@devices_to_extract.each do |devex,ls|
@engine._cmd(@l2n, :extract_devices, devex, ls)
end
@layers.each { |id,l| @l2n.connect(l) }
@connections.each { |a,b| @l2n.connect(@layers[a], @layers[b]) }
@global_connections.each { |l,n| @l2n.connect_global(@layers[l], n) }
# run extraction in a timed environment
@engine._cmd(@l2n, :extract_netlist, @join_nets)
@l2n
end
end
end

View File

@ -0,0 +1,116 @@
# $autorun-early
# Extend the Float class by methods which convert
# values with units, i.e. 1.3nm gives 0.0013
1.0.class.class_eval do
class << self
@dbu = nil
def _dbu=(dbu)
@dbu = dbu
end
def _dbu
@dbu
end
end
def um
self
end
def micron
self
end
def degree
self
end
def nm
self*0.001
end
def mm
self*1000.0
end
def m
self*1000000.0
end
def nm2
self*1e-6
end
def um2
self
end
def micron2
self
end
def mm2
self*1.0e6
end
def m2
self*1.0e12
end
def dbu
self.class._dbu || raise("No layout loaded - cannot determine database unit")
self*self.class._dbu
end
end
# Extend the Fixnum class, so it is possible to
# convert a value to Float with a unit spec, i.e.
# 5.nm -> 0.005. A spec with ".dbu" gives the
# Fixnum value itself. This indicates a value in
# database units for most methods of the DRC
# framework.
1.class.class_eval do
class << self
@dbu = nil
def _dbu=(dbu)
@dbu = dbu
end
def _dbu
@dbu
end
end
def um
self.to_f
end
def micron
self.to_f
end
def degree
self.to_f
end
def nm
self*0.001
end
def mm
self*1000.0
end
def m
self*1000000.0
end
def nm2
self*1.0e-6
end
def um2
self.to_f
end
def micron2
self.to_f
end
def mm2
self*1.0e6
end
def m2
self*1.0e12
end
def dbu
self.class._dbu || raise("No layout loaded - cannot determine database unit")
self*self.class._dbu
end
end

View File

@ -0,0 +1,405 @@
# $autorun-early
module DRC
# A layout source representative object.
# This object describes an input. It consists of a layout reference plus
# some attributes describing how input is to be gathered.
# %DRC%
# @scope
# @name Source
# @brief DRC Reference: Source Object
# The layer object represents a collection of polygons, edges or edge pairs.
# A source specifies where to take layout from. That includes the actual layout,
# the top cell and options such as clip/query boxes, cell filters etc.
class DRCSource
def initialize(engine, layout, layout_var, cell)
@engine = engine
@layout = layout
@layout_var = layout_var
@cell = cell
@inside = nil
@box = nil
@layers = nil
@sel = []
@clip = false
@overlapping = false
@tmp_layers = []
end
# %DRC%
# @name layout
# @brief Returns the RBA::Layout object associated with this source
# @synopsis layout
def layout
@layout
end
# %DRC%
# @name cell_name
# @brief Returns the name of the currently selected cell
# @synopsis cell_name
def cell_name
@cell.name
end
# %DRC%
# @name cell_obj
# @brief Returns the RBA::Cell object of the currently selected cell
# @synopsis cell_obj
def cell_obj
@cell
end
def finish
@tmp_layers.each do |li|
@layout.delete_layer(li)
end
end
def set_box(method, *args)
box = nil
if args.size == 1
box = args[0]
box.is_a?(RBA::DBox) || raise("'#{method}' method requires a box specification")
elsif args.size == 2
(args[0].is_a?(RBA::DPoint) && args[1].is_a?(RBA::DPoint)) || raise("'#{method}' method requires a box specification with two points")
box = RBA::DBox::new(args[0], args[1])
elsif args.size == 4
box = RBA::DBox::new(*args)
else
raise("Invalid number of arguments for '#{method}' method")
end
@box = RBA::Box::from_dbox(box * (1.0 / @layout.dbu))
self
end
def inplace_clip(*args)
set_box("clip", *args)
@clip = true
@overlapping = true
end
def inplace_touching(*args)
set_box("touching", *args)
@clip = false
@overlapping = false
end
def inplace_overlapping(*args)
set_box("overlapping", *args)
@clip = false
@overlapping = true
end
def inplace_cell(arg)
@cell = layout.cell(arg)
@cell ||= layout.create_cell(arg)
self
end
def inplace_select(*args)
args.each do |a|
a.is_a?(String) || raise("Invalid arguments to 'select' method - must be strings")
@sel.push(a)
end
self
end
# %DRC%
# @name select
# @brief Adds cell name expressions to the cell filters
# @synopsis source.select(filter1, filter2, ...)
# This method will construct a new source object with the given cell filters
# applied.
# Cell filters will enable or disable cells plus their subtree.
# Cells can be switched on and off, which makes the hierarchy traversal
# stop or begin delivering shapes at the given cell. The arguments of
# the select method form a sequence of enabling or disabling instructions
# using cell name pattern in the glob notation ("*" as the wildcard, like shell).
# Disabling instructions start with a "-", enabling instructions with a "+" or
# no specification.
#
# The following options are available:
#
# @ul
# @li @tt+@/tt @i name_filter @/i: Cells matching the name filter will be enabled @/li
# @li @i name_filter @/i: Same as "+name_filter" @/li
# @li @tt-@/tt @i name_filter @/i: Cells matching the name filter will be disabled @/li
# @/ul
#
# To disable the TOP cell but enabled a hypothetical cell B below the top cell, use that
# code:
#
# @code
# layout_with_selection = layout.select("-TOP", "+B")
# l1 = layout_with_selection.input(1, 0)
# ...
# @/code
#
# Please note that the sample above will deliver the children of "B" because there is
# nothing said about how to proceed with cells other than "TOP" or "B".
# The following code will just select "B" without it's children, because in the
# first "-*" selection, all cells including the children of "B" are disabled:
#
# @code
# layout_with_selection = layout.select("-*", "+B")
# l1 = layout_with_selection.input(1, 0)
# ...
# @/code
# %DRC%
# @name cell
# @brief Specifies input from a specific cell
# @synopsis source.cell(name)
# This method will create a new source that delivers shapes from the
# specified cell.
# %DRC%
# @name clip
# @brief Specifies clipped input
# @synopsis source.clip(box)
# @synopsis source.clip(p1, p2)
# @synopsis source.clip(l, b, r, t)
# Creates a source which represents a rectangular part of the
# original input. Three ways are provided to specify the rectangular
# region: a single RBA::DBox object (micron units), two RBA::DPoint
# objects (lower/left and upper/right coordinate in micron units)
# or four coordinates: left, bottom, right and top coordinate.
#
# This method will create a new source which delivers the shapes
# from that region clipped to the rectangle. A method doing the
# same but without clipping is \touching or \overlapping.
# %DRC%
# @name touching
# @brief Specifies input selected from a region in touching mode
# @synopsis source.touching(box)
# @synopsis source.touching(p1, p2)
# @synopsis source.touching(l, b, r, t)
# Like \clip, this method will create a new source delivering shapes
# from a specified rectangular region. In contrast to clip, all shapes
# touching the region with their bounding boxes are delivered as a whole
# and are not clipped. Hence shapes may extent beyond the limits of
# the specified rectangle.
#
# \overlapping is a similar method which delivers shapes overlapping
# the search region with their bounding box (and not just touching)
# %DRC%
# @name overlapping
# @brief Specifies input selected from a region in overlapping mode
# @synopsis source.overlapping(...)
# Like \clip, this method will create a new source delivering shapes
# from a specified rectangular region. In contrast to clip, all shapes
# overlapping the region with their bounding boxes are delivered as a whole
# and are not clipped. Hence shapes may extent beyond the limits of
# the specified rectangle.
#
# \touching is a similar method which delivers shapes touching
# the search region with their bounding box (without the requirement to overlap)
# export inplace_* as * out-of-place
%w(select cell clip touching overlapping).each do |f|
eval <<"CODE"
def #{f}(*args)
s = self.dup
s.inplace_#{f}(*args)
s
end
CODE
end
# %DRC%
# @name extent
# @brief Returns a layer with the bounding box of the selected layout
# @synopsis source.extent
# The extent function is useful to invert a layer:
#
# @code
# inverse_1 = extent.sized(100.0) - input(1, 0)
# @/code
def extent
layer = input
if @box
layer.insert(RBA::DBox::from_ibox(@box) * @layout.dbu)
else
layer.insert(RBA::DBox::from_ibox(@cell.bbox) * @layout.dbu)
end
layer
end
# %DRC%
# @name input
# @brief Specifies input from a source
# @synopsis source.input(layer)
# @synopsis source.input(layer, datatype)
# @synopsis source.input(layer_into)
# @synopsis source.input(filter, ...)
# Creates a layer with the shapes from the given layer of the source.
# The layer can be specified by layer and optionally datatype, by a RBA::LayerInfo
# object or by a sequence of filters.
# Filters are expressions describing ranges
# of layers and/or datatype numbers or layer names. Multiple filters
# can be given and all layers matching at least one of these filter
# expressions are joined to render the input layer for the DRC engine.
#
# Some filter expressions are:
#
# @ul
# @li @tt 1/0-255 @/tt: Datatypes 0 to 255 for layer 1 @/li
# @li @tt 1-10 @/tt: Layers 1 to 10, datatype 0 @/li
# @li @tt METAL @/tt: A layer named "METAL" @/li
# @li @tt METAL (17/0) @/tt: A layer named "METAL" or layer 17, datatype 0 (for GDS, which does
# not have names)@/li
# @/ul
#
# Layers created with "input" contain both texts and polygons. There is a subtle
# difference between flat and deep mode: in flat mode, texts are not visible in polygon
# operations. In deep mode, texts appear as small 2x2 DBU rectangles. In flat mode,
# some operations such as clipping are not fully supported for texts. Also, texts will
# vanish in most polygon operations such as booleans etc.
#
# Texts can later be selected on the layer returned by "input" with the \Layer#texts method.
#
# If you don't want to see texts, use \polygons to create an input layer with polygon data
# only. If you only want to see texts, use \labels to create an input layer with texts only.
#
# Use the global version of "input" without a source object to address the default source.
def input(*args)
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll))
end
# %DRC%
# @name labels
# @brief Gets the labels (texts) from an input layer
# @synopsis source.labels(layer)
# @synopsis source.labels(layer, datatype)
# @synopsis source.labels(layer_into)
# @synopsis source.labels(filter, ...)
#
# Creates a layer with the labels from the given layer of the source.
#
# This method is identical to \input, but takes only texts from the given input
# layer.
#
# Use the global version of "labels" without a source object to address the default source.
def labels(*args)
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::STexts))
end
# %DRC%
# @name polygons
# @brief Gets the polygon shapes (or shapes that can be converted polygons) from an input layer
# @synopsis source.polygons(layer)
# @synopsis source.polygons(layer, datatype)
# @synopsis source.polygons(layer_into)
# @synopsis source.polygons(filter, ...)
#
# Creates a layer with the polygon shapes from the given layer of the source.
# With "polygon shapes" we mean all kind of shapes that can be converted to polygons.
# Those are boxes, paths and real polygons.
#
# This method is identical to \input with respect to the options supported.
#
# Use the global version of "polygons" without a source object to address the default source.
def polygons(*args)
layers = parse_input_layers(*args)
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SBoxes | RBA::Shapes::SPaths | RBA::Shapes::SPolygons | RBA::Shapes::SEdgePairs))
end
# %DRC%
# @name make_layer
# @brief Creates an empty polygon layer based on the hierarchy of the layout
# @synopsis make_layer
# This method delivers a new empty original layer.
def make_layer
layers = []
DRCLayer::new(@engine, @engine._cmd(@engine, :_input, @layout_var, @cell.cell_index, layers, @sel, @box, @clip, @overlapping, RBA::Shapes::SAll))
end
# %DRC%
# @name layers
# @brief Gets the layers the source contains
# @synopsis source.layers
# Delivers a list of RBA::LayerInfo objects representing the layers
# inside the source.
#
# One application is to read all layers from a source. In the following
# example, the "and" operation is used to perform a clip with the given
# rectangle. Note that this solution is not efficient - it's provided
# as an example only:
#
# @code
# output_cell("Clipped")
#
# clip_box = polygon_layer
# clip_box.insert(box(0.um, -4.um, 4.um, 0.um))
#
# layers.each { |l| (input(l) & clip_box).output(l) }
# @/code
def layers
@layout.layer_indices.collect { |li| @layout.get_info(li) }
end
private
def parse_input_layers(*args)
layers = []
if args.size == 0
li = @layout.insert_layer(RBA::LayerInfo::new)
li && layers.push(li)
li && @tmp_layers.push(li)
elsif (args.size == 1 && args[0].is_a?(RBA::LayerInfo))
li = @layout.find_layer(args[0])
li && layers.push(li)
elsif (args.size == 1 || args.size == 2) && args[0].is_a?(1.class)
li = @layout.find_layer(args[0], args[1] || 0)
li && layers.push(li)
else
args.each do |a|
if a.is_a?(String)
# use the LayerMap class to fetch the matching layers
lm = RBA::LayerMap::new
lm.map(a, 0)
@layout.layer_indices.each do |li|
if lm.is_mapped?(@layout.get_info(li))
layers.push(li)
end
end
end
end
end
layers
end
end
end

View File

@ -0,0 +1,116 @@
# $autorun-early
module DRC
# A wrapper for a named value which is stored in
# a variable for delayed execution
class DRCVar
def initialize(name)
@name = name
end
def inspect
@name
end
def to_s
@name
end
end
# A wrapper for the sizing mode value
class DRCSizingMode
attr_accessor :value
def initialize(v)
self.value = v
end
end
# A wrapper for the join flag for extended
class DRCJoinFlag
attr_accessor :value
def initialize(v)
self.value = v
end
end
# A wrapper for the angle limit
# The purpose of this wrapper is to identify the
# angle limit specification
class DRCAngleLimit
attr_accessor :value
def initialize(v)
self.value = v
end
end
# A wrapper for a metrics constant
# The purpose of this wrapper is to identify the
# metrics constant by the class.
class DRCMetrics
attr_accessor :value
def initialize(v)
self.value = v
end
end
# A wrapper for the "whole edges" flag for
# the DRC functions. The purpose of this class
# is to identify the value by the class.
class DRCWholeEdges
attr_accessor :value
def initialize(v)
self.value = v
end
end
# A wrapper for the "as_dots" or "as_boxes" flag for
# some DRC functions. The purpose of this class
# is to identify the value by the class.
class DRCAsDots
attr_accessor :value
def initialize(v)
self.value = v
end
end
# A wrapper for a glob-pattern style text selection for
# some DRC functions. The purpose of this class
# is to identify the value by the class.
class DRCPattern
attr_accessor :as_pattern
attr_accessor :pattern
def initialize(f, p)
self.as_pattern = f
self.pattern = p
end
end
# A wrapper for a pair of limit values
# This class is used to identify projection limits for DRC
# functions
class DRCProjectionLimits
attr_accessor :min
attr_accessor :max
def initialize(*a)
if a.size > 2 || a.size == 0
raise("A projection limits specification requires a maximum of two values and at least one argument")
elsif a.size == 1
if !a[0].is_a?(Range) || (!a[0].min.is_a?(Float) && !a[0].min.is_a?(1.class))
raise("A projection limit requires an interval of two length values or two individual length values")
end
self.min = a[0].min
self.max = a[0].max
elsif a.size == 2
if a[0] && !a[0].is_a?(Float) && !a[0].is_a?(1.class)
raise("First argument to a projection limit must be either nil or a length value")
end
if a[1] && !a[1].is_a?(Float) && !a[1].is_a?(1.class)
raise("Second argument to a projection limit must be either nil or a length value")
end
self.min = a[0]
self.max = a[1]
end
end
end
end

View File

@ -1,6 +1,11 @@
<RCC>
<qresource prefix="/built-in-macros">
<file alias="drc.lym">built-in-macros/drc.lym</file>
<file alias="_drc_engine.rb">built-in-macros/_drc_engine.rb</file>
<file alias="_drc_layer.rb">built-in-macros/_drc_layer.rb</file>
<file alias="_drc_netter.rb">built-in-macros/_drc_netter.rb</file>
<file alias="_drc_patch.rb">built-in-macros/_drc_patch.rb</file>
<file alias="_drc_source.rb">built-in-macros/_drc_source.rb</file>
<file alias="_drc_tags.rb">built-in-macros/_drc_tags.rb</file>
<file alias="drc_interpreters.lym">built-in-macros/drc_interpreters.lym</file>
</qresource>
</RCC>

View File

@ -684,16 +684,19 @@ void Macro::sync_properties_with_text ()
for (size_t i = 0; i < sizeof (property_fields) / sizeof (property_fields[0]); ++i) {
tl::Extractor pex = ex;
const PropertyField *pf = property_fields + i;
if (ex.test (pf->name)) {
if (pex.test (pf->name) && (pex.at_end () || pex.test (":"))) {
if (pf->string_setter) {
ex.test (":");
(this->*(pf->string_setter)) (unescape_pta_string (ex.skip ()));
(this->*(pf->string_setter)) (unescape_pta_string (pex.skip ()));
} else if (pf->bool_setter) {
(this->*(pf->bool_setter)) (true);
}
break;
}
}
@ -1334,6 +1337,7 @@ void MacroCollection::scan (const std::string &path)
ResourceWithChildren res (tl::to_qstring (path));
QStringList children = res.children ();
children.sort ();
for (QStringList::const_iterator c = children.begin (); c != children.end (); ++c) {