More intuitive DRC specification of 'projection_limits' with 'projecting' and a condition

This commit is contained in:
Matthias Koefferlein 2021-01-13 15:58:55 +01:00
parent c5859cd957
commit 80d0229b7c
5 changed files with 283 additions and 43 deletions

View File

@ -96,7 +96,20 @@ module DRC
def projection_limits(*args)
self._context("projection_limits") do
DRCProjectionLimits::new(*args)
if args.size == 0
raise("At least one argument is required")
end
res = DRCProjectionLimits::new(*args)
res.description = "projection_limits"
res
end
end
def projecting
self._context("projecting") do
res = DRCProjectionLimits::new
res.description = "projecting"
res
end
end
@ -2312,6 +2325,30 @@ CODE
v
end
def _make_value_with_nil(v)
if v == nil
return v
end
self._check_numeric(v)
self._prep_value(v)
end
def _make_area_value_with_nil(v)
if v == nil
return v
end
self._check_numeric(v)
self._prep_value_area(v)
end
def _make_numeric_value_with_nil(v)
if v == nil
return v
end
self._check_numeric(v)
v
end
private
def _make_string(v)

View File

@ -344,12 +344,12 @@ module DRC
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._prep_value_area(a.first), @engine._prep_value_area(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_area_value_with_nil(a.begin), @engine._make_area_value_with_nil(a.end), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._prep_value_area(a), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_area_value(a), #{inv.inspect}))
end
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._prep_value_area(args[0]), @engine._prep_value_area(args[1]), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_area_value_with_nil(args[0]), @engine._make_area_value_with_nil(args[1]), #{inv.inspect}))
else
raise("Invalid number of arguments (1 or 2 expected)")
end
@ -500,12 +500,12 @@ CODE
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._prep_value(a.first), @engine._prep_value(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_value_with_nil(a.begin), @engine._make_value_with_nil(a.end), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._prep_value(a), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_value(a), #{inv.inspect}))
end
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._prep_value(args[0]), @engine._prep_value(args[1]), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_value_with_nil(args[0]), @engine._make_value_with_nil(args[1]), #{inv.inspect}))
else
raise("Invalid number of arguments (1 or 2 expected)")
end
@ -600,7 +600,7 @@ CODE
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_numeric_value(a.first), @engine._make_numeric_value(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_numeric_value_with_nil(a.begin), @engine._make_numeric_value_with_nil(a.end), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :with_#{f}, @engine._make_numeric_value(a), #{inv.inspect}))
end
@ -655,12 +655,12 @@ CODE
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :with_#{f}, @engine._prep_value(a.first), @engine._prep_value(a.last), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :with_#{f}, @engine._make_value_with_nil(a.begin), @engine._make_value_with_nil(a.end), #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :with_#{f}, @engine._prep_value(a), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :with_#{f}, @engine._make_value(a), #{inv.inspect}))
end
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :with_#{f}, @engine._prep_value(args[0]), @engine._prep_value(args[1]), #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :with_#{f}, @engine._make_value_with_nil(args[0]), @engine._make_value_with_nil(args[1]), #{inv.inspect}))
else
raise("Invalid number of arguments (1 or 2 expected)")
end
@ -733,7 +733,7 @@ CODE
if args.size == 1
a = args[0]
if a.is_a?(Range)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, result_class, :with_angle, a.first, a.last, #{inv.inspect}))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, result_class, :with_angle, a.begin, a.end, #{inv.inspect}))
else
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, result_class, :with_angle, a, #{inv.inspect}))
end
@ -779,7 +779,7 @@ CODE
def rounded_corners(inner, outer, n)
@engine._context("rounded_corners") do
requires_region
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :rounded_corners, @engine._prep_value(inner), @engine._prep_value(outer), n))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :rounded_corners, @engine._make_value(inner), @engine._make_value(outer), n))
end
end
@ -798,7 +798,7 @@ CODE
def smoothed(d)
@engine._context("smoothed") do
requires_region
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :smoothed, @engine._prep_value(d)))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :smoothed, @engine._make_value(d)))
end
end
@ -1365,9 +1365,9 @@ CODE
@engine._context("ongrid") do
requires_region
if args.size == 1
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::EdgePairs, :grid_check, @engine._prep_value(args[0]), @engine._prep_value(args[0])))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::EdgePairs, :grid_check, @engine._make_value(args[0]), @engine._make_value(args[0])))
elsif args.size == 2
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::EdgePairs, :grid_check, @engine._prep_value(args[0]), @engine._prep_value(args[1])))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::EdgePairs, :grid_check, @engine._make_value(args[0]), @engine._make_value(args[1])))
else
raise("Invalid number of arguments (1 or 2 expected)")
end
@ -1406,14 +1406,13 @@ CODE
requires_region
gx = gy = 0
if args.size == 1
gx = gy = @engine._prep_value(args[0])
gx = gy = @engine._make_value(args[0])
elsif args.size == 2
gx = @engine._prep_value(args[0])
gy = @engine._prep_value(args[1])
gx = @engine._make_value(args[0])
gy = @engine._make_value(args[1])
else
raise("Invalid number of arguments (1 or 2 expected)")
end
aa = args.collect { |a| @engine._prep_value(a) }
if :#{f} == :snap && @engine.is_tiled?
# in tiled mode, no modifying versions are available
self.data = @engine._tcmd(self.data, 0, self.data.class, :snapped, gx, gy)
@ -2606,7 +2605,7 @@ CODE
def #{f}(length, fraction = 0.0)
@engine._context("#{f}") do
requires_edges
length = @engine._prep_value(length)
length = @engine._make_value(length)
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Edges, :#{f}, length, fraction))
end
end
@ -2657,16 +2656,16 @@ CODE
av = [ 0, 0, 0, 0, false ]
args.each_with_index do |a,i|
if a.is_a?(Hash)
a[:begin] && av[0] = @engine._prep_value(a[:begin])
a[:end] && av[1] = @engine._prep_value(a[:end])
a[:out] && av[2] = @engine._prep_value(a[:out])
a[:in] && av[3] = @engine._prep_value(a[:in])
a[:begin] && av[0] = @engine._make_value(a[:begin])
a[:end] && av[1] = @engine._make_value(a[:end])
a[:out] && av[2] = @engine._make_value(a[:out])
a[:in] && av[3] = @engine._make_value(a[:in])
a[:joined] && av[4] = true
elsif i < 4
if !a.is_a?(1.class) && !a.is_a?(Float)
raise("Invalid type for argument #" + (i+1).to_s)
end
av[i] = @engine._prep_value(a)
av[i] = @engine._make_value(a)
elsif i == 4
if a.is_a?(DRCJoinFlag)
av[i] = a.value
@ -2715,7 +2714,7 @@ CODE
def #{f}(dist)
@engine._context("#{f}") do
requires_edges
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :#{f}, @engine._prep_value(dist)))
DRCLayer::new(@engine, @engine._tcmd(self.data, 0, RBA::Region, :#{f}, @engine._make_value(dist)))
end
end
CODE
@ -2999,6 +2998,10 @@ CODE
# @li @b projection_limits(min, max) or projection_limits(min .. max) @/b:
# this option makes the check only consider edge pairs whose projected length on
# each other is more or equal than min and less than max @/li
# @li @b projecting (in condition) @/b: This specification is equivalent to "projection_limits"
# but is more intuitive, as "projecting" is written with a condition, like
# "projecting < 2.um". Available operators are: "==", "<", "<=", ">" and ">=".
# Double-bounded ranges are also available, like: "0.5 <= projecting < 2.0".
# @li @b transparent @/b: performs the check without shielding (polygon layers only) @/li
# @li @b shielded @/b: performs the check with shielding (polygon layers only) @/li
# @/ul
@ -3333,13 +3336,12 @@ CODE
elsif a.is_a?(DRCLayer)
other = a
elsif a.is_a?(DRCProjectionLimits)
minp = @engine._prep_value(a.min)
maxp = @engine._prep_value(a.max)
(minp, maxp) = a.get_limits(@engine)
elsif a.is_a?(DRCShielded)
shielded = a.value
elsif a.is_a?(Float) || a.is_a?(1.class)
value && raise("Value already specified")
value = @engine._prep_value(a)
value = @engine._make_value(a)
else
raise("Parameter #" + n.to_s + " does not have an expected type")
end
@ -3542,7 +3544,7 @@ CODE
values = []
args.each do |a|
if a.is_a?(1.class) || a.is_a?(Float)
v = @engine._prep_value(a)
v = @engine._make_value(a)
v.abs > dist && dist = v.abs
values.push(v)
elsif a.is_a?(DRCSizingMode)

View File

@ -2,6 +2,157 @@
module DRC
# A base class for implementing ranges that can be put into a condition
module DRCComparable
attr_accessor :reverse
attr_accessor :original
attr_accessor :lt, :le, :gt, :ge
attr_accessor :description
attr_accessor :mode_or_supported
attr_accessor :mode_or
def _init_comparable
self.reverse = false
self.original = nil
self.le = nil
self.ge = nil
self.lt = nil
self.gt = nil
self.gt = nil
self.description = ""
self.mode_or_supported = false
self.mode_or = false
end
def _check_bounds
if ! self.mode_or && (self.lt || self.le) && (self.gt || self.ge)
epsilon = 1e-10
lower = self.ge ? self.ge - epsilon : self.gt + epsilon
upper = self.le ? self.le + epsilon : self.lt - epsilon
if lower > upper - epsilon
raise("'" + self.description + "': lower bound is larger than upper bound")
end
end
end
def set_lt(value)
(self.lt || self.le) && raise("'" + self.description + "' already has an upper bound of " + ("%.12g" % (self.lt || self.le)))
self.lt = value
self._check_bounds
end
def set_le(value)
(self.lt || self.le) && raise("'" + self.description + "' already has an upper bound of " + ("%.12g" % (self.lt || self.le)))
self.le = value
self._check_bounds
end
def set_gt(value)
(self.gt || self.ge) && raise("'" + self.description + "' already has an lower bound of " + ("%.12g" % (self.gt || self.ge)))
self.gt = value
self._check_bounds
end
def set_ge(value)
(self.gt || self.ge) && raise("'" + self.description + "' already has an lower bound of " + ("%.12g" % (self.gt || self.ge)))
self.ge = value
self._check_bounds
end
def coerce(something)
reversed = self.dup
reversed.reverse = true
reversed.original = self
[ reversed, something ]
end
def _self_or_original
return (self.original || self).dup
end
def !=(other)
if self.respond_to?(:inverted)
res = self.==(other).inverted
else
if !self.mode_or_supported
raise("!= operator is not allowed for '" + self.description + "'")
end
if !(other.is_a?(Float) || other.is_a?(Integer))
raise("!= operator needs a numerical argument for '" + self.description + "' argument")
end
res = self._self_or_original
res.mode_or = true
res.set_lt(other)
res.set_gt(other)
end
res
end
def ==(other)
if !(other.is_a?(Float) || other.is_a?(Integer))
raise("== operator needs a numerical argument for '" + self.description + "' argument")
end
res = self._self_or_original
res.set_le(other)
res.set_ge(other)
return res
end
def <(other)
if !(other.is_a?(Float) || other.is_a?(Integer))
raise("< operator needs a numerical argument for '" + self.description + "' argument")
end
res = self._self_or_original
if reverse
res.set_gt(other)
else
res.set_lt(other)
end
return res
end
def <=(other)
if !(other.is_a?(Float) || other.is_a?(Integer))
raise("<= operator needs a numerical argument for '" + self.description + "' argument")
end
res = self._self_or_original
if reverse
res.set_ge(other)
else
res.set_le(other)
end
return res
end
def >(other)
if !(other.is_a?(Float) || other.is_a?(Integer))
raise("> operator needs a numerical argument for '" + self.description + "' argument")
end
res = self._self_or_original
if reverse
res.set_lt(other)
else
res.set_gt(other)
end
return res
end
def >=(other)
if !(other.is_a?(Float) || other.is_a?(Integer))
raise(">= operator needs a numerical argument for '" + self.description + "' argument")
end
res = self._self_or_original
if reverse
res.set_le(other)
else
res.set_ge(other)
end
return res
end
end
# A wrapper for a named value which is stored in
# a variable for delayed execution
class DRCVar
@ -106,28 +257,55 @@ module DRC
# This class is used to identify projection limits for DRC
# functions
class DRCProjectionLimits
attr_accessor :min
attr_accessor :max
include DRCComparable
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")
_init_comparable
if a.size > 2
raise("A projection limits specification requires a maximum of two values")
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
self.set_ge(a[0].min)
self.set_lt(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]
self.set_ge(a[0])
self.set_lt(a[1])
end
end
def get_limits(engine)
if ! self.le && ! self.ge && ! self.lt && ! self.gt
raise("No constraint given for projection limits (supply values or place inside a condition)")
end
min = self.ge ? engine._make_value(self.ge) : (self.gt ? engine._make_value(self.gt) + 1 : 0)
max = self.le ? engine._make_value(self.le) + 1 : (self.lt ? engine._make_value(self.lt) : nil)
[ min, max ]
end
end
# A wrapper for an input for the antenna check

View File

@ -14,6 +14,29 @@ l1.output(1, 0)
l2.output(2, 0)
l3.output(3, 0)
# Self-test "projection_limits"
def self_test(id, a, b)
a == b || raise(id + ": self-test failed (" + a.inspect + " != " + b.inspect + ")")
end
l = projection_limits(0..2.0)
self_test("projection_limits(1)", l.get_limits(self), [ 0, 2000 ])
l = projecting < 2.0
self_test("projection_limits(2)", l.get_limits(self), [ 0, 2000 ])
l = projecting <= 2.0
self_test("projection_limits(3)", l.get_limits(self), [ 0, 2001 ])
l = projecting == 2.0
self_test("projection_limits(3)", l.get_limits(self), [ 2000, 2001 ])
l = projecting > 2.0
self_test("projection_limits(3)", l.get_limits(self), [ 2001, nil ])
l = projecting >= 2.0
self_test("projection_limits(3)", l.get_limits(self), [ 2000, nil ])
l = 1.0 < projecting < 2.0
self_test("projection_limits(3)", l.get_limits(self), [ 1001, 2000 ])
l1.space(1.0, projection).polygons.output(100, 0)
l1.space(1.0, euclidian).polygons.output(110, 0)
@ -23,7 +46,7 @@ l1.space(1.0, projection, only_opposite).output(113, 0)
l1.space(1.0, projection, not_opposite).output(114, 0)
l1.isolated(1.0, euclidian).polygons.output(120, 0)
l1.isolated(1.0, projection, projection_limits(0..2.0)).polygons.output(121, 0)
l1.isolated(1.0, projection, projecting < 2.0.um).polygons.output(121, 0)
l1.isolated(1.0, projection, whole_edges).polygons.output(122, 0)
l1.isolated(1.0, projection, only_opposite).output(123, 0)
l1.isolated(1.0, projection, not_opposite).output(124, 0)

View File

@ -17,7 +17,7 @@ l3.output(3, 0)
ar = 4.0/3.0 # "L" shape
l1.with_area_ratio(ar).output(100, 0)
l1.without_area_ratio(ar).output(101, 0)
l1.without_area_ratio(0..ar).output(102, 0)
l1.with_area_ratio(ar+1e-6..).output(102, 0)
l1.with_area_ratio(0.0, ar-1e-6).output(103, 0)
l1.squares.output(110, 0)
@ -25,12 +25,12 @@ l1.non_squares.output(111, 0)
l2.with_bbox_aspect_ratio(2).output(120, 0)
l2.with_bbox_aspect_ratio(1).output(121, 0)
l2.with_bbox_aspect_ratio(0..1).output(122, 0)
l2.with_bbox_aspect_ratio(..1).output(122, 0)
l2.without_bbox_aspect_ratio(0..2).output(123, 0)
l2.with_relative_height(2).output(130, 0)
l2.with_relative_height(1).output(131, 0)
l2.with_relative_height(0..1).output(132, 0)
l2.with_relative_height(..1).output(132, 0)
l2.without_relative_height(0..2).output(133, 0)