Beispiel #1
0
def separate(input, output, source, debug):
    """
    Separate a single board out of a multi-board design. The separated board is
    placed in the middle of the sheet.

    You can specify the board via bounding box or annotation. See documentation
    for further details on usage.
    """
    try:
        from kikit import panelize_ui_impl as ki
        from kikit.panelize import Panel
        from pcbnew import LoadBoard, wxPointMM

        preset = ki.obtainPreset([],
                                 validate=False,
                                 source=source,
                                 debug=debug)

        board = LoadBoard(input)
        sourceArea = ki.readSourceArea(preset["source"], board)

        panel = Panel()
        panel.inheritDesignSettings(input)
        panel.inheritProperties(input)
        destination = wxPointMM(150, 100)
        panel.appendBoard(input, destination, sourceArea)
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        if isinstance(preset, dict) and preset["debug"]["trace"]:
            traceback.print_exc(file=sys.stderr)
        sys.exit(1)
Beispiel #2
0
 def buildTabs(self, panel: Panel) -> Iterable[LineString]:
     """
     This function can directly build the tabs. In most cases, you don't have
     to override this and instead, override buildTabAnnotations.
     """
     panel.clearTabsAnnotations()
     self.buildTabAnnotations(panel)
     return panel.buildTabsFromAnnotations()
Beispiel #3
0
 def buildPartitionLine(self, panel: Panel,
                        framingSubstrates: Iterable[Substrate]) -> None:
     """
     This function should build the partition line in the panel. It gets an
     iterable of extra substrates that represent soon-to-be frame of the
     panel.
     """
     return panel.buildPartitionLineFromBB(framingSubstrates)
Beispiel #4
0
def extractBoard(input, output, sourcearea):
    """
    Extract a single board out of a file

    The extracted board is placed in the middle of the sheet
    """
    # Hide the import in the function to make KiKit start faster
    from kikit.panelize import Panel, fromMm, wxPointMM, wxRectMM, fromDegrees
    import sys
    try:
        panel = Panel()
        destination = wxPointMM(150, 100)
        area = wxRectMM(*sourcearea)
        panel.inheritDesignSettings(input)
        panel.inheritProperties(input)
        panel.appendBoard(input, destination, area, tolerance=fromMm(2))
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #5
0
def extractBoard(input, output, sourcearea):
    """
    Extract a single board out of a file

    The extracted board is placed in the middle of the sheet
    """
    panel = Panel()
    destination = wxPointMM(150, 100)
    area = wxRectMM(*sourcearea)
    panel.appendBoard(input, destination, area, tolerance=fromMm(2))
    panel.save(output)
Beispiel #6
0
def extractBoard(input, output, sourcearea):
    """
    Extract a single board out of a file

    The extracted board is placed in the middle of the sheet
    """
    try:
        panel = Panel()
        destination = wxPointMM(150, 100)
        area = wxRectMM(*sourcearea)
        panel.appendBoard(input, destination, area, tolerance=fromMm(2))
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #7
0
def tightgrid(input, output, space, gridsize, panelsize, tabwidth, tabheight,
              vcuts, mousebites, radius, sourcearea, vcutcurves, htabs, vtabs,
              rotation, slotwidth, tolerance, renamenet, renameref):
    """
    Create a regular panel placed in a frame by milling a slot around the
    boards' perimeters.
    """
    try:
        panel = Panel()
        rows, cols = gridsize
        if sourcearea[0]:
            sourcearea = wxRectMM(*sourcearea)
        else:
            sourcearea = None
        w, h = panelsize
        if 2 * radius > 1.1 * slotwidth:
            raise RuntimeError(
                "The slot is too narrow for given radius (it has to be at least 10% larger"
            )
        tolerance = fromMm(tolerance)
        psize, cuts = panel.makeTightGrid(input,
                                          rows,
                                          cols,
                                          wxPointMM(50, 50),
                                          verSpace=fromMm(space),
                                          horSpace=fromMm(space),
                                          slotWidth=fromMm(slotwidth),
                                          width=fromMm(w),
                                          height=fromMm(h),
                                          sourceArea=sourcearea,
                                          tolerance=tolerance,
                                          verTabWidth=fromMm(tabwidth),
                                          horTabWidth=fromMm(tabheight),
                                          verTabCount=htabs,
                                          horTabCount=vtabs,
                                          rotation=fromDegrees(rotation),
                                          netRenamePattern=renamenet,
                                          refRenamePattern=renameref)
        panel.addMillFillets(fromMm(radius))
        if vcuts:
            panel.makeVCuts(cuts, vcutcurves)
        if mousebites[0]:
            drill, spacing, offset = mousebites
            panel.makeMouseBites(cuts, fromMm(drill), fromMm(spacing),
                                 fromMm(offset))
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #8
0
def grid(input, output, space, gridsize, panelsize, tabwidth, tabheight, vcuts,
         mousebites, radius, sourcearea, vcutcurves, htabs, vtabs, rotation,
         tolerance, renamenet, renameref):
    """
    Create a regular panel placed in a frame.

    If you do not specify the panelsize, no frame is created
    """
    try:
        panel = Panel()
        rows, cols = gridsize
        if sourcearea[0]:
            sourcearea = wxRectMM(*sourcearea)
        else:
            sourcearea = None
        if panelsize[0]:
            w, h = panelsize
            frame = True
            oht, ovt = fromMm(space), fromMm(space)
        else:
            frame = False
            oht, ovt = 0, 0
        tolerance = fromMm(tolerance)
        psize, cuts = panel.makeGrid(input,
                                     rows,
                                     cols,
                                     wxPointMM(50, 50),
                                     sourceArea=sourcearea,
                                     tolerance=tolerance,
                                     verSpace=fromMm(space),
                                     horSpace=fromMm(space),
                                     verTabWidth=fromMm(tabwidth),
                                     horTabWidth=fromMm(tabheight),
                                     outerHorTabThickness=oht,
                                     outerVerTabThickness=ovt,
                                     horTabCount=htabs,
                                     verTabCount=vtabs,
                                     rotation=fromDegrees(rotation),
                                     netRenamePattern=renamenet,
                                     refRenamePattern=renameref)
        panel.addMillFillets(fromMm(radius))
        if vcuts:
            panel.makeVCuts(cuts, vcutcurves)
        if mousebites[0]:
            drill, spacing, offset = mousebites
            panel.makeMouseBites(cuts, fromMm(drill), fromMm(spacing),
                                 fromMm(offset))
        if frame:
            panel.makeFrame(psize, fromMm(w), fromMm(h), fromMm(space))
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #9
0
def panelize(input, output, preset, layout, source, tabs, cuts, framing,
             tooling, fiducials, text, post, debug, dump):
    """
    Panelize boards
    """
    try:
        # Hide the import in the function to make KiKit start faster
        from kikit import panelize_ui_impl as ki
        from kikit.panelize import Panel
        from pcbnew import LoadBoard, wxPointMM
        import json
        import commentjson
        import sys
        from itertools import chain

        preset = ki.obtainPreset(preset,
                                 layout=layout,
                                 source=source,
                                 tabs=tabs,
                                 cuts=cuts,
                                 framing=framing,
                                 tooling=tooling,
                                 fiducials=fiducials,
                                 text=text,
                                 post=post,
                                 debug=debug)

        board = LoadBoard(input)

        panel = Panel()
        panel.inheritDesignSettings(input)
        panel.inheritProperties(input)

        sourceArea = ki.readSourceArea(preset["source"], board)
        substrates = ki.buildLayout(preset["layout"], panel, input, sourceArea)
        framingSubstrates = ki.dummyFramingSubstrate(
            substrates, ki.frameOffset(preset["framing"]))
        panel.buildPartitionLineFromBB(framingSubstrates)

        tabCuts = ki.buildTabs(preset["tabs"], panel, substrates,
                               framingSubstrates)
        backboneCuts = ki.buildBackBone(preset["layout"], panel, substrates,
                                        ki.frameOffset(preset["framing"]))
        frameCuts = ki.buildFraming(preset["framing"], panel)

        ki.buildTooling(preset["tooling"], panel)
        ki.buildFiducials(preset["fiducials"], panel)
        ki.buildText(preset["text"], panel)
        ki.buildPostprocessing(preset["post"], panel)

        ki.makeTabCuts(preset["cuts"], panel, tabCuts)
        ki.makeOtherCuts(preset["cuts"], panel, chain(backboneCuts, frameCuts))

        ki.runUserScript(preset["post"], panel)

        ki.buildDebugAnnotation(preset["debug"], panel)

        panel.save(output)

        if (dump):
            with open(dump, "w") as f:
                f.write(ki.dumpPreset(preset))
    except Exception as e:
        import sys
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        if isinstance(preset, dict) and preset["debug"]["trace"]:
            traceback.print_exc(file=sys.stderr)
        sys.exit(1)
Beispiel #10
0
def tightgrid(input, output, space, hspace, vspace, gridsize, panelsize,
              tabwidth, tabheight, vcuts, mousebites, radius, sourcearea,
              vcutcurves, htabs, vtabs, rotation, slotwidth, tolerance,
              renamenet, renameref, tabsfrom, copperfill, fiducials, tooling,
              alternation):
    """
    Create a regular panel placed in a frame by milling a slot around the
    boards' perimeters.
    """
    # Hide the import in the function to make KiKit start faster
    from kikit.panelize import Panel, fromMm, wxPointMM, wxRectMM, fromDegrees
    import sys
    try:
        panel = Panel()
        #panel.inheritDesignSettings(input)
        panel.inheritProperties(input)
        rows, cols = gridsize
        if rows == -1 or cols == -1:
            raise RuntimeError(
                "Gridsize is mandatory. Please specify the --gridsize option.")
        if hspace is None:
            hspace = space
        if vspace is None:
            vspace = space
        if sourcearea[0]:
            sourcearea = wxRectMM(*sourcearea)
        else:
            sourcearea = None
        w, h = panelsize
        placementClass = getPlacementClass(alternation)
        validateSpaceRadius(vspace, radius)
        validateSpaceRadius(hspace, radius)
        if 2 * radius > 1.1 * slotwidth:
            raise RuntimeError(
                "The slot is too narrow for given radius (it has to be at least 10% larger"
            )
        tolerance = fromMm(tolerance)
        psize, cuts = panel.makeTightGrid(input,
                                          rows,
                                          cols,
                                          wxPointMM(50, 50),
                                          verSpace=fromMm(vspace),
                                          horSpace=fromMm(hspace),
                                          slotWidth=fromMm(slotwidth),
                                          width=fromMm(w),
                                          height=fromMm(h),
                                          sourceArea=sourcearea,
                                          tolerance=tolerance,
                                          verTabWidth=fromMm(tabwidth),
                                          horTabWidth=fromMm(tabheight),
                                          verTabCount=htabs,
                                          horTabCount=vtabs,
                                          rotation=fromDegrees(rotation),
                                          netRenamePattern=renamenet,
                                          refRenamePattern=renameref,
                                          placementClass=placementClass)
        tabs = []
        for layer, width in tabsfrom:
            tab, cut = panel.layerToTabs(layer, fromMm(width))
            cuts += cut
            tabs += tab
        panel.appendSubstrate(tabs)
        panel.addMillFillets(fromMm(radius))
        if vcuts:
            panel.makeVCuts(cuts, vcutcurves)
        if fiducials[0] is not None:
            hOffset, vOffset, copperDia, openingDia = tuple(
                map(fromMm, fiducials))
            panel.addFiducials(hOffset, vOffset, copperDia, openingDia)
        if tooling[0] is not None:
            hOffset, vOffset, dia = tuple(map(fromMm, tooling))
            panel.addTooling(hOffset, vOffset, dia)
        if mousebites[0]:
            drill, spacing, offset = mousebites
            panel.makeMouseBites(cuts, fromMm(drill), fromMm(spacing),
                                 fromMm(offset))
        if copperfill:
            panel.copperFillNonBoardAreas()
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #11
0
def grid(input, output, space, hspace, vspace, gridsize, panelsize, tabwidth,
         tabheight, vcuts, mousebites, radius, sourcearea, vcutcurves, htabs,
         vtabs, rotation, tolerance, renamenet, renameref, tabsfrom, framecutv,
         framecuth, copperfill, railstb, railslr, tooling, fiducials,
         alternation):
    """
    Create a regular panel placed in a frame.

    If you do not specify the panelsize, no frame is created
    """
    # Hide the import in the function to make KiKit start faster
    from kikit.panelize import Panel, fromMm, wxPointMM, wxRectMM, fromDegrees
    import sys
    try:
        panel = Panel()
        #panel.inheritDesignSettings(input)
        #panel.inheritProperties(input)
        rows, cols = gridsize
        if rows == -1 or cols == -1:
            raise RuntimeError(
                "Gridsize is mandatory. Please specify the --gridsize option.")
        if hspace is None:
            hspace = space
        if vspace is None:
            vspace = space
        if sourcearea[0]:
            sourcearea = wxRectMM(*sourcearea)
        else:
            sourcearea = None
        if panelsize[0]:
            w, h = panelsize
            frame = True
            oht, ovt = fromMm(hspace), fromMm(vspace)
        else:
            frame = False
            oht, ovt = 0, 0
        if railstb:
            frame = False
            railstb = fromMm(railstb)
            ovt = fromMm(vspace)
        if railslr:
            frame = False
            railslr = fromMm(railslr)
            oht = fromMm(hspace)
        placementClass = getPlacementClass(alternation)

        validateSpaceRadius(vspace, radius)
        validateSpaceRadius(hspace, radius)
        tolerance = fromMm(tolerance)
        psize, cuts = panel.makeGrid(input,
                                     rows,
                                     cols,
                                     wxPointMM(50, 50),
                                     sourceArea=sourcearea,
                                     tolerance=tolerance,
                                     verSpace=fromMm(vspace),
                                     horSpace=fromMm(hspace),
                                     verTabWidth=fromMm(tabwidth),
                                     horTabWidth=fromMm(tabheight),
                                     outerHorTabThickness=oht,
                                     outerVerTabThickness=ovt,
                                     horTabCount=htabs,
                                     verTabCount=vtabs,
                                     rotation=fromDegrees(rotation),
                                     netRenamePattern=renamenet,
                                     refRenamePattern=renameref,
                                     forceOuterCutsV=railstb or frame,
                                     forceOuterCutsH=railslr or frame,
                                     placementClass=placementClass)
        tabs = []
        for layer, width in tabsfrom:
            tab, cut = panel.layerToTabs(layer, fromMm(width))
            cuts += cut
            tabs += tab
        panel.appendSubstrate(tabs)
        if vcuts:
            panel.makeVCuts(cuts, vcutcurves)
        if frame:
            (_, frame_cuts_v,
             frame_cuts_h) = panel.makeFrame(psize, fromMm(w), fromMm(h),
                                             fromMm(space))
            if framecutv:
                cuts += frame_cuts_v
            if framecuth:
                cuts += frame_cuts_h
        if railslr:
            panel.makeRailsLr(railslr)
        if railstb:
            panel.makeRailsTb(railstb)
        if fiducials[0] is not None:
            hOffset, vOffset, copperDia, openingDia = tuple(
                map(fromMm, fiducials))
            panel.addFiducials(hOffset, vOffset, copperDia, openingDia)
        if tooling[0] is not None:
            hOffset, vOffset, dia = tuple(map(fromMm, tooling))
            panel.addTooling(hOffset, vOffset, dia)
        if mousebites[0]:
            drill, spacing, offset = mousebites
            panel.makeMouseBites(cuts, fromMm(drill), fromMm(spacing),
                                 fromMm(offset))
        panel.addMillFillets(fromMm(radius))
        if copperfill:
            panel.copperFillNonBoardAreas()
        panel.save(output)
    except Exception as e:
        raise
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #12
0
def separate(input, output, source, page, debug, keepannotations):
    """
    Separate a single board out of a multi-board design. The separated board is
    placed in the middle of the sheet.

    You can specify the board via bounding box or annotation. See documentation
    for further details on usage.
    """
    try:
        from kikit import panelize_ui_impl as ki
        from kikit.panelize import Panel
        from pcbnewTransition.transition import isV6, pcbnew
        from pcbnew import LoadBoard, wxPointMM
        from kikit.common import fakeKiCADGui
        app = fakeKiCADGui()

        preset = ki.obtainPreset([], validate=False, source=source, page=page, debug=debug)

        if preset["debug"]["deterministic"] and isV6():
            pcbnew.KIID.SeedGenerator(42)

        board = LoadBoard(input)
        sourceArea = ki.readSourceArea(preset["source"], board)

        panel = Panel(output)
        panel.inheritDesignSettings(board)
        panel.inheritProperties(board)
        panel.inheritTitleBlock(board)

        destination = wxPointMM(150, 100)
        panel.appendBoard(input, destination, sourceArea,
            interpretAnnotations=(not keepannotations))
        ki.setStackup(preset["source"], panel)
        ki.positionPanel(preset["page"], panel)
        ki.setPageSize(preset["page"], panel, board)

        panel.save(reconstructArcs=True)
    except Exception as e:
        import sys
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        if isinstance(preset, dict) and preset["debug"]["trace"]:
            traceback.print_exc(file=sys.stderr)
        sys.exit(1)
Beispiel #13
0
def doPanelization(input, output, preset, plugins=[]):
    """
    The panelization logic is separated into a separate function so we can
    handle errors based on the context; e.g., CLI vs GUI
    """
    from kikit import panelize_ui_impl as ki
    from kikit.panelize import Panel
    from pcbnewTransition.transition import isV6, pcbnew
    from pcbnew import LoadBoard
    from itertools import chain

    if preset["debug"]["deterministic"] and isV6():
        pcbnew.KIID.SeedGenerator(42)
    if preset["debug"]["drawtabfail"]:
        import kikit.substrate
        kikit.substrate.TABFAIL_VISUAL = True

    board = LoadBoard(input)
    panel = Panel(output)

    useHookPlugins = ki.loadHookPlugins(plugins, board, preset)

    useHookPlugins(lambda x: x.prePanelSetup(panel))

    # Register extra footprints for annotations
    for tabFootprint in preset["tabs"]["tabfootprints"]:
        panel.annotationReader.registerTab(tabFootprint.lib, tabFootprint.footprint)

    panel.inheritDesignSettings(board)
    panel.inheritProperties(board)
    panel.inheritTitleBlock(board)

    useHookPlugins(lambda x: x.afterPanelSetup(panel))

    sourceArea = ki.readSourceArea(preset["source"], board)
    substrates, framingSubstrates, backboneCuts = \
        ki.buildLayout(preset, panel, input, sourceArea)

    useHookPlugins(lambda x: x.afterLayout(panel, substrates))

    tabCuts = ki.buildTabs(preset, panel, substrates,
        framingSubstrates, ki.frameOffset(preset["framing"]))

    useHookPlugins(lambda x: x.afterTabs(panel, tabCuts, backboneCuts))

    frameCuts = ki.buildFraming(preset, panel)

    useHookPlugins(lambda x: x.afterFraming(panel, frameCuts))

    ki.buildTooling(preset, panel)
    ki.buildFiducials(preset, panel)
    ki.buildText(preset["text"], panel)
    ki.buildPostprocessing(preset["post"], panel)

    ki.makeTabCuts(preset, panel, tabCuts)
    ki.makeOtherCuts(preset, panel, chain(backboneCuts, frameCuts))

    useHookPlugins(lambda x: x.afterCuts(panel))

    ki.buildCopperfill(preset["copperfill"], panel)

    ki.setStackup(preset["source"], panel)
    ki.positionPanel(preset["page"], panel)
    ki.setPageSize(preset["page"], panel, board)

    ki.runUserScript(preset["post"], panel)
    useHookPlugins(lambda x: x.finish(panel))

    ki.buildDebugAnnotation(preset["debug"], panel)

    panel.save(reconstructArcs=preset["post"]["reconstructarcs"],
               refillAllZones=preset["post"]["refillzones"])
Beispiel #14
0
def grid(input, output, space, gridsize, panelsize, tabwidth, tabheight, vcuts,
         mousebites, radius, sourcearea, vcutcurves, htabs, vtabs, rotation,
         tolerance, renamenet, renameref, tabsfrom, framecutv, framecuth):
    """
    Create a regular panel placed in a frame.

    If you do not specify the panelsize, no frame is created
    """
    try:
        panel = Panel()
        rows, cols = gridsize
        if rows == -1 or cols == -1:
            raise RuntimeError("Gridsize is mandatory. Please specify the --gridsize option.")
        if sourcearea[0]:
            sourcearea = wxRectMM(*sourcearea)
        else:
            sourcearea = None
        if panelsize[0]:
            w, h = panelsize
            frame = True
            oht, ovt = fromMm(space), fromMm(space)
        else:
            frame = False
            oht, ovt = 0, 0
        validateSpaceRadius(space, radius)
        tolerance = fromMm(tolerance)
        psize, cuts = panel.makeGrid(input, rows, cols, wxPointMM(50, 50),
            sourceArea=sourcearea, tolerance=tolerance,
            verSpace=fromMm(space), horSpace=fromMm(space),
            verTabWidth=fromMm(tabwidth), horTabWidth=fromMm(tabheight),
            outerHorTabThickness=oht, outerVerTabThickness=ovt,
            horTabCount=htabs, verTabCount=vtabs, rotation=fromDegrees(rotation),
            netRenamePattern=renamenet, refRenamePattern=renameref)
        tabs = []
        for layer, width in tabsfrom:
            tab, cut = panel.layerToTabs(layer, fromMm(width))
            cuts += cut
            tabs += tab
        panel.appendSubstrate(tabs)
        if vcuts:
            panel.makeVCuts(cuts, vcutcurves)
        if frame:
            (_, frame_cuts_v, frame_cuts_h) = panel.makeFrame(psize, fromMm(w), fromMm(h), fromMm(space))
            if framecutv:
                cuts += frame_cuts_v
            if framecuth:
                cuts += frame_cuts_h
        if mousebites[0]:
            drill, spacing, offset = mousebites
            panel.makeMouseBites(cuts, fromMm(drill), fromMm(spacing), fromMm(offset))
        panel.addMillFillets(fromMm(radius))
        panel.save(output)
    except Exception as e:
        sys.stderr.write("An error occurred: " + str(e) + "\n")
        sys.stderr.write("No output files produced\n")
        sys.exit(1)
Beispiel #15
0
def grid(input, output, space, gridsize, panelsize, tabwidth, tabheight, vcuts,
         mousebites, radius, sourcearea):
    """
    Create a regular panel placed in a frame.

    If you do not specify the panelsize, no frame is created
    """
    panel = Panel()
    rows, cols = gridsize
    if sourcearea[0]:
        sourcearea = wxRectMM(*sourcearea)
    else:
        sourcearea = None
    if panelsize[0]:
        w, h = panelsize
        frame = True
        oht, ovt = fromMm(space), fromMm(space)
    else:
        frame = False
        oht, ovt = 0, 0
    psize, cuts = panel.makeGrid(input, rows, cols, wxPointMM(50, 50),
        sourceArea=sourcearea, tolerance=fromMm(5), radius=fromMm(radius),
        verSpace=fromMm(space), horSpace=fromMm(space),
        verTabWidth=fromMm(tabwidth), horTabWidth=fromMm(tabheight),
        outerHorTabThickness=oht, outerVerTabThickness=ovt)
    if vcuts:
        panel.makeVCuts(cuts)
    if mousebites[0]:
        drill, spacing = mousebites
        panel.makeMouseBites(cuts, fromMm(drill), fromMm(spacing))
    if frame:
        panel.makeFrame(psize, fromMm(w), fromMm(h), fromMm(space), radius=fromMm(radius))
    panel.save(output)