Esempio n. 1
0
def main():
    """
    Entry point for readdxf.py.
    """
    args = process_arguments()
    for f in ut.xpand(args.files):
        print('Filename: {}'.format(f))
        try:
            data = dx.parse(f)
            if args.verbose:
                for d in data:
                    pprint.pprint(d)
            entities = dx.entities(data)
        except Exception as ex:
            logging.info('skipping file {}: {}'.format(f, ex))
            continue
        if not args.all:
            numbered = dx.numberedlayers(entities)
            bylayer = {nm: dx.fromlayer(entities, nm) for nm in numbered}
            entities = []
            for layerent in bylayer.values():
                entities += layerent
        num = len(entities)
        if num == 0:
            logging.warning('no entities found!')
            continue
        print('Contains: {} entities'.format(num))
        if args.verbose:
            for e in entities:
                pprint.pprint(e)
        layers = dx.layernames(entities)
        for layer in layers:
            print('Layer: "{}"'.format(layer))
            for e in dx.fromlayer(entities, layer):
                printent(e, args.verbose)
Esempio n. 2
0
def main():
    """
    Entry point for readdxf.py.
    """
    args = process_arguments()
    for f in ut.xpand(args.files):
        print('Filename: {}'.format(f))
        try:
            data = dx.parse(f)
            if args.verbose:
                for d in data:
                    pprint.pprint(d)
            entities = dx.entities(data)
        except Exception as ex:
            logging.info('skipping file {}: {}'.format(f, ex))
            continue
        if not args.all:
            numbered = dx.numberedlayers(entities)
            bylayer = {nm: dx.fromlayer(entities, nm) for nm in numbered}
            entities = []
            for layerent in bylayer.values():
                entities += layerent
        num = len(entities)
        if num == 0:
            logging.warning('no entities found!')
            continue
        print('Contains: {} entities'.format(num))
        if args.verbose:
            for e in entities:
                pprint.pprint(e)
        layers = dx.layernames(entities)
        for layer in layers:
            print('Layer: "{}"'.format(layer))
            for e in dx.fromlayer(entities, layer):
                printent(e, args.verbose)
Esempio n. 3
0
def main():
    """
    Entry point for dxf2nc.py.
    """
    args = process_arguments()
    sorters = {'xy': utils.bbxykey, 'yx': utils.bbyxkey, 'dist': utils.distkey}
    sortkey = sorters[args.sort]
    lines.epsilon = args.dist
    for f in utils.xpand(args.files):
        logging.info('Starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.nc')
            data = dx.parse(f)
            entities = dx.entities(data)
        except ValueError as ex:
            logging.info(str(ex))
            fns = "error during processing. Skipping file '{}'."
            logging.error(fns.format(f))
            continue
        except IOError as ex:
            logging.info(str(ex))
            logging.error("i/o error in file '{}'. Skipping it.".format(f))
            continue
        layers = dx.numberedlayers(entities)
        entities = [e for e in entities if dx.bycode(e, 8) in layers]
        num = len(entities)
        if num == 0:
            logging.info("no entities found! Skipping file '{}'.".format(f))
            continue
        logging.info('{} entities found.'.format(num))
        out = gerbernc.Writer(ofn)
        for layername in layers:
            out.newpiece()
            thislayer = dx.fromlayer(entities, layername)
            ls = '{} entities found in layer "{}".'
            logging.info(ls.format(len(thislayer), layername))
            segments = lines.mksegments(thislayer)
            fs = '{} segments in layer "{}"'
            logging.info(fs.format(len(segments), layername))
            if args.contours:
                cut_contours(segments, out, layername, sortkey)
            else:
                segments.sort(key=sortkey)
                cut_segments(segments, out)
        out.write()
Esempio n. 4
0
def main():
    """
    Entry point for dxf2nc.py.
    """
    args = process_arguments()
    sorters = {'xy': utils.bbxykey, 'yx': utils.bbyxkey, 'dist': utils.distkey}
    sortkey = sorters[args.sort]
    lines.epsilon = args.dist
    for f in utils.xpand(args.files):
        logging.info('Starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.nc')
            data = dx.parse(f)
            entities = dx.entities(data)
        except ValueError as ex:
            logging.info(str(ex))
            fns = "error during processing. Skipping file '{}'."
            logging.error(fns.format(f))
            continue
        except IOError as ex:
            logging.info(str(ex))
            logging.error("i/o error in file '{}'. Skipping it.".format(f))
            continue
        layers = dx.numberedlayers(entities)
        entities = [e for e in entities if dx.bycode(e, 8) in layers]
        num = len(entities)
        if num == 0:
            logging.info("no entities found! Skipping file '{}'.".format(f))
            continue
        logging.info('{} entities found.'.format(num))
        out = gerbernc.Writer(ofn)
        for layername in layers:
            out.newpiece()
            thislayer = dx.fromlayer(entities, layername)
            ls = '{} entities found in layer "{}".'
            logging.info(ls.format(len(thislayer), layername))
            segments = lines.mksegments(thislayer)
            fs = '{} segments in layer "{}"'
            logging.info(fs.format(len(segments), layername))
            if args.contours:
                cut_contours(segments, out, layername, sortkey)
            else:
                segments.sort(key=sortkey)
                cut_segments(segments, out)
        out.write()
Esempio n. 5
0
def main():
    """
    Entry point for dxfgerber.py.
    """
    args = process_arguments()
    sorters = {'xy': utils.bbxykey, 'yx': utils.bbyxkey, 'dist': utils.distkey}
    sortkey = sorters[args.sort]
    lines.epsilon = args.dist
    for f in utils.xpand(args.files):
        logging.info('starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.dxf', addenum='_mod')
            data = dx.parse(f)
            entities = dx.entities(data)
        except ValueError as ex:
            logging.info(str(ex))
            fns = "error during processing. Skipping file '{}'."
            logging.error(fns.format(f))
            continue
        except IOError as ex:
            logging.info(str(ex))
            logging.error("i/o error in file '{}'. Skipping it.".format(f))
            continue
        layers = dx.numberedlayers(entities)
        entities = [e for e in entities if dx.bycode(e, 8) in layers]
        num = len(entities)
        if num == 0:
            logging.info("no entities found! Skipping file '{}'.".format(f))
            continue
        logging.info('{} entities found.'.format(num))
        with open(ofn, 'w') as out:
            out.write(dxfheader)
            for layername in layers:
                thislayer = dx.fromlayer(entities, layername)
                ls = '{} entities found in layer "{}".'
                logging.info(ls.format(num, layername))
                segments = lines.mksegments(thislayer)
                fs = '{} segments in layer "{}"'
                logging.info(fs.format(len(segments), layername))
                write_allseg(segments, out, layername, sortkey)
            out.write(dxffooter)
Esempio n. 6
0
def main():
    """
    Entry point for dxfgerber.py.
    """
    args = process_arguments()
    sorters = {'xy': utils.bbxykey, 'yx': utils.bbyxkey, 'dist': utils.distkey}
    sortkey = sorters[args.sort]
    lines.epsilon = args.dist
    for f in utils.xpand(args.files):
        logging.info('starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.dxf', addenum='_mod')
            data = dx.parse(f)
            entities = dx.entities(data)
        except ValueError as ex:
            logging.info(str(ex))
            fns = "error during processing. Skipping file '{}'."
            logging.error(fns.format(f))
            continue
        except IOError as ex:
            logging.info(str(ex))
            logging.error("i/o error in file '{}'. Skipping it.".format(f))
            continue
        layers = dx.numberedlayers(entities)
        entities = [e for e in entities if dx.bycode(e, 8) in layers]
        num = len(entities)
        if num == 0:
            logging.info("no entities found! Skipping file '{}'.".format(f))
            continue
        logging.info('{} entities found.'.format(num))
        with open(ofn, 'w') as out:
            out.write(dxfheader)
            for layername in layers:
                thislayer = dx.fromlayer(entities, layername)
                ls = '{} entities found in layer "{}".'
                logging.info(ls.format(num, layername))
                segments = lines.mksegments(thislayer)
                fs = '{} segments in layer "{}"'
                logging.info(fs.format(len(segments), layername))
                write_allseg(segments, out, layername, sortkey)
            out.write(dxffooter)
Esempio n. 7
0
def main():
    """
    Entry point for dxf2pdf.py.
    """
    args = process_arguments()
    for f in utils.xpand(args.files):
        logging.info('starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.pdf', addenum='_dxf')
            data = dxf.parse(f)
            entities = dxf.entities(data)
        except ValueError as ex:
            logging.info(str(ex))
            fns = "cannot construct output filename. Skipping file '{}'."
            logging.error(fns.format(f))
            continue
        except IOError as ex:
            logging.info(str(ex))
            logging.error("cannot open the file '{}'. Skipping it.".format(f))
            continue
        output(f, ofn, entities, args)
Esempio n. 8
0
def main():
    """
    Entry point for dxf2pdf.py.
    """
    args = process_arguments()
    for f in utils.xpand(args.files):
        logging.info('starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.pdf', addenum='_dxf')
            data = dxf.parse(f)
            entities = dxf.entities(data)
        except ValueError as ex:
            logging.info(str(ex))
            fns = "cannot construct output filename. Skipping file '{}'."
            logging.error(fns.format(f))
            continue
        except IOError as ex:
            logging.info(str(ex))
            logging.error("cannot open the file '{}'. Skipping it.".format(f))
            continue
        output(f, ofn, entities, args)
Esempio n. 9
0
def main():
    """
    Entry point for nc2pdf.py.
    """
    args = process_arguments()
    for fn in utils.xpand(args.files):
        logging.info('starting file "{}"'.format(fn))
        try:
            ofn = utils.outname(fn, extension='.pdf', addenum='_nc')
            cuts = list(gerbernc.segments(fn))
        except ValueError as e:
            logging.info(str(e))
            fns = "cannot construct output filename. Skipping file '{}'."
            logging.error(fns.format(fn))
            continue
        except IOError as e:
            logging.info("cannot read file: {}".format(e))
            logging.error("i/o error, skipping file '{}'".format(fn))
            continue
        cnt = len(cuts)
        logging.info('got {} cuts'.format(cnt))
        xvals = [pnt[0] for s in cuts for pnt in s]
        yvals = [pnt[1] for s in cuts for pnt in s]
        minx, maxx = min(xvals), max(xvals)
        miny, maxy = min(yvals), max(yvals)
        bs = '{} range from {:.1f} mm to {:.1f} mm'
        logging.info(bs.format('X', minx, maxx))
        logging.info(bs.format('Y', miny, maxy))
        logging.info('plotting the cuts')
        out, ctx = plot.setup(ofn, minx, miny, maxx, maxy)
        plot.grid(ctx, minx, miny, maxx, maxy)
        plot.lines(ctx, cuts)
        plot.title(ctx, 'nc2pdf', ofn, maxy - miny)
        out.show_page()
        logging.info('writing output file "{}"'.format(ofn))
        out.finish()
        logging.info('file "{}" done.'.format(fn))
Esempio n. 10
0
def main(argv):
    """Main program for the readnc utility.

    :param argv: command line arguments
    """
    parser = argparse.ArgumentParser(description=__doc__)
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-L',
                       '--license',
                       action=LicenseAction,
                       nargs=0,
                       help="print the license")
    group.add_argument('-V',
                       '--version',
                       action='version',
                       version=__version__)
    parser.add_argument('files',
                        nargs='*',
                        help='one or more file names',
                        metavar='file')
    pv = parser.parse_args(argv)
    if not pv.files:
        parser.print_help()
        sys.exit(0)
    for fn in utils.xpand(pv.files):
        try:
            rd = gerbernc.Reader(fn)
        except IOError as e:
            utils.skip(e, fn)
            continue
        except ValueError as e:
            utils.skip(e, fn)
            continue
        # print the file
        for cmd, _ in rd:
            print(cmd)
Esempio n. 11
0
def main(argv):
    """Main program for the dxfgerber utility.

    :param argv: command line arguments
    """
    parser = argparse.ArgumentParser(description=__doc__)
    argtxt = """maximum distance between two points considered equal when
    searching for contours (defaults to 0.5 mm)"""
    parser.add_argument('-l',
                        '--limit',
                        nargs=1,
                        help=argtxt,
                        dest='limit',
                        metavar='F',
                        type=float,
                        default=0.5)
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-L',
                       '--license',
                       action=LicenseAction,
                       nargs=0,
                       help="print the license")
    group.add_argument('-V',
                       '--version',
                       action='version',
                       version=__version__)
    parser.add_argument('-v', '--verbose', dest='verbose', action="store_true")
    parser.add_argument('files',
                        nargs='*',
                        help='one or more file names',
                        metavar='file')
    pv = parser.parse_args(argv)
    msg = utils.Msg(pv.verbose)
    lim = pv.limit**2
    if not pv.files:
        parser.print_help()
        sys.exit(0)
    for f in utils.xpand(pv.files):
        msg.say('Starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.dxf', addenum='_mod')
            entities = dxf.reader(f)
        except Exception as ex:  # pylint: disable=W0703
            utils.skip(ex, f)
            continue
        num = len(entities)
        if num == 0:
            msg.say('No entities found!')
            continue
        if num > 1:
            msg.say('Contains {} entities'.format(num))
            bbe = [e.bbox for e in entities]
            bb = bbox.merge(bbe)
            msg.say('Gathering connected entities into contours')
            contours, rement = ent.findcontours(entities, lim)
            ncon = 'Found {} contours, {} remaining single entities'
            msg.say(ncon.format(len(contours), len(rement)))
            entities = contours + rement
            msg.say('Sorting entities')
            entities.sort(key=lambda e: (e.bbox.minx, e.bbox.miny))
        else:
            msg.say('Contains: 1 entity')
            bb = entities[0].bbox
        es = 'Original extents: {:.1f} ≤ x ≤ {:.1f} mm,' \
             ' {:.1f} ≤ y ≤ {:.1f} mm'
        msg.say(es.format(bb.minx, bb.maxx, bb.miny, bb.maxy))
        # move entities so that the bounding box begins at 0,0
        if bb.minx != 0 or bb.miny != 0:
            ms = 'Moving all entities by ({:.1f}, {:.1f}) mm'
            msg.say(ms.format(-bb.minx, -bb.miny))
            for e in entities:
                e.move(-bb.minx, -bb.miny)
        length = sum(e.length for e in entities)
        msg.say('Total length of entities: {:.0f} mm'.format(length))
        msg.say('Writing output to "{}"'.format(ofn))
        dxf.writer(ofn, 'dxfgerber', entities)
        msg.say('File "{}" done.'.format(f))
Esempio n. 12
0
def main(argv):
    """Main program for the dxf2nc utility.

    :param argv: command line arguments
    """
    parser = argparse.ArgumentParser(description=__doc__)
    argtxt = """maximum distance between two points considered equal when
    searching for contours (defaults to 0.5 mm)"""
    argtxt2 = u"""minimum rotation angle in degrees where the knife needs
    to be lifted to prevent breaking (defaults to 60°)"""
    argtxt4 = "assemble connected lines into contours (off by default)"
    parser.add_argument('-l',
                        '--limit',
                        help=argtxt,
                        dest='limit',
                        metavar='F',
                        type=float,
                        default=0.5)
    parser.add_argument('-a',
                        '--angle',
                        help=argtxt2,
                        dest='ang',
                        metavar='F',
                        type=float,
                        default=60)
    parser.add_argument('-c',
                        '--contours',
                        help=argtxt4,
                        dest='contours',
                        action="store_true")
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-L',
                       '--license',
                       action=LicenseAction,
                       nargs=0,
                       help="print the license")
    group.add_argument('-V',
                       '--version',
                       action='version',
                       version=__version__)
    parser.add_argument('-v', '--verbose', dest='verbose', action="store_true")
    parser.add_argument('files',
                        nargs='*',
                        help='one or more file names',
                        metavar='file')
    pv = parser.parse_args(argv)
    msg = utils.Msg(pv.verbose)
    lim = pv.limit**2
    if not pv.files:
        parser.print_help()
        sys.exit(0)
    for f in utils.xpand(pv.files):
        parts = []
        msg.say('Starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='')
            entities = dxf.reader(f)
        except Exception as ex:  # pylint: disable=W0703
            utils.skip(ex, f)
            continue
        # separate entities into parts according to their layers
        layers = {e.layer for e in entities}
        # Delete layer names that are not numbers
        layers = [la for la in layers if re.search('^[0-9]+', la)]
        layers.sort(key=lambda x: int(x))  # sort by integer value!
        # remove entities from unused layers.
        entities = [e for e in entities if e.layer in layers]
        num = len(entities)
        if num == 0:
            msg.say('No entities found!')
            continue
        if num > 1:
            msg.say('Contains {} entities'.format(num))
            bbe = [e.bbox for e in entities]
            bb = bbox.merge(bbe)
            es = 'Original extents: {:.1f} ≤ x ≤ {:.1f} mm,' \
                ' {:.1f} ≤ y ≤ {:.1f} mm'
            msg.say(es.format(bb.minx, bb.maxx, bb.miny, bb.maxy))
            for layer in layers:
                msg.say('Found layer: "{}"'.format(layer))
                le = [e for e in entities if e.layer == layer]
                if pv.contours:
                    msg.say('Gathering connected entities into contours')
                    contours, rement = ent.findcontours(le, lim)
                    for c in contours:
                        c.layer = layer
                    ncon = 'Found {} contours, {} remaining single entities'
                    msg.say(ncon.format(len(contours), len(rement)))
                    le = contours + rement
                msg.say('Sorting entities')
                le.sort(key=lambda e: (e.bbox.minx, e.bbox.miny))
                parts.append(le)
            msg.say('Sorting pieces')
            parts.sort(key=lambda p: bbox.merge([e.bbox for e in p]).minx)
        length = sum(e.length for e in entities)
        msg.say('Total length of entities: {:.0f} mm'.format(length))
        msg.say('Writing output to "{}"'.format(ofn))
        write_entities(ofn, parts, pv.ang)
        msg.say('File "{}" done.'.format(f))
Esempio n. 13
0
def main(argv):
    """Main program for the readdxf utility.

    :param argv: command line arguments
    """
    parser = argparse.ArgumentParser(description=__doc__)
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-L',
                       '--license',
                       action=LicenseAction,
                       nargs=0,
                       help="print the license")
    group.add_argument('-V',
                       '--version',
                       action='version',
                       version=__version__)
    parser.add_argument('-v', '--verbose', dest='verbose', action="store_true")
    parser.add_argument('files',
                        nargs='*',
                        help='one or more file names',
                        metavar='file')
    pv = parser.parse_args(argv)
    msg = utils.Msg(pv.verbose)
    if not pv.files:
        parser.print_help()
        sys.exit(0)
    offset = 40
    for f in utils.xpand(pv.files):
        msg.say('Starting file "{}"'.format(f))
        try:
            ofn = utils.outname(f, extension='.pdf', addenum='_dxf')
            entities = dxf.reader(f)
        except ValueError as ex:
            msg.say(str(ex))
            fns = "Cannot construct output filename. Skipping file '{}'."
            msg.say(fns.format(f))
            continue
        except IOError as ex:
            msg.say(str(ex))
            msg.say("Cannot open the file '{}'. Skipping it.".format(f))
            continue
        # Output
        num = len(entities)
        if num == 0:
            msg.say('No entities found!')
            continue
        if num > 1:
            msg.say('Contains {} entities'.format(num))
            bbx = [e.bbox for e in entities]
            bb = bbox.merge(bbx)
        else:
            msg.say('Contains: 1 entity')
            bb = entities[0].bbox
        w = bb.width + offset
        h = bb.height + offset
        xf = cairo.Matrix(xx=1.0, yy=-1.0, y0=h)
        out = cairo.PDFSurface(ofn, w, h)
        ctx = cairo.Context(out)
        ctx.set_matrix(xf)
        ctx.set_line_cap(cairo.LINE_CAP_ROUND)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_line_width(0.5)
        plot.plotgrid(ctx, w, h)
        colors = plot.crange(380, 650, len(entities))
        msg.say('Plotting the entities')
        plot.plotentities(ctx, (offset / 2 - bb.minx, offset / 2 - bb.miny),
                          entities, colors)
        # plot the color bar
        plot.plotcolorbar(ctx, w, len(entities), colors)
        # Plot the filename
        ctx.save()
        ctx.set_matrix(cairo.Matrix(xx=1.0, yy=1.0))
        ctx.select_font_face('Sans')
        fh = min(10, h / 40)
        ctx.set_source_rgb(0.0, 0.0, 0.0)
        ctx.set_font_size(fh)
        ctx.move_to(5, fh + 5)
        txt = ' '.join([
            'Produced by: dxf2pdf', __version__, 'on',
            str(datetime.datetime.now())[:-10]
        ])
        ctx.show_text(txt)
        ctx.stroke()
        fh = min(30, h / 20)
        ctx.move_to(5, h - 15)
        txt = 'File: "{}", last modified: {}'
        ctx.show_text(txt.format(f, time.ctime(os.path.getmtime(f))))
        ctx.stroke()
        ctx.restore()
        # Finish the page.
        out.show_page()
        out.finish()
        msg.say('File "{}" done.'.format(f))
Esempio n. 14
0
def main(argv):
    """Main program for the readdxf utility.

    :param argv: command line arguments
    """
    parser = argparse.ArgumentParser(description=__doc__)
    argtxt = """maximum distance between two points considered equal when
    searching for contours (defaults to 0.5 mm)"""
    parser.add_argument('-l',
                        '--limit',
                        nargs='?',
                        help=argtxt,
                        dest='limit',
                        type=float,
                        default=0.5)
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-L',
                       '--license',
                       action=LicenseAction,
                       nargs=0,
                       help="print the license")
    group.add_argument('-V',
                       '--version',
                       action='version',
                       version=__version__)
    parser.add_argument('files',
                        metavar='file',
                        nargs='*',
                        help='one or more file names')
    pv = parser.parse_args(argv)
    msg = utils.Msg()
    lim = pv.limit**2
    parts = []
    if not pv.files:
        parser.print_help()
        sys.exit(0)
    for f in utils.xpand(pv.files):
        try:
            entities = dxf.reader(f)
        except Exception as ex:
            utils.skip(ex, f)
            continue
        num = len(entities)
        msg.say('Filename: {}'.format(f))
        if num == 0:
            msg.say('No entities found!')
            sys.exit(1)
        if num > 1:
            msg.say('Contains: {} entities'.format(num))
            bbe = [e.bbox for e in entities]
            bb = bbox.merge(bbe)
            layers = {e.layer for e in entities}
            for layer in layers:
                msg.say('Layer: "{}"'.format(layer))
                le = [e for e in entities if e.layer == layer]
                contours, rement = ent.findcontours(le, lim)
                for c in contours:
                    c.layer = layer
                ncon = 'Found {} contours, {} remaining single entities'
                msg.say(ncon.format(len(contours), len(rement)))
                le = contours + rement
                le.sort(key=lambda x: x.bbox.minx)
                parts.append(le)
        else:
            msg.say('Contains: 1 entity')
            msg.say('Layer: "{}"'.format(entities[0].layer))
            bb = entities[0].bbox
            parts.append(entities)
        es = 'Extents: {:.1f} ≤ x ≤ {:.1f}, {:.1f} ≤ y ≤ {:.1f}'
        msg.say(es.format(bb.minx, bb.maxx, bb.miny, bb.maxy))
        length = sum(e.length for e in entities)
        msg.say('Total length of entities: {:.0f} mm'.format(length))
        for p in parts:
            msg.say('Layer: "{}"'.format(p[0].layer))
            for e in p:
                msg.say(e)
                if isinstance(e, ent.Contour):
                    for c in e.entities:
                        msg.say('..', c)
Esempio n. 15
0
def main(argv):
    """Main program for the nc2pdf utility.

    :argv: command line arguments
    """
    parser = argparse.ArgumentParser(description=__doc__)
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-L',
                       '--license',
                       action=LicenseAction,
                       nargs=0,
                       help="print the license")
    group.add_argument('-V',
                       '--version',
                       action='version',
                       version=__version__)
    parser.add_argument('-v', '--verbose', dest='verbose', action="store_true")
    parser.add_argument('files',
                        nargs='*',
                        help='one or more file names',
                        metavar='file')
    pv = parser.parse_args(argv)
    msg = utils.Msg(pv.verbose)
    offset = 40
    if not pv.files:
        parser.print_help()
        sys.exit(0)
    for fn in utils.xpand(pv.files):
        msg.say('Starting file "{}"'.format(fn))
        try:
            ofn = utils.outname(fn, extension='.pdf', addenum='_nc')
            rd = gerbernc.Reader(fn)
        except ValueError as e:
            msg.say(str(e))
            fns = "Cannot construct output filename. Skipping file '{}'."
            msg.say(fns.format(fn))
            continue
        except IOError as e:
            msg.say("Cannot read file: {}".format(e))
            msg.say("Skipping file '{}'".format(fn))
            continue
        cuts, xvals, yvals = getcuts(rd)
        cnt = len(cuts)
        msg.say('Got {} cuts'.format(cnt))
        minx, maxx = min(xvals), max(xvals)
        miny, maxy = min(yvals), max(yvals)
        bs = '{} range from {:.1f} mm to {:.1f} mm'
        msg.say(bs.format('X', minx, maxx))
        msg.say(bs.format('Y', miny, maxy))
        w = maxx - minx + offset
        h = maxy - miny + offset
        msg.say('Plotting the cuts')
        # Produce PDF output. Scale factor is 1 mm real =
        # 1 PostScript point in the PDF file
        xf = cairo.Matrix(xx=1.0, yy=-1.0, y0=h)
        out = cairo.PDFSurface(ofn, w, h)
        ctx = cairo.Context(out)
        ctx.set_matrix(xf)
        ctx.set_line_cap(cairo.LINE_CAP_ROUND)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.set_line_width(0.5)
        # Plot a grid in red
        plot.plotgrid(ctx, w, h)
        # Plot the cutlines
        colors = plot.crange(380, 650, cnt)
        # Plot in colors
        ctx.save()
        ctx.translate(offset / 2 - minx, offset / 2 - miny)
        for section, (r, g, b) in zip(cuts, colors):
            x1, y1 = section.pop(0)
            ctx.move_to(x1, y1)
            ctx.set_source_rgb(r / 255.0, g / 255.0, b / 255.0)
            for x2, y2 in section:
                ctx.line_to(x2, y2)
            ctx.stroke()
        ctx.restore()
        # plot the color bar
        plot.plotcolorbar(ctx, w, cnt, colors)
        # Plot the filename
        ctx.save()
        ctx.set_matrix(cairo.Matrix(xx=1.0, yy=1.0))
        ctx.select_font_face('Sans')
        fh = min(10, h / 40)
        ctx.set_source_rgb(0.0, 0.0, 0.0)
        ctx.set_font_size(fh)
        ctx.move_to(5, fh + 5)
        txt = ' '.join([
            'Produced by: nc2pdf', __version__, 'on',
            str(datetime.datetime.now())[:-10]
        ])
        ctx.show_text(txt)
        ctx.stroke()
        fh = min(30, h / 20)
        ctx.move_to(5, h - 15)
        txt = 'File: "{}", last modified: {}'
        ctx.show_text(txt.format(fn, time.ctime(os.path.getmtime(fn))))
        ctx.stroke()
        ctx.restore()
        # Finish the page.
        out.show_page()
        msg.say('Writing output file "{}"'.format(ofn))
        out.finish()
        msg.say('File "{}" done.'.format(fn))