diff --git a/src/drc/drc/built-in-macros/_drc_engine.rb b/src/drc/drc/built-in-macros/_drc_engine.rb index 5dbe50541..2d449e49f 100644 --- a/src/drc/drc/built-in-macros/_drc_engine.rb +++ b/src/drc/drc/built-in-macros/_drc_engine.rb @@ -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) diff --git a/src/drc/drc/built-in-macros/_drc_layer.rb b/src/drc/drc/built-in-macros/_drc_layer.rb index bccc40bdc..0732e316e 100644 --- a/src/drc/drc/built-in-macros/_drc_layer.rb +++ b/src/drc/drc/built-in-macros/_drc_layer.rb @@ -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) diff --git a/src/drc/drc/built-in-macros/_drc_tags.rb b/src/drc/drc/built-in-macros/_drc_tags.rb index eaf58624b..4efcde2cc 100644 --- a/src/drc/drc/built-in-macros/_drc_tags.rb +++ b/src/drc/drc/built-in-macros/_drc_tags.rb @@ -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 diff --git a/testdata/drc/drcSimpleTests_25.drc b/testdata/drc/drcSimpleTests_25.drc index a5d88c616..57a23a4fe 100644 --- a/testdata/drc/drcSimpleTests_25.drc +++ b/testdata/drc/drcSimpleTests_25.drc @@ -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) diff --git a/testdata/drc/drcSimpleTests_26.drc b/testdata/drc/drcSimpleTests_26.drc index e9ddf0e79..acb7e3cda 100644 --- a/testdata/drc/drcSimpleTests_26.drc +++ b/testdata/drc/drcSimpleTests_26.drc @@ -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)