def main(): """ The tendril-genpcbpricing script entry point. """ parser = _get_parser() args = parser.parse_args() force = args.force lazy = args.lazy if args.all: regenerate_all(force=force, lazy=lazy, dry_run=args.dry_run) else: if not len(args.projfolders): parser.print_help() for projfolder in args.projfolders: if not os.path.isabs(projfolder): projfolder = os.path.join(os.getcwd(), projfolder) projfolder = os.path.normpath(projfolder) if not in_directory(projfolder, PROJECTS_ROOT): logger.error( 'Provided directory does not seem to be under the ' 'tendril PROJECTS_ROOT. Skipping ' + projfolder) continue targets = [projfolder] if args.recurse: lprojects, lpcbs, lcards, lcard_reporoot = \ projects.get_projects(projfolder) targets.extend([lpcbs[x] for x in lpcbs.keys()]) for target in targets: try: if args.dry_run is False: csil.generate_pcb_pricing(target, forceregen=force, noregen=lazy) logger.info("Checked " + target) else: conffile.ConfigsFile(target) logger.info("Will check " + target) except NoGedaProjectError: # Make a guess. if os.path.split(target)[1] == 'configs.yaml': target == os.path.split(target)[0] if os.path.split(target)[1] == 'schematic': target == os.path.split(target)[0] try: if args.dry_run is False: csil.generate_pcb_pricing(target, forceregen=force, noregen=lazy) logger.info("Checked " + target) else: conffile.ConfigsFile(target) logger.info("Will check " + target) except NoGedaProjectError: logger.error("No gEDA Project found at " + target)
def get_projects(basefolder=None): lcards = {} lpcbs = {} lprojects = {} lcard_reporoot = {} lcable_projects = {} if basefolder is None: basefolder = PROJECTS_ROOT for root, dirs, files in os.walk(basefolder): dirs[:] = [d for d in dirs if not d.endswith('.git') and not d.endswith('.svn')] for d in dirs: if is_project_folder(os.path.join(root, d)): lprojects[ os.path.relpath(os.path.join(root, d), basefolder) ] = os.path.join(root, d) cf = conffile.ConfigsFile(os.path.join(root, d)) if cf.is_pcb: lpcbs[cf.pcbname] = os.path.join(root, d) else: lcable_projects[cf.cblname] = os.path.join(root, d) for config in cf.configuration_names: lcards[config] = os.path.join(root, d) lcard_reporoot[config] = \ os.path.relpath(os.path.join(root, d), basefolder) return lprojects, lpcbs, lcards, lcard_reporoot, lcable_projects
def gen_pcb_dxf(projfolder, force=False): """ Generates a DXF file of the PCB provided by the gEDA project. The pcb file is the one listed in the gEDA project file, and the pcbname is the one specified in the :mod:`tendril.gedaif.conffile.ConfigsFile`. This function does not use jinja2 and latex. It relies on :func:`tendril.connectors.geda.pcb.conv_pcb2dxf` instead. :param projfolder: The gEDA project folder. :type projfolder: str :param force: Regenerate even if up-to-date. :type force: bool :return: The output file path. .. rubric:: Paths * Output File : ``<projectfolder>/pcb/<pcbfile>.dxf`` * Source Files : The project's `.pcb` file. """ configfile = conffile.ConfigsFile(projfolder) gpf = projfile.GedaProjectFile(configfile.projectfolder) pcb_mtime = fsutils.get_file_mtime( os.path.join(configfile.projectfolder, 'pcb', gpf.pcbfile + '.pcb'), ) if pcb_mtime is None: logger.warning("PCB does not seem to exist for : " + projfolder) return docfolder = get_project_doc_folder(projfolder) dxffile = path.normpath(os.path.join(docfolder, os.pardir, configfile.pcbname + '.dxf')) bottom_dxffile = path.normpath(os.path.join(docfolder, os.pardir, configfile.pcbname + 'bottom.dxf')) outf_mtime = fsutils.get_file_mtime(dxffile, fs=refdoc_fs) if not force and outf_mtime is not None and outf_mtime > pcb_mtime: logger.debug('Skipping up-to-date ' + dxffile) return dxffile logger.info('Regenerating ' + dxffile + os.linesep + 'Last modified : ' + str(pcb_mtime) + '; Last Created : ' + str(outf_mtime)) workspace_folder = workspace_fs.getsyspath(path.dirname(dxffile)) workspace_fs.makedir(path.dirname(dxffile), recursive=True, allow_recreate=True) pcb.conv_pcb2dxf( os.path.join(configfile.projectfolder, 'pcb', gpf.pcbfile + '.pcb'), workspace_folder, configfile.pcbname ) copyfile(workspace_fs, dxffile, refdoc_fs, dxffile, overwrite=True) copyfile(workspace_fs, bottom_dxffile, refdoc_fs, bottom_dxffile, overwrite=True) return dxffile
def is_project_folder(folder): if folder in projects.values(): return True try: conffile.ConfigsFile(folder) except conffile.NoGedaProjectError: return False return True
def gen_cobom_csv(projfolder, namebase, force=False): """ Generates a CSV file in the :mod:`tendril.boms.outputbase.CompositeOutputBom` format, including the BOMs of the all the defined configurations of the project. This function uses a :mod:`csv.writer` instead of rendering a jinja2 template. It also generates configdocs for all the defined configurations of the project, using :func:`gen_confpdf`. :param projfolder: The gEDA project folder. :type projfolder: str :param namebase: The project name. :type namebase: str :param force: Regenerate even if up-to-date. :type force: bool :return: The output file path. .. rubric:: Paths * Output Files : ``<project_doc_folder>/confdocs/conf_boms.csv`` * Also triggers : :func:`gen_confpdf` for all listed configurations. * Source Files : The project's schematic folder. """ gpf = projfile.GedaProjectFile(projfolder) configfile = conffile.ConfigsFile(projfolder) sch_mtime = fsutils.get_folder_mtime(gpf.configsfile.schfolder) docfolder = get_project_doc_folder(projfolder) cobom_csv_path = path.join(docfolder, 'confdocs', 'conf-boms.csv') outf_mtime = fsutils.get_file_mtime(cobom_csv_path, fs=refdoc_fs) if not force and outf_mtime is not None and outf_mtime > sch_mtime: logger.debug('Skipping up-to-date ' + cobom_csv_path) return cobom_csv_path logger.info('Regenerating ' + cobom_csv_path + os.linesep + 'Last modified : ' + str(sch_mtime) + '; Last Created : ' + str(outf_mtime)) bomlist = [] for cfn in configfile.configuration_names: gen_confpdf(projfolder, cfn, namebase, force=force) lbom = boms_electronics.import_pcb(projfolder) lobom = lbom.create_output_bom(cfn) bomlist.append(lobom) cobom = boms_outputbase.CompositeOutputBom(bomlist) with refdoc_fs.open(cobom_csv_path, 'wb') as f: writer = csv.writer(f) writer.writerow(['device'] + [x.configname for x in cobom.descriptors]) for line in cobom.lines: writer.writerow([line.ident] + line.columns)
def validate_project(projectfolder, s=False, statuses=None): """ Report validation errors for all modules provided by the specified project. :param projectfolder: The path to the project folder. :param s: Whether to report sourcing errors as well. :param statuses: Criterion for module status to include the module. """ try: projectfolder = get_project_folder(projectfolder) cf = conffile.ConfigsFile(projectfolder) modulenames = cf.configuration_names for modulename in modulenames: validate_module(modulename, s=s, statuses=statuses) except conffile.NoGedaProjectError: logger.error("No gEDA Project found at " + projectfolder)
def regenerate_all(force=False, dry_run=False): """ Regenerates documentation for all projects :param force: Regenerate even for up-to-date projects. :param dry_run: Check only. Don't actually generate any documentation. """ for project in projects.projects: if dry_run: conffile.ConfigsFile(projects.projects[project]) logger.info("Will check " + projects.projects[project]) else: logger.info("Checking " + projects.projects[project]) gedaproject.generate_docs(projects.projects[project], force=force)
def get_img_list(projfolder, cardname=None): """ Returns a list of :class:`docstore.ExposedDocument` instances, pointing to the generated renders for the gEDA project or card specified by the parameters. Currently, the ``cardname`` parameter is ignored, since no configuration specific images are generated. :param projfolder: The gEDA project folder. :param cardname: The cardname. :return: list of :class:`ExposedDocument` """ configfile = conffile.ConfigsFile(projfolder) gpf = projfile.GedaProjectFile(configfile.projectfolder) namebase = configfile.pcbname project_doc_folder = get_project_doc_folder(projfolder) if not project_doc_folder: return [] project_img_folder = os.path.join(project_doc_folder, os.pardir, 'img') rval = [ ExposedDocument( namebase + ' PCB Top View', path.join(project_img_folder, gpf.pcbfile + '.top.png'), refdoc_fs ), ExposedDocument( namebase + ' PCB Bottom View', path.join(project_img_folder, gpf.pcbfile + '.bottom.png'), refdoc_fs ), ExposedDocument( namebase + ' PCB Layers', path.join(project_img_folder, gpf.pcbfile + '.devel.png'), refdoc_fs ) ] for img in rval: if not img.exists: rval.remove(img) return rval
def regenerate_all(force=False, lazy=False, dry_run=False): """ Regenerates PCB pricing information for all projects. :param force: Regenerate even for up-to-date projects. :param lazy: New projects only. Doesn't regenerate for projects with out-of-date pricing information :param dry_run: Check only. Don't actually generate any documentation. """ for project in projects.pcbs: if dry_run: conffile.ConfigsFile(projects.pcbs[project]) logger.info("Will check " + projects.pcbs[project]) else: logger.info("Checking " + projects.pcbs[project]) csil.generate_pcb_pricing(projects.pcbs[project], forceregen=force, noregen=lazy)
def generate_docs(projfolder, force=False): """ Generates all the docs for a specified gEDA project. :param projfolder: The gEDA project folder. :type projfolder: str :param force: Regenerate even if up-to-date. :type force: bool :return: The output file path. .. rubric:: Paths * Output File : ``<project_doc_folder>/confdocs/<configname>-doc.pdf`` * Source Files : The project's schematic folder. .. rubric:: Generated Documents * Master Doc, generated by :func:`gen_masterdoc` * Cobom CSV, generated by :func:`gen_cobom_csv` * PCB PDF, generated by :func:`gen_pcb_pdf` * PCB Gerber, generated by :func:`gen_pcb_gbr` * PCB DXF, generated by :func:`gen_pcb_dxf` * PCB Pricing, generated by :func:`gen_pcbpricing` """ configfile = conffile.ConfigsFile(projfolder) namebase = configfile.pcbname if namebase is None: try: namebase = configfile.rawconfig['cblname'] except KeyError: logger.error("Project does not have a known identifier. " "Skipping : " + projfolder) return gen_masterdoc(projfolder, namebase, force) gen_cobom_csv(projfolder, namebase, force) if configfile.is_pcb: gen_pcb_pdf(projfolder, force) gen_pcb_gbr(projfolder, force) gen_pcb_dxf(projfolder, force) gen_pcbpricing(projfolder, namebase, force)
def get_module_config(modulename): if modulename not in cards.keys(): raise KeyError("Couldn't find {0} in the library!".format(modulename)) gcf = conffile.ConfigsFile(cards[modulename]) return gcf
""" This file is part of tendril See the COPYING, README, and INSTALL files for more information """ import os import csv from tendril.entityhub import projects from tendril.gedaif import conffile from tendril.utils.config import INSTANCE_ROOT if __name__ == '__main__': fpath = os.path.join(INSTANCE_ROOT, 'scratch', 'costing-summary-all.csv') with open(fpath, 'w') as f: writer = csv.writer(f) writer.writerow(["Card", "Indicative Cost"]) for card, cardfolder in sorted(projects.cards.iteritems()): cfg = conffile.ConfigsFile(cardfolder) for configuration in cfg.configdata['configurations']: if configuration['configname'] == card: carddesc = configuration['desc'] if carddesc is None: carddesc = '' cost = projects.get_card_indicative_cost(card) if cost is not None: writer.writerow([card, round(cost), carddesc]) else: writer.writerow([card, None, carddesc])
def get_docs_list(projfolder, cardname=None): """ Returns a list of :class:`docstore.ExposedDocument` instances, pointing to the documentation linked to the gEDA project or card specified by the parameters. If the ``cardname`` is not specified, the documents linked to the base PCB only are returned. If the ``cardname`` is specified, the documents defining the specific configuration only are returned. :param projfolder: The gEDA project folder. :param cardname: The cardname. :return: list of :class:`ExposedDocument` """ configfile = conffile.ConfigsFile(projfolder) namebase = configfile.pcbname is_cable = False if namebase is None: try: namebase = configfile.rawconfig['cblname'] is_cable = True except KeyError: logger.error("Project does not have a known identifier. " "Skipping : " + projfolder) return project_doc_folder = get_project_doc_folder(projfolder) if not project_doc_folder: return [] if not cardname: # Get all docs linked to the project rval = [ ExposedDocument( 'Project Master Doc', path.join(project_doc_folder, namebase + '-masterdoc.pdf'), refdoc_fs), ExposedDocument( namebase + ' Schematic (Full)', path.join(project_doc_folder, namebase + '-schematic.pdf'), refdoc_fs), ExposedDocument( 'Composite Bom (All Configs)', path.join(project_doc_folder, 'confdocs', 'conf-boms.csv'), refdoc_fs), ] if is_cable: return rval gpf = projfile.GedaProjectFile(configfile.projectfolder) rval.extend([ ExposedDocument( namebase + ' PCB Layers', path.join(project_doc_folder, namebase + '-pcb.pdf'), refdoc_fs), ExposedDocument( namebase + ' PCB Pricing', path.join(project_doc_folder, namebase + '-pricing.pdf'), refdoc_fs), ExposedDocument( namebase + ' PCB DXF', path.join(project_doc_folder, os.pardir, configfile.pcbname + '.dxf'), refdoc_fs), ExposedDocument( namebase + ' PCB Gerber', path.join(project_doc_folder, os.pardir, gpf.pcbfile + '-gerber.zip'), refdoc_fs), ]) return rval else: cardname = cardname.strip() rval = [ ExposedDocument( cardname + ' Doc', path.join(project_doc_folder, 'confdocs', cardname + '.pdf'), refdoc_fs), ExposedDocument( cardname + ' Reference BOM', path.join(project_doc_folder, 'confdocs', cardname + '-bom.pdf'), refdoc_fs), ExposedDocument( cardname + ' Schematic (Configured)', path.join(project_doc_folder, 'confdocs', namebase + '-conf-schematic.pdf'), refdoc_fs), ExposedDocument( cardname + ' Schematic (Full)', path.join(project_doc_folder, namebase + '-schematic.pdf'), refdoc_fs), ExposedDocument( 'Composite Bom (All Configs)', path.join(project_doc_folder, 'confdocs', 'conf-boms.csv'), refdoc_fs), ExposedDocument( 'Project Master Doc', path.join(project_doc_folder, namebase + '-masterdoc.pdf'), refdoc_fs), ] return rval
def gen_pcb_gbr(projfolder, force=False): """ Generates gerber files for the PCB provided by the gEDA project, and also creates a ``zip`` file of the generated gerbers. The pcbfile is the one listed in the gEDA project file, and the pcbname is the one specified in the :mod:`tendril.gedaif.conffile.ConfigsFile`. This function does not use jinja2 and latex. It relies on :func:`tendril.gedaif.pcb.conv_pcb2gbr` instead. :param projfolder: The gEDA project folder. :type projfolder: str :param force: Regenerate even if up-to-date. :type force: bool :return: The output file path. .. rubric:: Paths * Output Files : ``<project_doc_folder>/../gerber/*`` * Output Zip File : ``<project_doc_folder>/../<pcbfile>-gerber.zip`` * Source Files : The project's `.pcb` file. """ configfile = conffile.ConfigsFile(projfolder) gpf = projfile.GedaProjectFile(configfile.projectfolder) pcb_mtime = fsutils.get_file_mtime( os.path.join(configfile.projectfolder, 'pcb', gpf.pcbfile + '.pcb')) if pcb_mtime is None: logger.warning("PCB does not seem to exist for : " + projfolder) return docfolder = get_project_doc_folder(projfolder) imgfolder = os.path.join(docfolder, os.pardir, 'img') gbrfolder = os.path.join(docfolder, os.pardir, 'gerber') outf_mtime = None if not refdoc_fs.exists(gbrfolder): refdoc_fs.makedir(gbrfolder) else: outf_mtime = fsutils.get_folder_mtime(gbrfolder, fs=refdoc_fs) if not refdoc_fs.exists(imgfolder): refdoc_fs.makedir(imgfolder) if not force and outf_mtime is not None and outf_mtime > pcb_mtime: logger.debug('Skipping up-to-date ' + gbrfolder) return gbrfolder logger.info('Regenerating ' + gbrfolder + os.linesep + 'Last modified : ' + str(pcb_mtime) + '; Last Created : ' + str(outf_mtime)) rf = refdoc_fs.listdir(gbrfolder, files_only=True, full=True) for f in rf: refdoc_fs.remove(f) workspace_folder = workspace_fs.getsyspath(gbrfolder) workspace_fs.makedir(gbrfolder, recursive=True, allow_recreate=True) gbrfolder = pcb.conv_pcb2gbr( os.path.join(configfile.projectfolder, 'pcb', gpf.pcbfile + '.pcb'), workspace_folder) workspace_fs.makedir(imgfolder, recursive=True, allow_recreate=True) img_workspace_folder = workspace_fs.getsyspath(imgfolder) gen_pcb_img(gbrfolder, outfolder=img_workspace_folder, outfname=gpf.pcbfile, force=False) for f in os.listdir(img_workspace_folder): fpath = os.path.relpath(os.path.join(img_workspace_folder, f), workspace_fs.getsyspath('/')) copyfile(workspace_fs, fpath, refdoc_fs, fpath, overwrite=True) zfile = os.path.join(workspace_folder, os.pardir, gpf.pcbfile + '-gerber.zip') fsutils.zipdir(gbrfolder, zfile) for f in os.listdir(workspace_folder): fpath = os.path.relpath(os.path.join(workspace_folder, f), workspace_fs.getsyspath('/')) copyfile(workspace_fs, fpath, refdoc_fs, fpath, overwrite=True) zfpath = os.path.relpath(os.path.join(workspace_folder, zfile), workspace_fs.getsyspath('/')) copyfile(workspace_fs, zfpath, refdoc_fs, zfpath, overwrite=True) return gbrfolder
def gen_schpdf(projfolder, namebase, configname=None, force=False): """ Generates a PDF file of all the project schematics listed in the gEDA project file. This function does not use jinja2 and latex. It relies on :func:`tendril.gedaif.gschem.conv_gsch2pdf` instead. :param projfolder: The gEDA project folder. :type projfolder: str :param namebase: The project name. :type namebase: str :param force: Regenerate even if up-to-date. :type force: bool :return: The output file path. .. rubric:: Paths * Output File : ``<project_doc_folder>/<namebase>-schematic.pdf`` * Source Files : The project's schematic folder. """ gpf = projfile.GedaProjectFile(projfolder) sch_mtime = fsutils.get_folder_mtime(gpf.schfolder) configfile = conffile.ConfigsFile(projfolder) docfolder = get_project_doc_folder(projfolder) # TODO Consider converting all configurations in one go instead? if configname is None: schpdfpath = path.join(docfolder, namebase + '-schematic.pdf') else: docfolder = path.join(docfolder, 'confdocs') pdfname = configname + '-conf-schematic.pdf' schpdfpath = path.join(docfolder, pdfname) outf_mtime = fsutils.get_file_mtime(schpdfpath, fs=refdoc_fs) if not force and outf_mtime is not None and outf_mtime > sch_mtime: logger.debug('Skipping up-to-date ' + schpdfpath) return schpdfpath logger.info('Regenerating ' + schpdfpath + os.linesep + 'Last modified : ' + str(sch_mtime) + '; Last Created : ' + str(outf_mtime)) if configfile.rawconfig is not None: workspace_outpath = workspace_fs.getsyspath(schpdfpath) workspace_folder = workspace_fs.getsyspath(docfolder) workspace_fs.makedir(docfolder, recursive=True, allow_recreate=True) pdffiles = [] obom = None if configname is not None: tfolder = path.join(docfolder, configname) workspace_tfolder = workspace_fs.getsyspath(tfolder) workspace_fs.makedir(tfolder, recursive=False, allow_recreate=True) bom = boms_electronics.import_pcb(projfolder) bom.configure_motifs(configname) obom = bom.create_output_bom(configname) for schematic in gpf.schfiles: schfile = os.path.normpath(projfolder + '/schematic/' + schematic) if configname is not None: tschfile = path.join(workspace_tfolder, schematic) gschem.rewrite_schematic(schfile, obom, gpf, tschfile) pdffile = gschem.conv_gsch2pdf(tschfile, workspace_tfolder) os.remove(tschfile) else: pdffile = gschem.conv_gsch2pdf(schfile, workspace_folder) pdffiles.append(pdffile) pdf.merge_pdf(pdffiles, workspace_outpath) for pdffile in pdffiles: os.remove(pdffile) copyfile(workspace_fs, schpdfpath, refdoc_fs, schpdfpath, overwrite=True) return schpdfpath