How the rounded_corner function works?

Hi sir,
I have some question about rounded_corners function .
here is my code below....

customer_psv_layer=input(86,0)
outter_corner=10
inner_corner=2
shrink_value=10
customer_psv_layer.sized(-shrink_value.um).rounded_corners(inner_corner.um,outter_corner.um,100).output(50,5)

and here is what the pattern we have , you can check with the attatched also.

after this script running , that is result and it is what the question do I meantion.

As you can see , most outter corner can usnig the round corner value to do trim/cut corner and base on the value do I provided.
I knew that , some of shape didn't have enought size to make the round corner base on that value .
Look like them still been make round corner , but the key is....what the value of round corner is?
Can I control that or can I measure that ?
please help it , Thanks.

1.7z 2.6K

Comments

  • Hi jiunnweiyeh

    There's a limitation for corner rounding radius, like you mensioned, if the size is smaller then the rounded value, the result will not be matched.

    And round_corner cannot recognize chamfered corners, the two coner of this chamfered edge wil be rounded separately so the 45deg slant edge (side-1 in following image) needs to be long enough for rounding.

    The maximum rounding that can be applied to a corner is as below, the slant part side-1 is 9.11 um, d is 4.555um, the max radius that can be applied Max r is 10.996 um, which is larger than 10um

    so if you look closely, you'll see the countour is not a single arc from 10um circle, instead it's form by connecting two arcs.

    In cases that Max_r is smaller than 10um, Max_r will be applied instead of 10um

  • A de-chamfer process can be applied to avoid the rounding limitation issue from chamfered shapes

    de-chamfered result, white dash-lines removes chamfering while preserves step like features.

    by filter out vertical, horiozontal and slant edges, we can use their relation to narrow down to chamfer corners.

    For slant edge that interacted with both H and V edges and with length < cetran length, we preserve them for further process.

    Extracting the bound box of chamfer edges automatically generates the patch shapes for the corner
    combined with input layer and merge generates the final result.

    sizing and rounding can be applied and shoud gives you what you need.

    chamferLen = 35
    psv_layer  = input(86,0)
    edges      = layer.edges
    hEdges     = edges.with_angle(0)
    vEdges     = edges.with_angle(90)
    sEdges     = edges.with_angle(diagonal_only)
    chamfer    = sEdges.interacting(hEdges).interacting(vEdges).with_length(0..chamferLen.um)
    patch      = chamfer.collect_to_region{|e| e.bbox}
    patched    = (psv_layer + patch).merged
    
    chamfer.output(50,4)
    patch.output(50,5)
    patched.output(50,6)
    
  • Hi @RawrRanger
    Thanks a lot for your help , I will check it and make it into what my code.
    Thanks.

  • edited December 2023

    If you're in particular interested in the inner corners and the objects you treat are larger than 40µm in dimension, you can also consider using the Minkowski sum with a circle. The Minkowski sum acts like a "pen" that is drawn around the perimeter of the polygon (like illustrated here: https://demonstrations.wolfram.com/TheMinkowskiSumOfADiskAndAPolygon/).

    The Minkowski sum is not readily available in DRC, but can be emulated.

    The code is that:

    customer_psv_layer = input(86, 0)
    
    outer_corner = 10.um
    shrink_value = 10.um
    num_points = 64
    
    # NOTE: we shrink by the outer_corner radius in addition to compensate for the later 
    # enlargement by the Minkowski sum
    sz = customer_psv_layer.sized(-(shrink_value+outer_corner))
    
    # creates a circle with radius outer_corner centered at 0,0 in database units:
    um2dbu = RBA::CplxTrans::new(dbu).inverted
    circle_um = RBA::DPolygon::ellipse(RBA::DBox::new(-outer_corner, -outer_corner, outer_corner, outer_corner), num_points)
    circle_dbu = um2dbu * circle_um
    
    # apply the Minkowski sum
    # NOTE: we use the data object to access the internal Region
    sz.data = sz.data.minkowski_sum(circle_dbu)
    
    sz.output(50, 5)
    

    Here is the output:

    The yellow dashed polygon in the pre-Minkowski layer.

    Matthias

  • Hi @RawrRanger @Matthias
    Do you have idea for how to check what the min/max value round_corner done in GDS?
    as previsouly mention.if the edge is not long enough for rounding.
    After round_corner function , the real value of rounding (as your picture , 10.996um) will not match with what the code we command.
    what the code I can "GUESS" would be that...
    Using "xor" function to boolean the pattern of "before_round" and "after_round"
    But look like the bbox value is not what the answer I need.
    Do you have any idea for that to check outter /inner rounding value?

    after_round.xor(before_round).raw.extents.each do |xx|
    if xx.bbox.height.round(2) >= xx.bbox.width.round(2) then
    pm1_trim_list_inner << xx.bbox.height.round(2)
    end
    

  • HI jiunnweiyeh,

    Basically what following code does is to loop through all shapes and edges to check their angle/length relation

    the length / angle relation check basically does what my previous post shows.

    layer = input(86,0)
    
    def maxRoundR (layer)
        rmax = Float::INFINITY
        layer.each{ |poly|
            estart = nil
            e1     = nil
            poly.each_edge{ |e2| 
                if (e1.nil?) then
                    estart = e2 #register first edge and leave this for last edge check
                else
                    rmax = [edgeMaxRoundR(e1, e2), rmax].min  #check e1, e2 edge angle and length relation
                end
                e1 = e2 #store current edge as old edge
            }
            rmax = [edgeMaxRoundR(e1, estart), rmax].min #check last edge, first edge angle and length relation
        }
        return rmax
    end
    
    def edgeMaxRoundR (e1, e2)
        d     = [e1.length, e2.length].min / 2
        theta = edgeAngle(e1.d, e2.d).abs  / 2
        r     = d * Math::tan(theta)
        return r
    end 
    
    def edgeAngle(v1, v2)
        vp = v1.vprod(-v2)
        sp = v1.sprod(-v2)
        return Math::atan2(vp, sp)
    end
    
    puts maxRoundR (layer)
    

    the runnung result of your example layout is 0.0853um reason due to there's a small segment in this shape
    to avoid this, a threshold should be applied to reject those results that is too small.

  • edited January 4

    Hi RawrRanger ,
    sorry for that due to my depiction not accurate to make some gap.
    please check with the attached file.
    we have 2 layers , 151/11 layer is before round , 151/1 is after round.
    I want to check what is the min corner rounding value by inner and outter side.
    it is why my previsouly metnion I guess that I need using not or xor function.

    before_round=input(151,11)
    after_round=input(151,1)
    after_round.xor(before_round).raw.extents.each do |xx|
    if xx.bbox.height.round(2) >= xx.bbox.width.round(2) then
    pm1_trim_list_inner << xx.bbox.height.round(2)
    end
    

    But I knew bbox should be not the correct answer , and base on this code , I can't separate the inner/outter value .

    This is base on when I am a checker not designer .
    Designer will base on this code to make trim/shrink/rounding (that is not final version , I am working for combine your code into mind)

        customer_psv_layer=input(86,0)
        outter_corner=10
        inner_corner=2
        shrink_value=10
        customer_psv_layer.sized(-shrink_value.um).output(151,11)
        customer_psv_layer.sized(-shrink_value.um).rounded_corners(inner_corner.um,outter_corner.um,100).output(151,1)
    

    But for Checker , whom have the result GDS 86,0 / 151,1 / 151,11 layers only.
    our team need to measure the rouding corner by manual .
    This is why/what I ask....

    could you please help ?
    Thanks.

  • edited January 4

    Hi jiunnweiyeh,

    The process can be split into four steps.
    (1) Pre-Process oroginal laye to remove chamfers
    (2) Feed processed layers to check max inner/outer radius
    (3) Apply rounding to layer
    (4) compare pre-processed layer to rounded layer and extract rounded part for measurement

    The processed gds is as attaehed.
    Original layer : L(151/11)
    Chamfer patched layer : L(151/12)
    Rounded patched layer : L(151/13)
    Rounded inner corner layer : L(151/14)
    Rounded outer corner layer : L(151/15)

    (1) pre processing:
    basically the exact copy of my previous post, works the same way.

    def patchChamfer(layer, threshold = 35)
        chamferLen = threshold
        edges      = layer.edges
        hEdges     = edges.with_angle(0)
        vEdges     = edges.with_angle(90)
        sEdges     = edges.with_angle(diagonal_only)
        chamfer    = sEdges.interacting(hEdges).interacting(vEdges).with_length(0..chamferLen.um)
        patch      = chamfer.collect_to_region{|e| e.bbox}
        patched    = (layer + patch).merged
        return patched
    end
    
    originalLayer = input(151,11)
    patchedLayer  = patchChamfer(layer, 35)
    patchedLayer.output(151,12)
    

    (2) For extracting max radius and (3) apply rounding:
    similar code, except now we determine the corner type (inner/outer) and find max separately


    def maxRoundR (layer) rmax_inner = Float::INFINITY rmax_outer = Float::INFINITY layer.each{ |poly| estart = nil e1 = nil poly.each_edge{ |e2| if (e1.nil?) then estart = e2 else # if emr is negative, the corner is a inner conrner emr = edgeMaxRoundR(e1, e2) if emr > 0 then rmax_outer = [emr, rmax_outer].min else # add abs to remove inner/outer recognition sign rmax_inner = [emr.abs, rmax_inner].min end end e1 = e2 } emr = edgeMaxRoundR(e1, estart) if emr > 0 then rmax_outer = [emr, rmax_outer].min else rmax_inner = [emr.abs, rmax_inner].min end } return rmax_inner, rmax_outer end def edgeMaxRoundR (e1, e2) # if returned value is negative, the corner is a inner conrner d = [e1.length, e2.length].min / 2 theta = edgeAngle(e1.d, e2.d) / 2 r = d * Math::tan(theta) return r end def edgeAngle(v1, v2) # if returned value is negative, the corner is a inner conrner vp = v1.vprod(-v2) sp = v1.sprod(-v2) return Math::atan2(vp, sp) end patchedLayer = input(151, 12) rmax_inner, rmax_outer = maxRoundR (patchedLayer) roundedLayer = patchedLayer.rounded_corners(rmax_inner.um, rmax_outer.um, 100) roundedLayer.output(151, 13) puts "rmax_inner = #{rmax_inner}\nrmax_outer = #{rmax_outer}"

    (4) For extracting rounded region and apply measurement:


    def autoMeasuer(originalLayer, roundedLayer)
        view            = RBA::Application.instance.main_window.current_view
    
        # check edges from rounded layer, the non-interacting parts are rounded parts
        roundEdges      = roundedLayer.edges.select_not_interacting(originalLayer.edges)
        # further split those edges with inside check to determine that is a inner or outer corner
        outerRoundEdges = roundEdges.inside(originalLayer)
        # extend edges to make those segments becomes a single polygon for the easy of grouping and processing
        outerRoundGroup = outerRoundEdges.extended_in(1).merged
    
        innerRoundEdges = roundEdges.not_inside(originalLayer)
        innerRoundGroup = innerRoundEdges.extended_out(1).merged
    
    
        [innerRoundGroup, outerRoundGroup].each{|g|
            g.collect{ |p|
                sides = []
                edges = []
                p.each_edge{|e| 
                    if (e.length < 0.002) then
    
                        sides << e
                    else
                        edges << e
                    end
                }
                # check each polygon in each group, and separate them into two categories
                # edges from two sides of 1dbu extension
                # any given point from the arc center part
                e1, e2      = sides[0], sides[1]
                ec          = edges[((edges.length - 1) / 3).round()]
    
                # form a three point arc measurement using annotation 
                ant         = RBA::Annotation::new()    
                ant.style   = RBA::Annotation::StyleLine
                ant.outline = RBA::Annotation::OutlineRadius
                ant.points  = [e1.p1, ec.p1, e2.p1]
                view.insert_annotation(ant)
    
            }
        }
    
        return innerRoundEdges, outerRoundEdges
    end
    
    patchedLayer    = input(151,12)
    roundedLayer    = input(151,13)
    extractedEdges  = autoMeasuer(patchedLayer, roundedLayer)
    innerRoundEdges = extractedEdges[0]
    outerRoundEdges = extractedEdges[1]
    
    innerRoundEdges.output(151,14)
    outerRoundEdges.output(151,15)
    
  • The arc finding part is like this, first get edges, use extend in/out to form a polygon and merge it

    check small length edges, which should be the target end piece pushed out by extended_in

    it should have two of them, e1 and e2, finallyget any given point from other edges as ec

    feed them for annotation measurement

  • edited January 4

    @RawrRanger
    After check your code 3 (autoMeasuer(originalLayer, roundedLayer)) that is workable.
    Thanks.
    Cause code1 /2 should be merged into the code of designer.
    for Checker , they should using what the layers in GDS file and check/measure it.
    Your code is workable for measure , but can we get the min value for inner / outter corner round ?
    Now , I get the measure result in GDS as below figure.
    but human (checker) need to check the value by each measure, how can I get the min. vlaue ?

    by the way , what the programming command I make corner round is outter by 10um and inner 2um.
    as this figure shown , some of measure result is 9.9935...um , I knew that will be gap in measure due to grid .
    is that reason?

  • edited January 4

    Hi jiunnweiyeh,

    Glad this works, unfortunatelly the annotation (Ruler) cannot provid radius data, so we'll need to implement a fit function.

    example as below, this was modified from this source
    I can't read math, but the code seems works fine.

    def circle_interpolation(p1, p2, p3)
        r    = Float::INFINITY
        temp = p2.x * p2.x + p2.y * p2.y
        bc   = (p1.x * p1.x + p1.y * p1.y - temp) / 2
        cd   = (temp - p3.x * p3.x - p3.y * p3.y) / 2
        det  = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y)
    
        if (det.abs > 1.0e-6) then
            cx = (bc *(p2.y - p3.y) - cd * (p1.y - p2.y)) / det
            cy = ((p1.x - p2.x) * cd - (p2.x - p3.x) * bc) / det
            r  = ((cx - p1.x) ** 2 + (cy - p1.y) ** 2) ** (0.5)
        end
        return r
    end
    

    by adding this line

        puts circle_interpolation (e1.p1, ec.p1, e2.p1)
    

    after this should generate all radius results.

        ant.points  = [e1.p1, ec.p1, e2.p1]
    

    as to the question of 10.00 um to 9.9935...um mis-match, the source of differences can be :
    (1) Fit circle error
    (2) Off grid of the layout
    (3) The macig from edge extension

    The (1) (2) is quite intuitive, the (3) probably makes most of the contribtion to this difference.

    because we extend the edges to polygon for easy of operation, and loop through edges and filter our e1,e2
    by their length, so they might not always appears in sequence, means either one of these edges could be e1 depends on the shape of the object

    this could leads to situations like this, where e1.p1 is registered, but is not the point that is exactly on it's original edge

    and because I kind of just choos a randon point from the polygon, by deviding the edge list in to 3 so it might also happens to be points that is on the other side, like the example shows.

  • edited January 5

    Hi jiunnweiyeh,

    To minimize the error mensioned above, which was caused by measurement point not exactly on the edge,
    I've implement a pick3Points function, which first uses the cut_point of the two side to determine the curved side.
    Due to the nature of how these two small sides were generated, the cut point will always located at the curved-in side of the arc.

    By comparing the distance betweene1.p1/e1.p2 and cut_point cp we can properly choose the correct point that is on the actual Arc, the one that is on the far side.

    Similar approach applies to e2 and ec, the mid point of Arc.

    With the help of cut_point cp we can cast a edge from cp and throuth the Arc polygon at a roughly center location.

    This should always generates two edgs ecs that intersects the cast edge, by choosing the one that is far away from the ec, we can determine our last point on the Arc polygon.

    So as image shown, the three far points can be choosen and feed to circle fitting

    A slighe improvement, around 1~3 dbu of accuracy is observed using previous test cases try.gds.

    def pick3Points(sides, edges)
        e1, e2   = sides[0], sides[1]
        cp       = e1.cut_point(e2)
        p1       = (cp.distance(e1.p1) > cp.distance(e1.p2)) ? e1.p1 : e1.p2
        p2       = (cp.distance(e2.p1) > cp.distance(e2.p2)) ? e2.p1 : e2.p2
        echeck   = RBA::DEdge::new(cp, cp + (p1-cp) + (p2-cp))
        ecs      = edges.select{|e| e.intersects?(echeck)}
        ec1, ec2 = ecs[0], ecs[1]
        pc       = (cp.distance(ec1.p1) > cp.distance(ec2.p1)) ? ec1.p1 : ec2.p1
        return p1, pc, p2
    end
    
    def addCircleAnt(view, p1, pc, p2)
        ant         = RBA::Annotation::new()    
        ant.style   = RBA::Annotation::StyleLine
        ant.outline = RBA::Annotation::OutlineRadius
        ant.points  = [p1, pc, p2]
        view.insert_annotation(ant)
    end
    
    def circle_interpolation(p1, p2, p3)
        r    = Float::INFINITY
        temp = p2.x * p2.x + p2.y * p2.y
        bc   = (p1.x * p1.x + p1.y * p1.y - temp) / 2
        cd   = (temp - p3.x * p3.x - p3.y * p3.y) / 2
        det  = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y)
    
        if (det.abs > 1.0e-6) then
            cx = (bc *(p2.y - p3.y) - cd * (p1.y - p2.y)) / det
            cy = ((p1.x - p2.x) * cd - (p2.x - p3.x) * bc) / det
            r  = ((cx - p1.x) ** 2 + (cy - p1.y) ** 2) ** (0.5)
        end
        return r
    end
    
    def autoMeasuer(originalLayer, roundedLayer)
        view            = RBA::Application.instance.main_window.current_view
        dbu             = view.active_cellview.layout.dbu
        roundEdges      = roundedLayer.edges.select_not_interacting(originalLayer.edges)
        outerRoundEdges = roundEdges.inside(originalLayer)
        innerRoundEdges = roundEdges.not_inside(originalLayer)
        outerRoundGroup = outerRoundEdges.extended_in(1).merged
        innerRoundGroup = innerRoundEdges.extended_out(1).merged
    
        [innerRoundGroup, outerRoundGroup].each{|g|
            g.collect{ |p|
                sides, edges = [], []
                p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e }
                p1, pc, p2  = pick3Points(sides, edges)
                addCircleAnt(view, p1, pc, p2)
                puts circle_interpolation(p1, pc, p2)
            }
        }
        return innerRoundEdges, outerRoundEdges
    end
    
    patchedLayer    = input(151,12)
    roundedLayer    = input(151,13)
    extractedEdges  = autoMeasuer(patchedLayer, roundedLayer)
    innerRoundEdges = extractedEdges[0]
    outerRoundEdges = extractedEdges[1]
    
    innerRoundEdges.output(151,14)
    outerRoundEdges.output(151,15)
    
  • Hi @RawrRanger
    Thanks very much for your hard work and coding.
    it is helpful.
    But look like the puts result mix both of inner / outter value.
    Do you have any idea for that?

  • edited January 7

    Wow ... @RawrRanger I need to digest this first.

    But a brief comment: There is the "Radius Ruler" type. It can measure a radius when you provide three points (clicks):

    The measurements are precise only to the actual values which are subject to DBU rounding (1nm in this case).

    Matthias

  • edited January 8

    Hi @Matthias
    Let me talk you a long story~
    totolay , we have 2 function , 1 for designer , 1 for checker.

    The first code is for designer
    I am working for bumping house , our customer provide what the passivation layer (as picture shown , it is polygon) to us.
    we need to do some process like this in our design team ...

    def patchChamfer(layer, threshold )
        chamferLen = threshold
        edges      = layer.edges
        hEdges     = edges.with_angle(0)
        vEdges     = edges.with_angle(90)
        sEdges     = edges.with_angle(diagonal_only)
        chamfer    = sEdges.interacting(hEdges).interacting(vEdges).with_length(0..chamferLen.um)
        patch      = chamfer.collect_to_region{|e| e.bbox}
        patched    = (layer + patch).merged
        return patched
    end
    
    originalLayer = input(86,0)
    outter_corner=10
    inner_corner=2
    shrink_value=10
    first_step_layer=originalLayer.sized(-shrink_value.um)
    patchedLayer  = patchChamfer(first_step_layer, 10)
    patchedLayer.output(151,11)
    patchedLayer.rounded_corners(inner_corner.um,outter_corner.um,100).output(151,12)
    

    After that , finally , when the designer finished a lot modify for the layer 151,12
    our check team need to base on the GDSII file Designer provided to check if any DRC or anything wrong.
    when that moment , checker only have the GDSII file include what the original layer customer provided (86,0) as my code.
    and 151,11 layer (after shrink) and 151,12 (after round corner).(designer make it)

    As the previosuly mention , sometimes when the polygon shape didn't have enought length to make chamfer. (10um for outter in this case)
    The round_corner function will base on what the real length of shape to make it corner routing .
    That is what /reason our checker need to confirm---the real rounding value is not 10um (outter) / 2um (inner) , we have to check what is the min one.
    And it is what the code RawrRanger provied .

    As what I said , RawrRanger's function is workable , but checker need to know what /where the min. value for inner /outter rounding.
    It is hard to check the auto-measure result by human .
    I have try to make a array to store circle_interpolation(p1, pc, p2) (follow RawrRanger's code)
    Unfortunate , I can't seprater the value by inner / outter....
    Can you help it?

  • Hi jiunnweiyeh,

    The round data are stored in outerRoundGroup and innerRoundGroup respectively, because my previous example group then into a list, so the output results will be mixed together.

    Extract this part into a separate function measureGroup which takes aview object (for insert annotation) and group data.

        measureGroup(view, innerRoundGroup).each {|r| puts "inner : #{r}"}
        measureGroup(view, outerRoundGroup).each {|r| puts "outer : #{r}"}
    

    this provides measurement results separately for inner/outer rounding.

    the rest of the parts stays the same.


    def measureGroup(view, roundGroup) r = [] roundGroup.collect{ |p| sides, edges = [], [] p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e } p1, pc, p2 = pick3Points(sides, edges) addCircleAnt(view, p1, pc, p2) r << circle_interpolation(p1, pc, p2) } return r end def autoMeasuer(originalLayer, roundedLayer) view = RBA::Application.instance.main_window.current_view dbu = view.active_cellview.layout.dbu roundEdges = roundedLayer.edges.select_not_interacting(originalLayer.edges) outerRoundEdges = roundEdges.inside(originalLayer) innerRoundEdges = roundEdges.not_inside(originalLayer) outerRoundGroup = outerRoundEdges.extended_in(1).merged innerRoundGroup = innerRoundEdges.extended_out(1).merged measureGroup(view, innerRoundGroup).each {|r| puts "inner : #{r}"} measureGroup(view, outerRoundGroup).each {|r| puts "outer : #{r}"} end
  • Hi @RawrRanger

    Here , I combine both of your code to let our checker to easy check the round value and location...
    some of case , this code is workable , but some of case I got error message as below

    Do you have any idea for how the fixt it?

    def pick3Points(sides, edges)
        e1, e2   = sides[0], sides[1]
        cp       = e1.cut_point(e2)
        p1       = (cp.distance(e1.p1) > cp.distance(e1.p2)) ? e1.p1 : e1.p2
        p2       = (cp.distance(e2.p1) > cp.distance(e2.p2)) ? e2.p1 : e2.p2
        echeck   = RBA::DEdge::new(cp, cp + (p1-cp) + (p2-cp))
        ecs      = edges.select{|e| e.intersects?(echeck)}
        ec1, ec2 = ecs[0], ecs[1]
        pc       = (cp.distance(ec1.p1) > cp.distance(ec2.p1)) ? ec1.p1 : ec2.p1
        return p1, pc, p2
    end
    
    def addCircleAnt(view, p1, pc, p2)
        ant         = RBA::Annotation::new()    
        ant.style   = RBA::Annotation::StyleLine
        ant.outline = RBA::Annotation::OutlineRadius
        ant.points  = [p1, pc, p2]
        view.insert_annotation(ant)
    end
    
    def circle_interpolation(p1, p2, p3)
        r    = Float::INFINITY
        temp = p2.x * p2.x + p2.y * p2.y
        bc   = (p1.x * p1.x + p1.y * p1.y - temp) / 2
        cd   = (temp - p3.x * p3.x - p3.y * p3.y) / 2
        det  = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y)
    
        if (det.abs > 1.0e-6) then
            cx = (bc *(p2.y - p3.y) - cd * (p1.y - p2.y)) / det
            cy = ((p1.x - p2.x) * cd - (p2.x - p3.x) * bc) / det
            r  = ((cx - p1.x) ** 2 + (cy - p1.y) ** 2) ** (0.5)
        end
        return r
    end
    
    
    def measureGroup(view, roundGroup)
        r = []
        roundGroup.collect{ |p|
            sides, edges  = [], []
            p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e }
            p1, pc, p2  = pick3Points(sides, edges)
            r << circle_interpolation(p1, pc, p2)
          #  addCircleAnt(view, p1, pc, p2)
        }
        return r
    end
    
    def autoMeasuer(originalLayer, roundedLayer)
        view            = RBA::Application.instance.main_window.current_view
        dbu             = view.active_cellview.layout.dbu
        roundEdges      = roundedLayer.edges.select_not_interacting(originalLayer.edges)
        outerRoundEdges = roundEdges.inside(originalLayer)
        innerRoundEdges = roundEdges.not_inside(originalLayer)
        outerRoundGroup = outerRoundEdges.extended_in(1).merged
        innerRoundGroup = innerRoundEdges.extended_out(1).merged
        inner_value =Array.new()
        outter_value =Array.new()
        measureGroup(view, innerRoundGroup).each {|r| inner_value << r}
        measureGroup(view, outerRoundGroup).each {|r| outter_value << r}
        puts "the min corner value of ineer is #{inner_value.min.round(2)}"
        puts "the min corner value of outter is #{outter_value.min.round(2)}"
        RBA::MessageBox.info("check Value","the min corner value of ineer is #{inner_value.min.round(2)}  \
        the min corner value of outter is #{outter_value.min.round(2)}", RBA::MessageBox.b_ok)
        return inner_value.min.round(2) ,outter_value.min.round(2)
    end
    
    
    def autoMeasuer2(originalLayer, roundedLayer,min_inner,min_outter)
        view            = RBA::Application.instance.main_window.current_view
        dbu             = view.active_cellview.layout.dbu
        roundEdges      = roundedLayer.edges.select_not_interacting(originalLayer.edges)
        outerRoundEdges = roundEdges.inside(originalLayer)
        innerRoundEdges = roundEdges.not_inside(originalLayer)
        outerRoundGroup = outerRoundEdges.extended_in(1).merged
        innerRoundGroup = innerRoundEdges.extended_out(1).merged
    
        [innerRoundGroup, outerRoundGroup].each{|g|
            g.collect{ |p|
                sides, edges = [], []
                p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e }
                p1, pc, p2  = pick3Points(sides, edges)
                r=(circle_interpolation(p1, pc, p2)).round(2)
                if min_inner==2 && min_outter==10 then
                if (r <min_outter) then
                addCircleAnt(view, p1, pc, p2)
                end
                end
    
                if (min_inner!=2) && (r ==min_inner || r==min_outter) then
                addCircleAnt(view, p1, pc, p2)
                end
            }
        }
        return innerRoundEdges, outerRoundEdges
    end
    
    patchedLayer    = input(151,101)
    roundedLayer    = input(151,1)
    real_round_pattern=roundedLayer.interacting(patchedLayer)
    min_inner,min_outter=autoMeasuer(patchedLayer, real_round_pattern)
    answer_list=Array.new()
    answer_list << "2(inner)/10(outter)_check"
    answer_list << "min.value"
    infcase=RBA::InputDialog::ask_item("request", "choose one way to find answer:",answer_list,1)
    if infcase=="min.value" then
    extractedEdges  = autoMeasuer2(patchedLayer, real_round_pattern,min_inner,min_outter)
    end
    
    if infcase=="2(inner)/10(outter)_check" then
    extractedEdges  = autoMeasuer2(patchedLayer, real_round_pattern,2,10)
    end
    
    innerRoundEdges = extractedEdges[0]
    outerRoundEdges = extractedEdges[1]
    
    innerRoundEdges.output(151,14)
    outerRoundEdges.output(151,15)
    
  • edited January 10

    Hi jiunnweiyeh,

    The error message :
    undefined method of 'distance' for nil:NilClass
    Means you are trying to access the distance attribute of a empty object.

    This error is caused by function def pick3Points(sides, edges)
    the code that causes this error are lines that contains cp.distance(XXX), which suggests the point cp
    is not being generated properly by e1.cut_point(e2), the only possible reason for this is
    the two edge e1 and e2 just happens to be in parellel, and no crossing point is found
    for an ideal Arc shaped object this should not occur.

    This revised version will try to diliver less accurate results if the arc is not a ideal shape,
    if the value is off by too much then the rest of the rounded corners, you can apply filters to hightlight the area,

    def pick3Points(sides, edges)
        e1, e2   = sides[0], sides[1]
        cp       = e1.cut_point(e2)
        if cp then
            p1       = (cp.distance(e1.p1) > cp.distance(e1.p2)) ? e1.p1 : e1.p2
            p2       = (cp.distance(e2.p1) > cp.distance(e2.p2)) ? e2.p1 : e2.p2
            echeck   = RBA::DEdge::new(cp, cp + (p1-cp) + (p2-cp))
            ecs      = edges.select{|e| e.intersects?(echeck)}
            ec1, ec2 = ecs[0], ecs[1]
            pc       = (cp.distance(ec1.p1) > cp.distance(ec2.p1)) ? ec1.p1 : ec2.p1
            return p1, pc, p2
        end
        return e1.p1, edges[((edges.length - 1) / 4).round()].p1, e2.p1
    end
    
  • Hi @RawrRanger
    I have update the code as you provied , and I keep these function cause each function can help us to find the trim value , size , location.

    circle_interpolation(p1, p2, p3)
    measureGroup(view, roundGroup) 
    pick3Points(sides, edges)
    

    The detail (full coding) please check with the second attached file.
    And runing that in the attached 1 , you will get the error message , that been shown as below.
    please help it.
    Thanks.

  • Hi jiunnweiyeh,

    Thanks for the test case, and regarding to the error of undefined method 'x'
    this is due to I for got to access the edge with p1 attribute, the intention here is to return a point from edge instead of returning an edge.

    the code above has been updated with a correct return

    return e1.p1, edges[((edges.length - 1) / 4).round()].p1, e2.p1
    #                                                    ^^^
    #                                        this part was missing
    

    As to how undefined method of 'distance' for nil:NilClass this issue is triggered, I found the error is related to this shape, the vertex generated by rounding (red) snapped to a grid in a way that the whole edge shift slightly awayfrom original edge (white), which makes this straight line being falsely recognized as an rounded corner, due to I use edges.select_not_interacting to separate rounded Arcs.

  • Hi @RawrRanger
    Got the udpate and I will try it ASAP.
    Thanks very much for your help.

  • edited January 10

    Hi jiunnweiyeh,

    few update about the code, I see you use autoMeasure/autoMeasure2 to process shapes and analyze results separately.

    So i've revised this part so it retuens a list of dictionary which contains radius info and collection of points for adding annotation, now this main function is renamed to measuerResults .

    def addCircleAnt(p1, pc, p2)
        view        = RBA::Application.instance.main_window.current_view
        ant         = RBA::Annotation::new()    
        ant.style   = RBA::Annotation::StyleLine
        ant.outline = RBA::Annotation::OutlineRadius
        ant.points  = [p1, pc, p2]
        view.insert_annotation(ant)
    end
    
    def pick3Points(sides, edges)
        e1, e2   = sides[0], sides[1]
        cp       = e1.cut_point(e2)
        if cp then
            p1       = (cp.distance(e1.p1) > cp.distance(e1.p2)) ? e1.p1 : e1.p2
            p2       = (cp.distance(e2.p1) > cp.distance(e2.p2)) ? e2.p1 : e2.p2
            echeck   = RBA::DEdge::new(cp, cp + (p1-cp) + (p2-cp))
            ecs      = edges.select{|e| e.intersects?(echeck)}
            ec1, ec2 = ecs[0], ecs[1]
            pc       = (cp.distance(ec1.p1) > cp.distance(ec2.p1)) ? ec1.p1 : ec2.p1
            return p1, pc, p2
        end
        # condition for non-ideal arc
        return e1.p1, edges[((edges.length - 1) / 4).round()].p1, e2.p1
    end
    
    def circle_interpolation(p1, p2, p3)
        r    = Float::INFINITY
        temp = p2.x * p2.x + p2.y * p2.y
        bc   = (p1.x * p1.x + p1.y * p1.y - temp) / 2
        cd   = (temp - p3.x * p3.x - p3.y * p3.y) / 2
        det  = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y)
    
        if (det.abs > 1.0e-6) then
            cx = (bc *(p2.y - p3.y) - cd * (p1.y - p2.y)) / det
            cy = ((p1.x - p2.x) * cd - (p2.x - p3.x) * bc) / det
            r  = ((cx - p1.x) ** 2 + (cy - p1.y) ** 2) ** (0.5)
        end
        return r
    end
    
    def measureGroup(roundGroup)
        results = []
        roundGroup.collect{ |p|
            # Arc polygon with only 4 points are false signal casued by grid snapping
            if p.num_points > 4 then
                sides, edges  = [], []
                p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e }
                p1, pc, p2  = pick3Points(sides, edges)
                results << {
                    "radius" => circle_interpolation(p1, pc, p2), 
                    "points" => [p1, pc, p2]
                }
            end
        }
        # pack radius  and points info for further analyze
        return results
    end
    
    def measuerResults(originalLayer, roundedLayer)
        roundEdges      = roundedLayer.edges.select_not_interacting(originalLayer.edges)
        outerRoundEdges = roundEdges.inside(originalLayer)
        innerRoundEdges = roundEdges.not_inside(originalLayer)
        outerRoundGroup = outerRoundEdges.extended_in(1).merged
        innerRoundGroup = innerRoundEdges.extended_out(1).merged
        inVal           = measureGroup(innerRoundGroup)
        outVal          = measureGroup(outerRoundGroup)
        return inVal, outVal, innerRoundEdges, outerRoundEdges
    end
    
    def showMsgbox(inVal, outVal)
        # empty check, incase there's no inner rounded corners or no outer rounded corner
        inValMinR     = (inVal.empty?)  ? 0 : inVal.collect{ |v| v["radius"]}.min
        outValMinR    = (outVal.empty?) ? 0 : outVal.collect{|v| v["radius"]}.min
    
        inMinRStr     = "the min corner value of inner is #{inValMinR.round(2)}"
        outMinRStr    = "the min corner value of outer is #{outValMinR.round(2)}"
        resultMSG     = "#{inMinRStr}\n#{outMinRStr}"
        puts resultMSG
        RBA::MessageBox.info("check Value", resultMSG, RBA::MessageBox.b_ok)
    end
    
    def showDialog(inVal, outVal)
        # collect all "R" and get min Radius
        # empty check, incase there's no inner rounded corners or no outer rounded corner
        inValMinR  = (inVal.empty?)  ? 0 : inVal.collect{ |v| v["radius"]}.min
        outValMinR = (outVal.empty?) ? 0 : outVal.collect{|v| v["radius"]}.min
    
        ansList    = ["2(inner)/10(outter)_check", "min.value"]
        infcase    = RBA::InputDialog::ask_item("request", "choose one way to find answer:", ansList, 1)
    
        if infcase == "min.value" then
            min_inner  = inValMinR.round(2)
            min_outter = outValMinR.round(2)
            # Use "R" to filter out sites that needs a Annotation, 
            # hight light by insert Ant using existed data unpacked from "Points"
            inVal.each{ |v| (v["radius"].round(2) == min_inner)  ? addCircleAnt( *v["points"]) : nil }
            outVal.each{|v| (v["radius"].round(2) == min_outter) ? addCircleAnt( *v["points"]) : nil }
        end
    
        if infcase == "2(inner)/10(outter)_check" then
            min_inner  =  2
            min_outter = 10
            inVal.each{ |v| (v["radius"].round(2) < min_outter) ? addCircleAnt( *v["points"]) : nil }
            outVal.each{|v| (v["radius"].round(2) < min_outter) ? addCircleAnt( *v["points"]) : nil }
        end
    end
    
    
    patchedLayer  = input(151,101)
    roundedLayer  = input(151,1)
    inVal, outVal, inEdges, outEdges = measuerResults(patchedLayer, roundedLayer)
    
    # this provides a data in following format, 
    # with radius and points for Annotation insetion
    # inVal = [
    #     {"radius" => R1, "points" => [p1_1, pc_1, p2_1]},
    #     {"radius" => R2, "points" => [p1_2, pc_2, p2_2]},
    #       ...
    #     {"radius" => Rn, "points" => [p1_n, pc_n, p2_n]},
    # ]
    
    showMsgbox(inVal, outVal)
    showDialog(inVal, outVal)
    inEdges.output(151, 14)
    outEdges.output(151, 15)
    
  • edited January 10

    Hi @RawrRanger
    Thanks , look like that will make a exception caught named "cut_point" .

  • Hi jiunnweiyeh,

    This should be related to edge lengh check part, of measureGroup
    p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e }

    which did not get sufficient amount of edges, results in input value e1 in pick3Points becomse nil

    In normal case, this should gurantee at least 2 edge is generated, which was from extend function Edges.extended_in(1).merged

    I've checked the result using sample try.gds and new sample 9999.gds, but cannot successfully triggger this exception, a sample of layout that triggers this message will be very helpful.

  • Hi @RawrRanger
    I got the answer ,cause some of pattern didn't have "patchedLayer" , so your code is workable .
    I just need to add a simple code to filter it .

    real_round_pattern=(roundedLayer.interacting(patchedLayer)).not_interacting(outruleLayer)
    inVal, outVal, inEdges, outEdges = measuerResults(patchedLayer, real_round_pattern)
    

    Thanks very much for your kindly help.

  • edited January 11

    Hi @RawrRanger and @jiunnweiyeh,

    your collaboration is amazing ... I really appreciate this, specifically the nice visualizations of @RawrRanger!

    I just wanted to come back to @jiunnweiyeh 's comment at Jan 8:

    The round_corner function will base on what the real length of shape to make it corner routing .
    That is what /reason our checker need to confirm---the real rounding value is not 10um (outter) / 2um (inner) , we have to check what is the min one.

    The rules by which the round_corner functions works are not very elaborate and can be evaluated.

    Basically, two connecting edges make a circular corner. Three edges form two corners. Here is one with an inner (r1) and outer corner (r2):

    This is a well-formed case where the given radii can be implemented. The parts denoted by "s1" and "s2" and their counterparts on the attached edges are converted into circular sections.

    However, when s1 + s2 == length(E2), E2 is entirely consumed by the circular sections. If s1 + s2 > length(E2), the circular sections cannot be formed any longer without introducing polygon path inversion - this is something we like to avoid.

    To avoid this, the radii r1 and r2 are reduced in this case:

    f = length(E2) / (s1 + s2)
    if f < 1.0:
      r1 *= f
      r2 *= f
    

    In general, each radius will potentially need to be reduced twice: r1 needs to both fit into E2 (together with r2) and E1 (together with the predecessor leftmost r0 which is not shown in the picture).

    So the full scheme is that:

    # for r1, fitting r0 (not shown) and r2 into E1 and E2 respectively
    f1 = length(E1) / (s0 + s1)
    f2 = length(E2) / (s1 + s2)
    f = min(f1, f2)
    if f < 1.0:
      r1 *= f
    

    So with this knowledge we can basically compute the minimum radius.

    Here is some code that performs this evaluation for one selected polygon and two sample radii (inner: 0.5 and outer: 1.0 µm):

    rinner = 0.5
    router = 1.0
    
    # Needs to have one polygon selected
    # (disclaimer: no checking)
    
    s = RBA::LayoutView::current.each_object_selected.first.shape
    poly = s.dsimple_polygon
    
    
    # Radius evaluation for one Polygon:
    
    # Code translated from dbPolygonTools.cc
    
    points = poly.each_point.to_a
    rad = []
    inner_or_outer = []
    seg = []
    
    # collect the circular segment lengths s[i] in "seg"
    # and the proposed radii in "rad".
    # "inner_or_outer" collects a flag: 
    # true for inner and false for outer corner
    
    points.each_with_index do |p,i|
    
      # prev and next point
      pp = points[i - 1]
      pn = points[i + 1 - points.size]
    
      # unit vectors along the edge before p and after p
      eb = p - pp
      eb *= 1.0 / eb.length
      ea = pn - p
      ea *= 1.0 / ea.length
    
      # angle between edges
      sin_a = eb.vprod(ea)
      cos_a = eb.sprod(ea)
      a = Math.atan2(sin_a, cos_a).abs
    
      io = sin_a > 0.0
      r = io ? rinner : router
      s = r * (Math.sin(a * 0.5) / Math.cos(a * 0.5)).abs
    
      inner_or_outer[i] = io
      rad[i] = r
      seg[i] = s
    
    end
    
    min_rinner = nil
    max_rinner = nil
    min_router = nil
    max_router = nil
    
    points.each_with_index do |p,i|
    
      # prev and next point
      pp = points[i - 1]
      pn = points[i + 1 - points.size]
    
      # prev, this and next circular segment lengths  
      sp = seg [i - 1]
      s  = seg [i]
      sn = seg [i + 1 - seg.size]
    
      # reduction factors from edge before and after
      # (normally 1 in the good case)
      fb = [ 1.0, (p - pp).length / (sp + s) ].min
      fa = [ 1.0, (pn - p).length / (s + sn) ].min
    
      # constrained radius
      r = [ fb, fa ].min * rad [i]
    
      if inner_or_outer[i]
        max_rinner = [ max_rinner || r, r ].max
        min_rinner = [ min_rinner || r, r ].min
      else
        max_router = [ max_router || r, r ].max
        min_router = [ min_router || r, r ].min
      end
    
    end
    
    # print results
    
    puts "R(inner): %.7g .. %.7g" % [ min_rinner, max_rinner ]
    puts "R(outer): %.7g .. %.7g" % [ min_router, max_router ]
    

    The results for this polygon:

    Are these:

    R(inner): 0.06666667 .. 0.3313708
    R(outer): 0.1333333 .. 1
    

    The minimum values correspond well to the critical situation here, within the limits of measurement accuracy of the radii:

    Best regards,

    Matthias

    P.S: I am aware that the quality of the drawings do NOT match those of @RawrRanger

  • Hi @Matthias,
    Thanks for your code, let me digestion it.
    in my side , our design team have another idea for corner rounding by programming.
    --Maybe I can check if the shape single-edge size small than rule value (maybe 10.um)
    Let the programming process all of other shape for corner_rounding (if the single -edge length > 10um)
    The others , let designer to check and process by manufature.
    I will check if I can do that or I need your help.
    Thanks very much for all your help.

  • edited January 12

    Hi sir ,
    Do you have function to check the single-edge length < rule ?
    such as ..

    psv=input(86,0)
    psv.edges.with_length(0..10).output(86,1)
    chklayer=input(86,1)
    psv.interacting(chklayer).output(100,50)
    

    look like I have to change polygon layer to a edge layer , but I have no idea for how to make it....

  • update , I got answer , it should be ...
    psv=input(86,0)
    chklayer=psv.edges.with_length(0.um..5.um)
    psv.interacting(chklayer.extended(:out => 1.0)).output(100,50)
    Thanks.

  • Hi @RawrRanger
    Could you please help to check why I can't measure the cut corner value in this pattern?

    here the GDS file and what the meausre script as below.

    def pick3Points(sides, edges)
        e1, e2   = sides[0], sides[1]
        cp       = e1.cut_point(e2)
        if cp then
            p1       = (cp.distance(e1.p1) > cp.distance(e1.p2)) ? e1.p1 : e1.p2
            p2       = (cp.distance(e2.p1) > cp.distance(e2.p2)) ? e2.p1 : e2.p2
            echeck   = RBA::DEdge::new(cp, cp + (p1-cp) + (p2-cp))
            ecs      = edges.select{|e| e.intersects?(echeck)}
            ec1, ec2 = ecs[0], ecs[1]
            pc       = (cp.distance(ec1.p1) > cp.distance(ec2.p1)) ? ec1.p1 : ec2.p1
            return p1, pc, p2
        end
            return e1.p1, edges[((edges.length - 1) / 4).round(1)].p1, e2.p1
    end
    
    def addCircleAnt(view, p1, pc, p2)
        ant         = RBA::Annotation::new()    
        ant.style   = RBA::Annotation::StyleLine
        ant.outline = RBA::Annotation::OutlineRadius
        ant.points  = [p1, pc, p2]
        view.insert_annotation(ant)
    end
    
    def circle_interpolation(p1, p2, p3)
        r    = Float::INFINITY
        temp = p2.x * p2.x + p2.y * p2.y
        bc   = (p1.x * p1.x + p1.y * p1.y - temp) / 2
        cd   = (temp - p3.x * p3.x - p3.y * p3.y) / 2
        det  = (p1.x - p2.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p2.y)
    
        if (det.abs > 1.0e-6) then
            cx = (bc *(p2.y - p3.y) - cd * (p1.y - p2.y)) / det
            cy = ((p1.x - p2.x) * cd - (p2.x - p3.x) * bc) / det
            r  = ((cx - p1.x) ** 2 + (cy - p1.y) ** 2) ** (0.5)
        end
        return r
    end
    
    def measureGroup(view, roundGroup,minvalue,gap)
            layout = RBA::CellView::active.layout
            marklayer = layout.layer(500,0)
            dbu  = view.active_cellview.layout.dbu
            databaseunit= 1 / dbu
            current_work_cell= RBA::Application::instance.main_window.current_view.active_cellview.cell_name
            drawtop = layout.cell(current_work_cell)
            r = []
            roundGroup.collect{ |p|
            sides, edges  = [], []
            p.each_edge{|e| ((e.length < dbu * 2) ? sides : edges) << e }
            p1, pc, p2  = pick3Points(sides, edges)
            rc=circle_interpolation(p1, pc, p2)
            r << rc
            if rc.round(3) < (minvalue.to_f-gap.to_f ).round(3) then
            addCircleAnt(view, p1, pc, p2)
            p1x=(p1.x*databaseunit).round(0)
            p1y=(p1.y*databaseunit).round(0)
            p2x=(p2.x*databaseunit).round(0)        
            p2y=(p2.y*databaseunit).round(0)        
            drawtop.shapes(marklayer).insert(RBA::Box::new(p1x,p1y,p2x,p2y))
            end
        }
        return r
    end
    
    def autoMeasuer(originalLayer, roundedLayer , measure_type , min_value , tolerence)
    
        view            = RBA::Application.instance.main_window.current_view
        dbu             = view.active_cellview.layout.dbu
        roundEdges      = roundedLayer.edges.select_not_interacting(originalLayer.edges)
        outerRoundEdges = roundEdges.inside(originalLayer)
        innerRoundEdges = roundEdges.not_inside(originalLayer)
        outerRoundGroup = outerRoundEdges.extended_in(1).merged
        innerRoundGroup = innerRoundEdges.extended_out(1).merged
        inner_value =Array.new()
        outter_value =Array.new()
    
        if measure_type=="1" then
        measureGroup(view, innerRoundGroup,min_value,tolerence).each {|r| inner_value << r}
        if (inner_value.min).is_a? Numeric then
        return inner_value.min.round(2)
        else 
        return 0
        end
        end    
    
    
        if measure_type=="2" then
        measureGroup(view, outerRoundGroup,min_value,tolerence).each {|r| outter_value << r}
        if (outter_value.min).is_a? Numeric then
        return outter_value.min.round(2)
        else
        return 0
        end
        end
    end
    ######################################################################
    def userform ()
    layoutView = RBA::Application::instance.main_window.current_view.active_cellview.layout
    dialog = RBA::QDialog.new( RBA::Application.instance.main_window)
    dialog.windowTitle = "measure PM1 corner trim value"
    formlayout = RBA::QVBoxLayout::new(dialog)
    dialog.setLayout(formlayout)
    dialog.setFixedWidth(350)
    mw = RBA::Application::instance.main_window
    
    answer_list=Array.new()
    answer_list << "1.inner_check"
    answer_list << "2.outer_check"
    
    mw.layout
    view = mw.current_view
    label =  RBA::QLabel.new(dialog)
    formlayout.addWidget(label)
    label.text = "(Please enter your condition)"
    measure_type =  RBA::QComboBox.new(dialog)
    measure_type.addItems(answer_list)
    formlayout.addWidget(measure_type)
    label =  RBA::QLabel.new(dialog)
    formlayout.addWidget(label)
    
    label =  RBA::QLabel.new(dialog)
    formlayout.addWidget(label)
    label.text = "(min. check size)"
    min_Check_value=  RBA::QLineEdit.new(dialog)
    formlayout.addWidget(min_Check_value)
    
    label =  RBA::QLabel.new(dialog)
    formlayout.addWidget(label)
    label.text = "(tolerence)"
    tolerence=  RBA::QLineEdit.new(dialog)
    tolerence.text="0.05"
    formlayout.addWidget(tolerence)
    
    buttonOK = RBA::QPushButton.new(dialog)
    formlayout.addWidget(buttonOK)
    buttonOK.text = " OK "
    buttonOK.clicked do 
    #############################################################
    outrule_layer=20
    measure_base_layer=40
    tolerence=tolerence.text.to_f
    min_Check_value=min_Check_value.text.to_f
    infcase=measure_type.currentText.split(".")[0]
    patchedLayer    = input(151,measure_base_layer)
    roundedLayer    = input(151,1)
    outruleLayer    = input(151,outrule_layer)
    
    if roundedLayer.is_empty? then
    patchedLayer    = input(51,measure_base_layer)
    roundedLayer    = input(51,1)
    outruleLayer    = input(51,outrule_layer)
    end
    
    real_round_pattern=(roundedLayer.interacting(patchedLayer)).not_interacting(outruleLayer)
    
    if infcase=="1" then
    min_inner=autoMeasuer(patchedLayer, real_round_pattern,infcase,min_Check_value,tolerence)
    RBA::MessageBox.info("check_result","The min corner value of inner(小數下第二位四捨五入(round(2))) is #{min_inner}", RBA::MessageBox.b_ok)
    end
    
    if infcase=="2" then
    min_outer=autoMeasuer(patchedLayer, real_round_pattern,infcase,min_Check_value,tolerence)
    RBA::MessageBox.info("check_result","The min corner value of outer(小數下第二位四捨五入(round(2)) is #{min_outer}", RBA::MessageBox.b_ok)
    end
    #############################################################
    mark_layer=input(500,0)
    mark_layer.output(500,0)
    dialog.accept()
    end  ##### for button OK.
    buttonOK = RBA::QPushButton.new(dialog)
    formlayout.addWidget(buttonOK)
    buttonOK.text = " Cancel"
    buttonOK.clicked do
    
    dialog.accept()
    end   
    dialog.exec
    end     ###
    userform
    ###########################################################
    
    7.7z 825B
Sign In or Register to comment.