domain decomposition

edited September 2023 in General

I would like to perform a domain decomposition of a multilayer layout (named layout1) according to following flow:
1) define a new layer named "internaIF"
2) Using Klayout GUI define a first polygon on layer "internaIF"
3) To have a command which intersects the polygon1 with all the shapes of all other layers and puts the result in a new layout named "layout1_part1". The polygon1 should be inserted in the layer "internaIF" of "layout1_part1".

Repeat steps 2 and 3 adding new polygons which share one ore more edges with previous polygons.
Polygon2 is added to layer "internaIF" of layout1 (which accumulates all polygons) and to layer "internaIF" of "layout1_part2".

The generated layouts "layout1_part1", "layout1_part2".... are meant to be transformed into 3D structures using the common technology stackup and then simulated with an electromagnetic solver.
Each edge of polygon1 will be extruded along the layer depth generating a rectangular face which will be used by the EM solver to define a multimodal waveguide port assoiciated with subcomponent "layout1_part1".

I need help in coding the function called in step 3.

«13

Comments

  • Hi wsteffe

    not quite sure whether I fully understand your request, but this example shows how to chop gds in chunks and save them.

    The layout I run for the following image is as attached zip file.

    import pya
    import math
    import functools
    
    folderPath   = #specify save path
    
    layoutView   = pya.Application.instance().main_window().current_view()  
    layout       = layoutView.active_cellview().layout()
    cell         = layout.cell("TOP")
    unit         = layout.dbu
    layoutBBox   = functools.reduce(lambda a, b : a + b, [cell.bbox(lyp.layer_index()) for lyp in layoutView.each_layer()])
    unitSize     = int(0.3 / unit)
    part         = 0
    
    internalFReg = pya.Region()
    for col in range(math.ceil(layoutBBox.width()/unitSize)):
        for row in range(math.ceil(layoutBBox.height()/unitSize)):
            part += 1
            internalFReg += pya.Region(
                pya.Box(
                    pya.Point(layoutBBox.left + unitSize *  col,     layoutBBox.top - unitSize  * (row + 1)),
                    pya.Point(layoutBBox.left + unitSize * (col + 1), layoutBBox.top - unitSize * row),
                )
            )
    
            for lyp in layoutView.each_layer():
                ly, dt     = lyp.source_layer, lyp.source_datatype
                layerIndex = lyp.layer_index()
                layerReg   = pya.Region([ shape.polygon for shape in cell.each_shape(layerIndex) ])
    
                if layerReg.count() > 0:
                    outputReg     = internalFReg & layerReg
                    newLayoutView = pya.LayoutView()
                    newLayoutView.create_layout(True)
                    newcellView   = newLayoutView.cellview(0)
                    newLayout     = newcellView.layout()
                    newLayout.create_cell("TOP")
                    newLayout.cell("TOP").shapes(newLayout.layer(ly, dt)).insert(outputReg)
                    newLayout.cell("TOP").shapes(newLayout.layer(100, 0)).insert(internalFReg)
                    newLayoutView.save_as(0, rf"{folderPath}\layout_part{part}_{ly}.{dt}.gds", pya.SaveLayoutOptions())
    
  • Hi RawrRanger
    Many thanks for your example. It is a very nice code.
    The main difference with respect to my requirement is that I would like to split regions using more general polygons instead of simple rectangles (Boxes).
    I also do not need the external loops:

    for col in range(math.ceil(layoutBBox.width()/unitSize)):
    for row in range(math.ceil(layoutBBox.height()/unitSize)):

    Instead I would like to select a polygon previously drawn in klayout GUI and then apply a macro which
    restricts the geometries of all layers to the interior of the selected polygon.

    I have also reconsidered the data structure that best fits my needs and now I think it would be better to put the
    result in a new cell instead of directly in a new layout. The new layout will be generated in a second phase using another
    macro called "create a new layout using only the selected cell".

    So more specific questions are:
    1) Is it possible to create and register a macro. Let say a macro called
    "create a new cell with data of all layers and all geometries restricted inside the selected polygon" ?
    2) How can I use a selected shape (a polygon) in the macro code ?

  • @wsteffe Your description is not easy to understand. A picture may be helpful.

    Matthias

  • HI wsteffe,

    This example list out all the polygons within given cell/layer, you can access them by index or filter out usefull polygons by their data points.

    def layerPolygons(cell, ln, dt):
        return [itr.shape().polygon.transformed(itr.trans()) for itr in layout.begin_shapes(cell, cell.layout().layer(ln, dt))]
    

    This example clips every layers in cell using given sets of polygons except layers that specified in ignor layers

    def clipLayoutByPolys(cell, newCell, clipPolys, ignorLayers =[]):
        clipReg = pya.Region(clipPolys)
        layout  = cell.layout()
    
        for lif in layout.layer_infos():
            ln, dt = lif.layer, lif.datatype
    
            if not((ln, dt) in ignorLayers):
                layerReg = pya.Region( layerPolygons(cell, ln, dt) )
    
                if layerReg.count() > 0:
                    newCell.shapes(layout.layer(ln, dt)).insert(clipReg & layerReg)
    

    Assuming oy have a meshed polygon layer on layer(100, 0) and you can use following code to perform a layer to layer boolean and greate this result.

    import pya
    
    def layerPolygons(cell, ln, dt):
        return [itr.shape().polygon.transformed(itr.trans()) for itr in layout.begin_shapes(cell, cell.layout().layer(ln, dt))]
    
    def sortClipPolygons(pool):
        i              = 0
        sortByNeighbor = []
        target         = pool[0]
        while len(pool) > 0:
            pool, someNeighbor, target = findNeighbor(target, pool)
            sortByNeighbor += someNeighbor
            i += 1
        return sortByNeighbor
    
    def findNeighbor(target, pool):
        nonNeighbor = []
        neighbor    = [target]
        for other in pool:
            (nonNeighbor, neighbor)[other.touches(target)].append(other)
        return nonNeighbor, neighbor[:-1], list(pya.Region(neighbor).merged().each())[0]
    
    def clipLayoutByPolys(cell, newCell, clipPolys, ignorLayers =[]):
        clipReg = pya.Region(clipPolys)
        layout  = cell.layout()
    
        for lif in layout.layer_infos():
            ln, dt = lif.layer, lif.datatype
    
            if not((ln, dt) in ignorLayers):
                layerReg = pya.Region( layerPolygons(cell, ln, dt) )
    
                if layerReg.count() > 0:
                    newCell.shapes(layout.layer(ln, dt)).insert(clipReg & layerReg)
    
    layoutView = pya.Application.instance().main_window().current_view()  
    layout     = layoutView.active_cellview().layout()
    cell       = layout.cell("TOP")
    pool       = layerPolygons(cell, 100, 0)    # get all mesh on layer(100, 0) as polygons
    pool       = sortClipPolygons(pool)         # sort them in orders by finding their neighbors
    clipPolys  = []
    
    for i, clipPoly in enumerate(pool):         # feed sorted polygons for clip layer
        clipPolys.append(clipPoly)              # accumulate the clipper
        if i % 100 == 0:
            num     = str(i).rjust(4, "0")
            newCell = layout.create_cell(f"TOP_part{num}")
            clipLayoutByPolys(cell, newCell, clipPolys, ignorLayers = [(100, 0)])
    
  • edited September 2023

    Hi RawrRanger, thanks again for your hints.
    The code seems nice but there are some parts I can not understand very well.
    The most difficult to understand is the function sortClipPolygons(pool).
    What does it do exactly ? It seems that target polygon is merged with touching (nighboring) ones .
    So it should increase inside the while loop until it covers all the pool. But I think that the loop may never
    end if the pool polygons don't make a connected region.

    The most interesting part to me is anyway the code
    newCell.shapes(layout.layer(ln, dt)).insert(clipReg & layerReg)

    I presume that it creates in the new cell a layer with (Number,Datatype) taken from input layout and that it
    puts inside of it a clipped version of the shapes of the corresponding layer (same info) in the input layout.
    This is exacty what I need. Actually I would like to identify the clipPoly with a user selected polygon and the
    ignorLayers with the hidden layers. In other words I would like to use the mouse selection to identify the clip polygon
    and the show/hide flags in the layer window to identify the layers that I want to place (with clipped shapes) in the new cell.

  • Hi wsteffe,

    Indeed, the sortClipPolygons does exactly the same as you described, this existed to sort polygons in a order that it always shows upright next (touches) to previous clip shape.

    In test case, all polygons are connected with each other and forms one object, if multiple object sets existed, then we might need to add additional check and jump to another starting point.

    For this part: newCell.shapes(layout.layer(ln, dt)).insert(clipReg & layerReg) is also like what you described.

    newCell    = layout.create_cell( 'some_cell_name' )   # this create a new cell in given layout, and returns cell_ptr
    ln, dt     = 1, 0                                     # layer number 1, datatype 0
    layerIndex = layout.layer(ln, dt)                     # get layer index of layer(1, 0), creates layer if not already existed
    insertReg  = clipReg & layerReg                       # boolean AND for two regions
    newCell.shapes(layerIndex).insert(insertReg)          # insert Region into new cell specified layer 
    

    By using layoutView.each_object_selected() you can get access to what user have selected.

    def selectedPolygons(sortSeq = True):
        layoutView   = pya.Application.instance().main_window().current_view()
        objInstPaths = [o for o in layoutView.each_object_selected() if not(o.is_cell_inst())]
        # list out selected objects that is not a cell, returns a list of ObjectInstPath
    
        objInstPaths = sorted(objInstPaths, key=lambda o: o.seq) if sortSeq else objInstPaths
        # sort ObjectInstPath by selection sequence if you like to know which is the first selected object
    
        polygons     = [o.shape.polygon.transformed(o.trans()) for o in objInstPaths]
        # turn ObjectInstPath into shape then to polygon, the transfrorm part is here to map the polygon 
        # location to current cell incase the shape is originally in side of another cell
    
        return polygons
    

    The current_layer provides layer that is currently on focused by user selection, using this to get layer index for shape insertion.

    def insertIntoNewCellActiveLayer(newCell, insertReg):
        layoutView       = pya.Application.instance().main_window().current_view()
        activeLayerProp  = layoutView.current_layer.current()
        activeLayerIndex = lyp.layer_index()
        newCell.shapes(activeLayerIndex).insert(insertReg) 
    

    hope this helps.

  • @RawrRanger Wow ... thanks for these elaborate contributions! I really appreciate it.

    @wsteffe I think you see that @RawrRanger's support is exceptional. I think you can help with a more elaborate explanation - like a mocked-up sample (input - intended output). If you have trouble attaching files here: simply place them into a .zip archive.

    Matthias

  • Yes, RawrRanger support is really very good and I am sorry for late replay (I had a very busy week).
    The last example given by RawrRanger is very helpful but it would be better to extract shapes from all visible layers instead of using the focused layer (current_layer).

    The final purpose is to extract a small part of a large circuit for EM simulations.

    The selected polygon is used to define the clipping area in the X,Y plane while, in the vertical direction, the extracted part can be convenienly limited to the visible layers (layers with visibility toggked on). This is because the user has to assure that the cutting polygon doesn't interfere in a bad way with any shape in the extracted layers. The best way to check it is to hide the other layers.

    This extract operation I want to implement is just a first step of a long way:

    • The extracted part has to be converted into a 3D shape using my layout2fc script (https://github.com/wsteffe/layout2fc. This script need be improved. The last development, which probably will be reverted, was suggested to me by Zheng Lei (https://github.com/realthunder/layout2fc) and was motivated by the need to improve efficiency/speed when dealing with large structures. Now this requirement is not so stringent because I have decided to split the circuit in 2D (using klayout) and then to pass to layout2fc only the extracted small parts.

    • The 3D shape is meant to be simulated with by EmCAD electromagnetic modeler (see http://hierarchical-electromagnetics.com) but there is still much work to do in the geometrical processing in order to make it possible.
      Here the main problem is that I will have to insert several parts (extracted from klayout) and it is necessary that the touching face/edges are "imprinted" and shared by adjacient parts. The "imprinting" operation is already done in the EmCAD code (http://hierarchical-electromagnetics.com) using the OpenCascade library but in a different context.
      In previous applications I was importing an entire assembly defined in a 3D CAD and the "imprinting" of all touching faces was made in a single run. Now I want to apply the EmCAD code to "planar" circuit (PCB, MIMIC...) and in this case it is better to add the parts (subcircuits) one by one making each time the imprinting of the new added faces/edges with the previously defined interfaces. Face/Edges associated with defined interfaces (and related index numbers) must be reused in order to not invalidate spice models generated by previous inserted parts.

    The first planar application in which I would like to introduce and test my EmCAD code is Signal Integrity of digital circuits. This is because the spice circuits generated by EmCAD are well suited for time domain simulations and this kind of simulation is much more useful in the digital domain (than RF circuits). So I asked a co-worker in Thales which is an expert in Signal Integrity and digital circuits to provide me a test case. Following image is taken from this large circuit:

  • edited September 2023

    I tried to experiment using code snippets taken from RawrRange examples:

    import pya
    import math
    
    layoutView  = pya.Application.instance().main_window().current_view()
    layout  = layoutView.active_cellview().layout()
    
    def layerPolygons(cell, ln, dt):
        return [itr.shape().polygon.transformed(itr.trans()) for itr in layout.begin_shapes(cell, cell.layout().layer(ln, dt))]
    
    def selectedPolygons(sortSeq = True):
        objInstPaths = [o for o in layoutView.each_object_selected() if not(o.is_cell_inst())]
        objInstPaths = sorted(objInstPaths, key=lambda o: o.seq) if sortSeq else objInstPaths
        polygons     = [o.shape.polygon.transformed(o.trans()) for o in objInstPaths]
        return polygons
    
    activeLayerProp  = layoutView.current_layer.current()
    activeLayerIndex = activeLayerProp.layer_index()
    topCell  = layout.cell("TOP")
    subdomCell= layout.cell("subdom1")  #cell named subdom1 was previously created using klayout GUI 
    
    # how may I go from activeLayerIndex (or from activeLayerProp) to ln,dt ??
    
    topLayerPolygons =  layerPolygons(topCell, ln, dt)
    topLayerReg      =  pya.Region(topLayerPolygons)
    
    clipPolys  =  selectedPolygons()
    clipReg    =  pya.Region(clipPolys)
    insertReg  =  clipReg & layerReg     
    
    subdomCell.shapes(activeLayerIndex).insert(insertReg)
    
  • edited September 2023

    Another experiment which should be closer to what I would like to have. As a next update I would like to restrict the function copyClippedPolys() to visible layers.

    import pya
    import math
    
    layoutView  = pya.Application.instance().main_window().current_view()
    layout  = layoutView.active_cellview().layout()
    
    def layerPolygons(cell, ln, dt):
       return [itr.shape().polygon.transformed(itr.trans()) for itr in layout.begin_shapes(cell, cell.layout().layer(ln, dt))]
    
    def selectedPolygons(sortSeq = True):
        objInstPaths = [o for o in layoutView.each_object_selected() if not(o.is_cell_inst())]
        objInstPaths = sorted(objInstPaths, key=lambda o: o.seq) if sortSeq else objInstPaths
        polygons     = [o.shape.polygon.transformed(o.trans()) for o in objInstPaths]
        return polygons
    
    def copyClippedPolys(cell, newCell, clipPolys, ignorLayers =[]):
        clipReg = pya.Region(clipPolys)
        layout  = cell.layout()
        for lif in layout.layer_infos():
            ln, dt = lif.layer, lif.datatype
            if not((ln, dt) in ignorLayers):
                layerReg = pya.Region( layerPolygons(cell, ln, dt) )
                if layerReg.count() > 0:
                    newCell.shapes(layout.layer(ln, dt)).insert(clipReg & layerReg)
    
    activeLayerProp  = layoutView.current_layer.current()
    activeLayerIndex = activeLayerProp.layer_index()
    topCell  = layout.cell("TOP")
    subdomCell= layout.cell("subdom1")  #cell named subdom1 was previously created using klayout GUI 
    
    clipPolys  =  selectedPolygons()
    #splitting Polygons should be placed in layer (ln,dt)=(100,0) and not copied
    copyClippedPolys(topCell, subdomCell, clipPolys, [(100,0)])
    

    Unfortunately it doesn't work. The lif variable in the for loop of function copyClippedPolys() is not properly set.

  • Another big problem is that, because I have created a new top cell (named subdomain1) the project can not be saved in a dxf file.
    I saved it into a gds2 file but in this process I have lost all layer names. And this is very bad.

  • Now I have tried to use a lyp file in order to restore layer names in the gds project. In fact loadig lyp file restores the layer names but all geometry is lost.

  • edited September 2023

    I have just understood the probable cause of my problems. The test case I am using (see previous image) is based on a dxf file that I have converted from an ODB++ file I had received. Layer index and datatype are not defined in the dxf file. Only layer names are defined (but not used by gds).

    On the other side I would like to be able to deal also with this situation. In a typical application I will expect the PCB design is done with a commercial EDA tool. In this case the tool was Siemens hyperlynx. And I can not ask to EDA vendor (Siemens) to add a layer index and a data type.

  • Hi wsteffe,

    Like you've mensioned, If the file is imported by non-generic layout files then the layer_number and datatype part might fail, due to this info is not specified in their file format , the layer_num and datatypes will all -1.

    if you save this into a new gds or dxf, klayout will assign layer_num/datatype to them, thats why the shapes might seems to be gone due to the layer info mismatch.

    without mapping them we can only process the file using layer_index, thats why codes that relies on layer_no/datatype parts fails.

    Here's an exapmle of previous test case, clip select shapes with all visible layers and drop into subdom1, but this time use layer index as parameters instead of gds num/datatype.

    import pya
    import math
    
    layoutView  = pya.Application.instance().main_window().current_view()
    layout      = layoutView.active_cellview().layout()
    
    def layerPolygonsByID(cell, layerID):
        print(layerID)
        return [itr.shape().polygon.transformed(itr.trans()) for itr in layout.begin_shapes(cell, layerID)]
    
    def selectedPolygons(sortSeq = True):
        objInstPaths = [o for o in layoutView.each_object_selected() if not(o.is_cell_inst())]
        objInstPaths = sorted(objInstPaths, key=lambda o: o.seq) if sortSeq else objInstPaths
        polygons     = [o.shape.polygon.transformed(o.trans()) for o in objInstPaths]
        return polygons
    
    def copyClippedPolys(layoutView, cell, newCell, clipPolys, ignorLayersByID = []):
        clipReg = pya.Region(clipPolys)
        for lyp in layoutView.each_layer():
            lid = lyp.layer_index()
            if not(lid in ignorLayersByID):
                layerReg = pya.Region( layerPolygonsByID(cell, lid) )
                if layerReg.count() > 0:
                    newCell.shapes(lid).insert(clipReg & layerReg)
    
    def getHiddenLayerIDs(layoutView):
        return [lyp.layer_index() for lyp in layoutView.each_layer() if not(lyp.visible)]
    
    # keep this here incase you like to add more ignore layers
    def getLayerIDbyName(layerName):  
        for lyp in layoutView.each_layer():
            if lyp.source_name == layerName:
                return lyp.layer_index()
        return -1
    
    topCell    = layout.cell("TOP")
    subdomCell = layout.cell( 'subdom1' )
    clipPolys  = selectedPolygons()
    ignorLID   = getHiddenLayerIDs(layoutView)
    copyClippedPolys(layoutView, topCell, subdomCell, clipPolys, ignorLID)
    
  • For file conversion part, currently I did not notice options that provides export to DXF with layer description preserved, once you export the cell into new file, you might encounter some difficulty recognizing layers.

    Since the automatic assigned layer number might not always be the same during export, a layer mapping is recommended, with mapped files and *.lyp, you should able to have a file that is much easy to maintain.

    Here's an exapmle for mapping layers, this operation can be done pior polygon clipping if you like most of the operation is done in gds format.

    import pya
    
    mapping = { 
        "layer_10_gnd3" : [10, 0],
        "layer_11_sig4" : [11, 0],
        "layer_12_sig5" : [12, 0],
        "layer_13_pwr3" : [13, 0],
    }
    
    layoutView = pya.Application.instance().main_window().current_view()
    layout     = layoutView.active_cellview().layout()
    
    for lyp in layoutView.each_layer():
        lid = lyp.layer_index()
        lmn = lyp.name
        if lmn in mapping:
            lno, ldt = mapping[ info.name ]
            layout.set_info(lid, pya.LayerInfo(lno, ldt, info.name))
    layoutView.add_missing_layers()
    
  • Hi RawrRanger,
    I have just tested your code (that one in your second last message) and it worked well.
    Next first image shows the selected polygon in the TOP cell while the second image shows what has been placed in cell subdomain1

  • Now I am reconsidering the whole process:
    It would like to avoid merge operations and savings to gds2 (which is required for having more than one top cell). This is because I would like to preserve circles (not converting them to polygons) and also the polygons with holes (without introducing internal cuts).
    To do that I have to put in a new cell (or perhaps better in a new layout because top cells not compatible with dxf) not the merged shapes but all individual shapes having a bbox that intersects with the clipping polygon. The clipping operation may be done subsequently using opecascade library after the dxf file has been imported in the FreeCAD environment. OCC booleans can be applied also on faces delimited by curved lines (circles, ellipses...). To be sure that operation involving bboxes do not destroy the original data structure (circles and polygon with holes) perhaps it is better to do them using the python module ezdxf which operates natively on the dxf data structures.
    I still want to do that in klayout because it is very fast and it is able to load/visualize compex layouts. I have tried to open the test file with qcad (which works on dxf files) but it is much slower. I have olso tried to import it directly in FreeCAD but it takes forever loading it. Another very good point of using klayout is its nice python API which can easily be extended with external python modules (like ezdxf).

  • I just went trhough the following steps
    1. open the test case "ebp.dxf" in klayout
    2. create a new layer ln/dt=100/0 named "clypPolys"
    3. add a polygon (4 edges) to the new layer 100/0
    4. save into a new file "ebp2.dxf"

    The file size has grown from 18 MB of "ebp.dxf" to 70 MB of "ebp2.dxf". And I haven't even touched the original shapes.
    To avoid this behaviour I think I have to put the clypping polygons in a new layout and never touching/saving the original dxf file.

  • Hi wsteffe,

    Due to the nature of gds, all curves will be stored in a collection of points and segments, even if the curves was imported from DXF.

    Some layout tools supports True Curves with non-standard file format, but I found most of them cannot open DXF files directly or does not have good API support.

    Previous I've tried a work around by parse Klayout shapes and convert them into curves using FreeCAD API for mechanical simulation, but this approach is very pattern dependent, I'm not quite sure whether this is the most effecient/reliable way to solve this type of task.

  • I've tried a work around by parse Klayout shapes and convert them into curves using FreeCAD API

    I think it would be a very complex task to do.

    Actually I have no need to change the original shapes.
    I just want to use klayout only to properly position a few clipping polygons but, for a good placement, I have to do it in a window in which I can see the positions of the existing shapes in the selected layers.
    Once I have defined a clippling polygon and a list of selected layers (here is better to use the layer names) I may pass these input data to a python code based on ezdxf which operates directly on the original dxf file.
    Iterdxf (https://ezdxf.readthedocs.io/en/stable/addons/iterdxf.html) is a nice addon which I could use for that.

  • edited September 2023

    After a thinking I came to the following requirements:
    I need to separate the following structures in different layaout (and files):

    • The layout1 of original shapes (imported from dxf file coming from the EDA tool)
    • The layout2 of the splitting polygons (created in the klayout GUI)
    • The layout3 of the subdomains generated in the python API which must contain the original shapes (circles...)

    When I am drawing a splitting polygon I have to see at the same time layout1 and layout2 in the same window.
    I think that I can do that by importing layout1 and then using Import/Other File Into Current (with option Instantiate) for layout2.dxf.
    Once I have created some polygons in the instance of layout2 I would like to save it again in the separate file (layout2.dxf).

    The reason for separation (in different files) of layout1.dxf and layout2.dxf is because I expect that, at a certain point, the circuit designer will make an updated design (a new layout1.dxf) containing new elements. In this case I would like to reuse layout2 with the new circuit eventually to add new clypping polygons to it.

  • One thing I can't really understand of klayout is cell creation and cell management.
    I am only able to create top level cells but I can't find a way to make them childs of another cell.
    I have also tried to select a polygon (which was defined in a layer of the "TOP" cell) and then to use the command
    "Edit/Selection/make cell". But also in this case it creates a top cell (with user given name) removing geometry
    from the original "TOP" cell.
    It doesn't make sense to me. Why it doesn't put the new cell below the active TOP cell in which the replaced geometry was defined?
    I have also tried the "Instance" command selecting a newly created top cell but it doesn't work.

  • Hi wsteffe,

    For part 1 and 2, opening multiple layout in same window is achievable, following example loads a DXF file and a program generated layout for clipping into the same LayoutView.

    each layout will be held by individual cellView, and operation on them will not affecting another.

    For DXF part you can set layer property to invalid to reduce unintentional changes from user input.

    import pya
    mainWindow = pya.Application.instance().main_window()
    
    def loadLayout(layoutView, path = None, lockLayers = False):
        cellViewId = layoutView.load_layout(path, 2) if path else layoutView.create_layout(2)
        cellView   = layoutView.cellview(cellViewId)
        if lockLayers:
            for lyp in layoutView.each_layer():
                lyp.valid = False
        return cellView
    
    dxfPath      = r"....\KaIMUX-PCB.dxf"
    layoutView   = mainWindow.view(mainWindow.create_view())
    dxfCellView  = loadLayout(layoutView, path = dxfPath, lockLayers = True)
    clipCellView = loadLayout(layoutView, path = None,    lockLayers = False)
    
    cliplLayout  = clipCellView.layout()
    clipCell     = cliplLayout.create_cell("TOP")  
    clipLayer    = cliplLayout.layer(100, 0)       
    clipCellView.cell = clipCell                  # set cell on focused
    clipCell.shapes(clipLayer).insert(pya.DPolygon([pya.DPoint(6, -3), pya.DPoint(6, 3), pya.DPoint(12, 3), pya.DPoint(12, -3)]))
    layoutView.add_missing_layers()
    

    for part 3, shape insertion,
    use layoutView = pya.LayoutView() to create a new layoutView in the background
    or use layoutView = mainWindow.view(mainWindow.create_view()) to create a new layoutView in user interface
    to replace layoutView = mainWindow.view(mainWindow.create_view())
    before calling copyClippedPolys(layoutView .... ) function

    This seperates what we are doing in our current window, and everything we done on either side will not affect another.

  • Hi RawRanger, I do not understand the purpose of your last code.
    The following image shows the result I get with this code:

    Apart of the @1 appended to layer names (which I don't know what it means) it seems to me that it is the same as the origonal layout with the addition of a polygon in the new layer 100/0. I could have done it directly in the GUI, eventually using a copy of the origonal dxf.

  • Hi wsteffe

    This examples load two layout into the user interface
    @1 this one is the DXF for clipping
    @2 another is a newly create layout by code, can later be change to another layout with custom clip shapes.

    The code that generates Rect can be removed, this is to represent user input/drawing that defines clip shapes.

    Both layout is loaded into one viewport for operation, so to @ sign is added before layers to distinguish layers from one layout to another.

    editing that is done on @2 will not affect @1 and both can be saved/closed separately for future use.

    If you right click on the cell panel and check split mode it'll show both top cell from each layout.

  • edited September 2023

    Hi RawRanger, I see and indeed it is very good. But would it be possible to do all that in the GUI ?:

    1) Load originalGeometry.dxf possibly with locked layers

    2) Create a new cell named cell1 and make both visibles in the same newport
    3) Draw the clip rectangle in the layer 100/0 of cell1
    4) At this point I would like to create in cell1 a set of void layers with same names as the visible layer in original.dxf TOP
    5) save cell1 into originalGeometry_cell1.dxf

    Repeat 2 to 5 with a new cell named cell2 an a new clipPoly.
    Here I would like to see the originalGeometry together with cell1 and cell2 in the same viewport.
    This is because while drawing the clipping polygon for cell2 I have to:

    • to look at the geometries I want to extract from the original shapes
    • to assure that poly2 is adjacent (in example sharing a common edge) to clipping poly1 of cell1

    Once created I may use cell1.dxf together with originalGeometry.dxf in a python script based on exdxf to extract from originalGeometry.dxf the shapes belonging to the layers that are stored in cell1 and have no void intersection with the clypPoly stored in layer 100/0 of cell1.
    The result should be placed in a new layout (let say subdomain1.dxf).

    Same kind of operations should be repeated with cell2.dxf, cell3.dxf ...leading to subdomain2.dxf, subdomain2.dxf, ....

  • edited September 2023

    Hi wsteffe,

    Items 1~5 are all achievable through GUI, (2) to load multiple layout into the view just need to select add layout to current panel option during creating layout or loading layout with a existed file opened in window.

    This creates two or more CellView in one LayoutView, but this way user need to load the layout in correct order otherwise the CellView indexing from the code might pointing to the wrong layout during clipping.

    For locking the layers (1), the way I do is to make the layer to invalid,. Due to files directly from DXF does not have layer number and datatype, so setting up layer properties through *.lyp is not feasible, this has to be done manually or by script. Selecting all corrsponding layers from the side panel and right click can to assign this property manually.

    This prevent user from selecting or moving shapes from UI, but cannot prevent user from drawing on them. Currently there's no easy way to make layout read only in edit mode except making the layout cell as a library object.

    For creating layers for specific CellView (4), select corrsponging cell tree item before using new layer command and assign layer name. However this name properties is not going to be stored in gds format, to make this works, a *.lyp is required.

  • For clip from mutiple layout, this example takes selection from different layout and perform clip to the target.
    clipped shapes will be insert into corrosponding layout based on the source of clipper.

    Generated layout should be able to export to dxf for processing.
    This example manually add two new layout into the same View of the target layout.
    I add @2 layer (100, 0), @3 layer (100, 0) and select them before run this script.

    import pya
    
    def layerPolygonsByID(cell, layerID):
        return [itr.shape().polygon.transformed(itr.trans()) for itr in cell.layout().begin_shapes(cell, layerID)]
    
    def selectedCVPolygons():
        # store the CellView id, need this so clip shapes can insert into corrosponding cells
        cvPolygons = {}
        objInstPaths = [o for o in layoutView.each_object_selected() if not(o.is_cell_inst())]
        for o in objInstPaths:
            if not (o.cv_index in cvPolygons):
                cvPolygons[o.cv_index] = []
            cvPolygons[o.cv_index].append(o.shape.polygon.transformed(o.trans()))
        return cvPolygons
    
    def copyClippedPolys(layoutView, targetCell, clipCell, clipPolys, ignorLayersByID = []):
        clipReg = pya.Region(clipPolys)
        for lif in targetCell.layout().layer_infos():
            lid = targetCell.layout().layer(lif)
            lmn = lif.name
            if not(lid in ignorLayersByID):
                layerReg  = pya.Region( layerPolygonsByID(targetCell, lid))
                resultReg = (clipReg & layerReg)
                if resultReg.count() > 0:
                    clipCell.shapes(clipCell.layout().layer(lmn)).insert(resultReg)
        layoutView.add_missing_layers()
    
    def getHiddenLayerIDs(layoutView):
        return [lyp.layer_index() for lyp in layoutView.each_layer() if not(lyp.visible)]
    
    mainWindow = pya.Application.instance().main_window()
    layoutView = mainWindow.current_view()
    targetCell = layoutView.cellview(0).layout().cell("TOP")
    
    clipCVPolygons = selectedCVPolygons()
    for cvIndex in clipCVPolygons:
        clipPolys  = clipCVPolygons[cvIndex]
        ignorLID   = getHiddenLayerIDs(layoutView)
        clipCell   = layoutView.cellview(cvIndex).layout().cell("TOP")
        copyClippedPolys(layoutView, targetCell, clipCell, clipPolys, ignorLID)
    
    mainWindow.call_menu('cm_lv_regroup_by_index')
    
  • edited September 2023

    Hi RawRanger,
    I have loost all the geometry after creating a new layout in the same panel
    Following image is before adding new layout:

    Following image is after adding new layout:

    In this case I had manually added (ln,dt) to each layer of oroginal dxf and saved the result into a gds+lyp.
    But I had the same problem also when I used the dxf file.

    Things are better but not really good if I do the same with the other test case (which I can not share):

    In this case the image is still visible but a lot of new text (in red color) has appeared which was not visible before making a new layout

  • I have just understood that the problem on geometry visibility vcan be fixed by increasing the levels (the second entry in the levels)

    But what deos it means ?

Sign In or Register to comment.