From 8679ab6d304dd646022c262371e0b12bb024d48b Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Sun, 25 Jan 2015 10:02:03 -0800 Subject: [PATCH] Begin refactoring pybom to use a better interface - Added generic BOMMatcher with Octopart implementation - Moved caching to a higher layer - Made the interface compatible with typedargs Update typedargs with class containing type_system code - Also expand unit test coverage of type system - Add default string formatting for exceptions Update for more consistent use of new type_system Add initial support for complex types - Add a map type that can take any two other types as key and value types Add typed return values to type system Update bom_matcher to use typed return value Update typedargs to export return_type Update typesystem to allow complex types in param and return_type Fix return value printing Begin creating provider-agnostic part information Add list complex type to typedargs Finish refactoring octopart code - Start refactoring board extraction and pricing code Refactored pybom - Added momo friendly CircuitBoard object - Simplified generation of pricing models - Consolidated code for getting bom information rename bom_lines() to capture what it does Fix improper location of config file Start finishign up pcbtool Fix help display in momo tool. Remove dirspec dependency and replace with in-house alternative Update README to remove mention of dirspec Update typedargs with class containing type_system code - Also expand unit test coverage of type system - Add default string formatting for exceptions Fix improper location of config file Update for more consistent use of new type_system Add initial support for complex types - Add a map type that can take any two other types as key and value types Update typedargs to export return_type Update typesystem to allow complex types in param and return_type Fix help display in momo tool. Add bool type Don't require a variant when there is only one option Add test for circuitboard - start work on partcache, moving from ZODB to integrated sqlite Recreate cache functionality on top of sqlite Still need to integrate into bommatching Start making CircuitBoard agnostic of the generating program. Begin the great refactor to make CircuitBoard agnostic of the type of board file that it was generated from. This does not work yet but is on its way. Rename pybom to pcb Working version of refactored CircuitBoard Attribute updating works now on eagle board files Integrate part cache into bom matching object Working version of part caching Working partcache integrated into bom_matcher Working part caching and unit tests for part matching Rename unit tests from pybom to pcb to match new package name Update TODO status Add support for updating metadata in boards and writing BOM lines Fix unit tests and partcache to use global defaults Fix unit tests for pcb tools Fix refactor copy/paste error and remove unnecessary setup dependencies Working excel exporting of BOMs Pump version 1.1.0 since we added a new bom feature Add unit test to exercise excel exporting Fix bug handling empty manufacturers Fix processing of assembly variants in eagle and add tests for it Update CircuitBoard to support pricing BOMs - Also fix issue with unicode display inside physical parts objects not being converted correctly to ascii when using __str__ - Updated type system to include price objects. Update momo to only display traceback information during an APIError Starting work on integrating fab and assy drawing generation Additional work on fab generation Update momo tool to lazily load modules when possible This cuts the startup time in half and will be very important going forward to make sure we keep the tool responsible and speedy Working version of production file generation Add unit test for building production files - note: EAGLE needs brd files to be named .brd for CAM processing to work Remove non conforming exception and update TODO docs Update production generation to zip up result Fix libedit logic to only apply on mac os x and not on Windows too Start autofinding EAGLE on various OS Add eagle autofind on mac os x in standard location Update version number Update modtool to show progress bars --- pymomo/__version__.py | 2 +- pymomo/commander/commands/rpc.py | 1 - pymomo/config/pcb/eagle.json | 75 + pymomo/{pybom => cparser}/__init__.py | 0 pymomo/exceptions.py | 35 +- pymomo/pcb/__init__.py | 22 + pymomo/pcb/board.py | 775 ++++ pymomo/pcb/bom_matcher.py | 364 ++ pymomo/pcb/eagle/__init__.py | 0 pymomo/pcb/eagle/board.py | 292 ++ pymomo/pcb/eagle/part.py | 186 + pymomo/pcb/match_engines/__init__.py | 4 + pymomo/pcb/match_engines/cache_only.py | 17 + pymomo/pcb/match_engines/octopart.py | 137 + pymomo/pcb/package.py | 159 + pymomo/pcb/partcache.py | 123 + pymomo/{pybom => pcb}/pricing.py | 28 +- pymomo/pcb/production.py | 125 + pymomo/{pybom => pcb}/reference.py | 4 +- pymomo/pcb/types/__init__.py | 7 + pymomo/pcb/types/bom_line.py | 62 + pymomo/pcb/types/logical_part.py | 109 + pymomo/pcb/types/part_offer.py | 50 + pymomo/pcb/types/physical_part.py | 82 + pymomo/pcb/types/price.py | 15 + .../prices.py => pcb/types/price_list.py} | 33 +- pymomo/pcb/types/unit_price.py | 26 + pymomo/pybom/board.py | 415 --- pymomo/pybom/octopart/__init__.py | 1 - pymomo/pybom/octopart/identifier.py | 27 - pymomo/pybom/octopart/octopart.py | 94 - pymomo/pybom/octopart/partcache.py | 60 - pymomo/pybom/octopart/partoffer.py | 30 - pymomo/pybom/octopart/physicalpart.py | 23 - pymomo/pybom/octopart/utilities.py | 5 - pymomo/pybom/package.py | 18 - pymomo/pybom/part.py | 159 - pymomo/pyeagle.py | 181 - pymomo/scripts/modtool.py | 2 + pymomo/scripts/momo.py | 28 +- pymomo/scripts/pcbtool.py | 28 +- pymomo/syslog/logdefinition.py | 4 +- pymomo/syslog/syslog.py | 6 +- pymomo/utilities/config.py | 3 +- pymomo/utilities/console.py | 24 +- pymomo/utilities/formatting.py | 19 + pymomo/utilities/typedargs/__init__.py | 4 +- pymomo/utilities/typedargs/annotate.py | 55 +- pymomo/utilities/typedargs/shell.py | 29 +- pymomo/utilities/typedargs/typeinfo.py | 322 +- pymomo/utilities/typedargs/types/__init__.py | 6 +- pymomo/utilities/typedargs/types/bool.py | 20 + pymomo/utilities/typedargs/types/integer.py | 3 + pymomo/utilities/typedargs/types/list.py | 30 + pymomo/utilities/typedargs/types/map.py | 31 + pymomo/utilities/typedargs/types/path.py | 5 +- pymomo/utilities/typedargs/types/string.py | 13 +- setup.py | 17 +- test/test_pcb/__init__.py | 0 test/test_pcb/eagle/assyvars.brd | 1328 +++++++ test/test_pcb/eagle/blank_board.brd | 244 ++ test/test_pcb/eagle/controller_complete.brd | 3164 +++++++++++++++++ .../test_pcb/eagle/controller_dist_distpn.brd | 3162 ++++++++++++++++ .../eagle/controller_missing_attrs.brd | 3161 ++++++++++++++++ test/test_pcb/eagle/controller_nodist.brd | 3161 ++++++++++++++++ test/test_pcb/eagle/pcb_part_cache.db | Bin 0 -> 147456 bytes test/test_pcb/test_bommatch.py | 105 + test/test_pcb/test_cache.py | 56 + test/test_pcb/test_circuitboard.py | 23 + test/test_pcb/test_eagleparsing.py | 103 + test/test_pcb/test_production.py | 76 + .../extra_type_package/__init__.py | 2 +- .../extra_type_package/extra_type.py | 7 + test/test_typedargs/extra_types.py | 8 +- test/test_typedargs/test_builtin_types.py | 72 + test/test_typedargs/test_complex_types.py | 32 + test/test_typedargs/test_injection.py | 23 +- test/test_typedargs/test_returntypes.py | 24 + test/test_utilities/test_settings_dir.py | 1 - 79 files changed, 17933 insertions(+), 1214 deletions(-) create mode 100644 pymomo/config/pcb/eagle.json rename pymomo/{pybom => cparser}/__init__.py (100%) create mode 100644 pymomo/pcb/__init__.py create mode 100644 pymomo/pcb/board.py create mode 100644 pymomo/pcb/bom_matcher.py create mode 100644 pymomo/pcb/eagle/__init__.py create mode 100644 pymomo/pcb/eagle/board.py create mode 100644 pymomo/pcb/eagle/part.py create mode 100644 pymomo/pcb/match_engines/__init__.py create mode 100644 pymomo/pcb/match_engines/cache_only.py create mode 100644 pymomo/pcb/match_engines/octopart.py create mode 100644 pymomo/pcb/package.py create mode 100644 pymomo/pcb/partcache.py rename pymomo/{pybom => pcb}/pricing.py (52%) create mode 100644 pymomo/pcb/production.py rename pymomo/{pybom => pcb}/reference.py (95%) create mode 100644 pymomo/pcb/types/__init__.py create mode 100644 pymomo/pcb/types/bom_line.py create mode 100644 pymomo/pcb/types/logical_part.py create mode 100644 pymomo/pcb/types/part_offer.py create mode 100644 pymomo/pcb/types/physical_part.py create mode 100644 pymomo/pcb/types/price.py rename pymomo/{pybom/octopart/prices.py => pcb/types/price_list.py} (52%) create mode 100644 pymomo/pcb/types/unit_price.py delete mode 100644 pymomo/pybom/board.py delete mode 100644 pymomo/pybom/octopart/__init__.py delete mode 100644 pymomo/pybom/octopart/identifier.py delete mode 100644 pymomo/pybom/octopart/octopart.py delete mode 100644 pymomo/pybom/octopart/partcache.py delete mode 100644 pymomo/pybom/octopart/partoffer.py delete mode 100644 pymomo/pybom/octopart/physicalpart.py delete mode 100644 pymomo/pybom/octopart/utilities.py delete mode 100644 pymomo/pybom/package.py delete mode 100644 pymomo/pybom/part.py delete mode 100644 pymomo/pyeagle.py create mode 100644 pymomo/utilities/formatting.py create mode 100644 pymomo/utilities/typedargs/types/bool.py create mode 100644 pymomo/utilities/typedargs/types/list.py create mode 100644 pymomo/utilities/typedargs/types/map.py create mode 100644 test/test_pcb/__init__.py create mode 100644 test/test_pcb/eagle/assyvars.brd create mode 100644 test/test_pcb/eagle/blank_board.brd create mode 100644 test/test_pcb/eagle/controller_complete.brd create mode 100644 test/test_pcb/eagle/controller_dist_distpn.brd create mode 100644 test/test_pcb/eagle/controller_missing_attrs.brd create mode 100644 test/test_pcb/eagle/controller_nodist.brd create mode 100644 test/test_pcb/eagle/pcb_part_cache.db create mode 100644 test/test_pcb/test_bommatch.py create mode 100644 test/test_pcb/test_cache.py create mode 100644 test/test_pcb/test_circuitboard.py create mode 100644 test/test_pcb/test_eagleparsing.py create mode 100644 test/test_pcb/test_production.py create mode 100644 test/test_typedargs/extra_type_package/extra_type.py create mode 100644 test/test_typedargs/test_builtin_types.py create mode 100644 test/test_typedargs/test_complex_types.py create mode 100644 test/test_typedargs/test_returntypes.py diff --git a/pymomo/__version__.py b/pymomo/__version__.py index 64a2795..545d07d 100644 --- a/pymomo/__version__.py +++ b/pymomo/__version__.py @@ -1 +1 @@ -__version__ = "1.0.10" \ No newline at end of file +__version__ = "1.1.1" \ No newline at end of file diff --git a/pymomo/commander/commands/rpc.py b/pymomo/commander/commands/rpc.py index 63caa03..22767cc 100644 --- a/pymomo/commander/commands/rpc.py +++ b/pymomo/commander/commands/rpc.py @@ -1,4 +1,3 @@ - from command import Command from pymomo.commander.exceptions import * import base64 diff --git a/pymomo/config/pcb/eagle.json b/pymomo/config/pcb/eagle.json new file mode 100644 index 0000000..13b92e5 --- /dev/null +++ b/pymomo/config/pcb/eagle.json @@ -0,0 +1,75 @@ +{ + "templates": + { + "two_layer": + { + "layers": + { + "Top Solderpaste Layer": + { + "extension": "crm", + "program_layers": ["tCream"], + "type": "gerber", + "remove": "gpi" + }, + + "Top Silkscreen Layer": + { + "extension": "plc", + "program_layers": ["Dimension", "tPlace", "tNames"], + "type": "gerber", + "remove": "gpi" + }, + + "Top Copper Layer": + { + "extension": "cmp", + "program_layers": ["Top", "Pads", "Vias"], + "type": "gerber", + "remove": "gpi" + }, + + "Bottom Copper Layer": + { + "extension": "sol", + "program_layers": ["Bottom", "Pads", "Vias"], + "type": "gerber", + "remove": "gpi" + }, + + "Top Soldermask Layer": + { + "extension": "stc", + "program_layers": ["tStop"], + "type": "gerber", + "remove": "gpi" + }, + + "Bottom Soldermask Layer": + { + "extension": "sts", + "program_layers": ["bStop"], + "type": "gerber", + "remove": "gpi" + }, + + "Drill Information": + { + "extension": "drd", + "program_layers": ["Drills", "Holes"], + "type": "excellon", + "remove": "dri" + } + }, + + "assembly": + { + "program_layers": ["tPlace", "tNames", "tDocu", "Document", "Reference","Dimension"], + "type": "drawing", + "extension": "ps" + }, + + "description": "Two layer board, Top silkscreen only, Soldermask on both sides" + } + } +} \ No newline at end of file diff --git a/pymomo/pybom/__init__.py b/pymomo/cparser/__init__.py similarity index 100% rename from pymomo/pybom/__init__.py rename to pymomo/cparser/__init__.py diff --git a/pymomo/exceptions.py b/pymomo/exceptions.py index 925c5d6..5d2e253 100644 --- a/pymomo/exceptions.py +++ b/pymomo/exceptions.py @@ -31,6 +31,10 @@ def format_msg(self): return msg + def __str__(self): + msg = self.format() + return msg + class ValidationError(MoMoException): """ API routines can impose validation criteria on their arguments in @@ -78,11 +82,22 @@ class ArgumentError(MoMoException): pass +class DataError(MoMoException): + """ + The method relied on data pass in by the user and the data was invalid. + + This could be because a file was the wrong type or because a data provider + returned an unexpected result. The parameters passed with this exception + provide more detail on what occurred and where. + """ + + pass + class InternalError(MoMoException): """ The method could not be completed with the user input passed for an unexpected reason. This does not signify a bug in the API - method code. More details should be passed in the arguments + method code. More details should be passed in the arguments. """ pass @@ -109,4 +124,22 @@ class BuildError(MoMoException): that something is misconfigured. """ + pass + +class TypeSystemError(MoMoException): + """ + There was an error with the MoMo type system. This can be due to improperly + specifying an unknown type or because the required type was not properly loaded + from an external module before a function that used that type was needed. + """ + + pass + +class EnvironmentError(MoMoException): + """ + The environment is not properly configured for the MoMo API command that was called. + This can be because a required program was not installed or accessible or because + a required environment variable was not defined. + """ + pass \ No newline at end of file diff --git a/pymomo/pcb/__init__.py b/pymomo/pcb/__init__.py new file mode 100644 index 0000000..8d57553 --- /dev/null +++ b/pymomo/pcb/__init__.py @@ -0,0 +1,22 @@ +""" +A package for managing bills of materials and pcb fabrication + +Methods and objects for creating and pricing bills of materials +as well as automatically generating gerber files for pcb fabrication. + +- The CircuitBoard object provides a way to generate BOMs and production + files for pcb fabrication from ECAD files +- various BOM pricing and matching engines like OctopartMatcher allow you + to see how much your BOM would cost in different quantities. +""" + +_name_ = "pcb" + + +#Add in required types that we need +import types +import pymomo.utilities.typedargs +pymomo.utilities.typedargs.type_system.load_type_module(types) + +from match_engines import * +from board import CircuitBoard diff --git a/pymomo/pcb/board.py b/pymomo/pcb/board.py new file mode 100644 index 0000000..7041aa2 --- /dev/null +++ b/pymomo/pcb/board.py @@ -0,0 +1,775 @@ +#TODO: +# - add compare_descriptions function that compares the descriptions +# currently associated with each bom line with those returned from +# an online database side by side so that you can make sure that +# you're matching the right part. +# - Make updating metadata and attributes reflected realtime in board +# without reloading the board. + +from pymomo.utilities.typedargs import * +from pymomo.exceptions import * +from eagle.board import EagleBoard +from pricing import OfferRequirements +import match_engines +from bom_matcher import BOMMatcher +from decimal import Decimal +from production import ProductionFileGenerator +import itertools + +#List of all of the types of board file we know how to deal with +board_types = {} +board_types['eagle'] = EagleBoard + +@context() +class CircuitBoard: + """ + A CircuitBoard that can be used to create a BOM or produce gerbers + + The price of the BOM can be looked up using the Octopart API and + complex rules can be specifed to decide who to buy the parts from. + A CircuitBoard can be created from an annotated pcb CAD file from any + supported CAD program. Currently we support EAGLE only but more will + be added in the future. + """ + + DefaultEngine = match_engines.OctopartMatcher + + @param("file", "path", "readable", desc="PCB board file to load") + @param("type", "string", ("list", "eagle"), desc="type of file [eagle is only currently supported option]") + def __init__(self, file, type='eagle'): + self.board = board_types[type](file) + self.clear_pricemodel() + + self._match_engine = CircuitBoard.DefaultEngine + + self.errors = [] + self.warnings = [] + + #Process board into variants so that each variant is a list of unique parts + self.variants = {name: self._process_variant(parts) for name,parts in self.board.data['variants'].iteritems()} + + #Copy all of the attributes from the board into this object, logging errors for any missing data + self._set_attribute('company', self.board.data) + self._set_attribute('part', self.board.data) + self._set_attribute('width', self.board.data) + self._set_attribute('height', self.board.data) + self._set_attribute('units', self.board.data) + self._set_attribute('revision', self.board.data) + self._set_attribute('no_populate', self.board.data, default=[]) + self._set_attribute('unknown_parts', self.board.data, default=[]) + self._set_attribute('fab_template', self.board.data) + self._set_attribute('fab_engine', self.board.data) + + #Make sure there are no parts that we don't have information about + for x in self.unknown_parts: + self._add_error(x, "Does not have required part information like distributor or manufacturer part numbers") + + #Make sure all parts have enough information to be matchable and placeable + for variant, lines in self.variants.iteritems(): + for line in lines: + for part in line: + if not part.matchable(): + self._add_warning(part.name, "Does not have enough information to be matchable in variant %s" % variant) + if not part.placeable(): + self._add_warning(part.name, "Does not have enough information to be placeble in variant %s" % variant) + + #Build a set containing all of the parts in all variants so we know what components could be populated but should not be + self.all_parts = frozenset([x.name for x in self._iterate_all_parts()]) + self._build_lookup_table() + + @return_type("list(string)") + @param("variant", "string", desc="Assembly variant to consider") + def nonpopulated_parts(self, variant=None): + """ + Fetch the reference identifiers of all parts not populated in this variant. + + The list of strings that is returned does not include parts that are not populated + in any assembly variant since those are considered to be virtual parts that should + be ignored. They are listed in the instance attribute no_populate. + """ + parts = frozenset([x.name for x in self._iterate_parts(variant)]) + + nopop = self.all_parts - parts + return [x for x in nopop] + + @return_type("bool") + def is_clean(self): + """ + Return true if there are no errors or warnings about this board. + """ + + return len(self.errors) == 0 and len(self.warnings) == 0 + + @return_type("logical_part") + @param("reference", "string", "not_empty", desc="Reference identifier to find") + @param("variant", "string", desc="Assembly variant to search") + def find(self, reference, variant=None): + """ + Find a part on the board using its reference identifier. + + If the same reference identifier refers to different parts in different + assembly variants, then you must specify which one you want by passing + the optional variant name. + """ + + if reference not in self.part_index: + raise ArgumentError("reference id did not exist", reference=reference) + + matches = self.part_index[reference] + if len(matches) > 1 and variant is None: + raise ArgumentError("reference id exists in multiple assembly variants, you must specify one", + reference=reference, variants=matches.keys()) + + if len(matches) > 1: + if variant in matches: + return matches[variant] + else: + raise ArgumentError("reference id is not populated in specified assembly variant", + reference=reference, variants=matches.keys(), specified_variant=variant) + + return matches.values()[0] + + @return_type("list(physical_part)") + @param("reference", "string", "not_empty", desc="Reference identifier to find") + @param("variant", "string", desc="Assembly variant to search") + def lookup(self, reference, variant=None): + """ + Use the currently selected matching system to look up a component. + + The data returned can contain realtime price and stock information, + a list of distributors that carry the part and other details depending + on the matching system used. + """ + + matcher = self._get_matcher() + part = self.find(reference, variant) + + matcher.add_part(part) + matcher.match_all() + + if not matcher.is_matched(part.name): + raise DataError("part could not be matched") + + return matcher.match_info(part.name) + + @return_type("bool") + @param("variant", "string", desc="Assembly variant to search") + def match_status(self, variant=None): + """ + Return whether all parts are matched with a component database + """ + + results = {} + matcher = self._get_matcher() + + for part,num in self._iterate_lines(variant): + if not part.matchable(): + return False + + matcher.add_part(part) + + matcher.match_all() + return matcher.all_matched() + + @return_type("map(string, string)") + @param("variant", "string", desc="Assembly variant to search") + def match_details(self, variant=None): + """ + Attempt to match all BOM components with a component database. + + Return a dictionary mapping reference numbers to matching statuses. + This function will tell you if any of your parts were not successfully + matched or did not have enough metadata to match to a unique component. + """ + + results = {} + matcher = self._get_matcher() + + for part,num in self._iterate_lines(variant): + if not part.matchable(): + results[part.name] = 'Insufficient metadata to match' + continue + + matcher.add_part(part) + + matcher.match_all() + match_results = matcher.match_details() + + return dict(itertools.chain(results.iteritems(), match_results.iteritems())) + + @param("variant", "string", desc="Assembly variant to update") + @param("missing_only", "bool", desc="Only update missing metadata attributes") + def update_all_metadata(self, variant=None, missing=True): + """ + Update the entire board with additional metadata from the matching engine. + + This can be used to automatically fill in descriptions, manufacturers and mpns + if you only have a distributor's part number, for example. If missing is True, + the default, only missing data will be added, entered data will not be overwritten. + + If variant is None, all assembly variants are updated. If variant is passed, then + only a single variant is updated. + """ + + if isinstance(variant, basestring): + update_vars = [self._assure_valid_variant(variant)] + else: + update_vars = self.variants.keys() + + handle = self.board.start_update() + + for var in update_vars: + for part in self._iterate_parts(var): + self.update_metadata(part.name, var, missing, handle=handle) + + self.board.finish_update(handle) + + @param("reference", "string", "not_empty", desc="Reference identifier to find") + @param("variant", "string", desc="Assembly variant to search") + @param("missing_only", "bool", desc="Only update missing metadata attributes") + def update_metadata(self, reference, variant=None, missing=True, handle=None): + """ + Update a single part with additional metadata from the matching engine. + + This can be used to automatically fill in descriptions, manufacturers and mpns + if you only have a distributor's part number, for example. If missing is True, + the default, only missing data will be added, entered data will not be overwritten. + """ + + part = self.find(reference, variant) + matches = self.lookup(reference, variant) + if len(matches) > 1: + raise DataError("part matched multiple times in database", reference=reference, variant=variant, matches=matches) + + match = matches[0] + + if match.mpn is not None and (not missing or (missing and part.mpn is None)): + self.board.set_metadata(part, variant, 'MPN', match.mpn, handle=handle) + + if match.manu is not None and (not missing or (missing and part.manu is None)): + self.board.set_metadata(part, variant, 'MANU', match.manu, handle=handle) + + if match.desc is not None and (not missing or (missing and part.desc is None)): + self.board.set_metadata(part, variant, 'DESCRIPTION', match.desc, handle=handle) + + @param("output", "path", desc="Output directory for production files") + def generate_fab(self, output): + """ + Create Gerber,excellon and readme files so this board can be fabricated + + Files are saved into the specified directory output, which is created if + it does not exist. A readme.txt file is also generated describing the + contents of each file. + """ + + prod = ProductionFileGenerator(self) + prod.build_fab(output) + + @param("output", "path", desc="Output directory for production files") + @param("varaint", "string", desc="Assembly variant to process") + def generate_production(self, output, variant=None): + """ + Create Gerber files, BOMs and assembly drawings for this board. + """ + + variant = self._assure_valid_variant(variant) + + prod = ProductionFileGenerator(self) + prod.build_production(variant, output) + + @return_type("list(string)") + def get_errors(self): + """ + Return a list of all errors in this board file. + """ + + return map(self._format_msg, self.errors) + + @return_type("list(string)") + def get_warnings(self): + """ + Return a list of all warnings in this board file. + """ + + return map(self._format_msg, self.warnings) + + + @param('dist', 'string', 'not_empty', desc='a distributor\'s name') + def require_distributor(self, dist): + """ + When searching for prices, require one of these distributors + + This can be called multiple times to whitelist a number of + distributors. + """ + + self.required_dists.append(dist) + + @param('dist', 'string', 'not_empty', desc='a distributor\'s name') + def exclude_distributor(self, dist): + """ + When searching for prices, exclude offerings from this distributor + + This can be called multiple times to blacklist a number of distributors. + """ + + self.excluded_dists.append(dist) + + @param('packaging', 'string', 'not_empty', desc='a type of packaging') + def require_packaging(self, packaging): + """ + When searching for prices, require a certain type of packaging + + This can be called multiple times to whitelist a number of + packaging types like 'Cut Tape' or 'Tape & Reel'. + """ + + self.required_packages.append(packaging) + + @param('packaging', 'string', 'not_empty', desc='a type of packaging') + def exclude_packaging(self, packaging): + """ + When searching for prices, forbid a certain type of packaging + + This can be called multiple times to blacklist a number of + packaging types like 'Cut Tape' or 'Tape & Reel'. + """ + + self.excluded_packages.append(packaging) + + @param("in_stock", "bool", desc="whether parts must be in stock") + def require_stock(self, in_stock): + """ + Set whether parts must be in stock when pricing + """ + + self.require_stock = in_stock + + @return_type("unit_price") + @param("ref", "string", desc="part to lookup price information on") + @param("n", "integer", "nonnegative", desc="number of parts to quote") + @param("variant", "string", desc="Assembly variant to search") + @param("total", "bool", desc="Return the total or unit price") + def price(self, ref, n, variant=None, total=False): + """ + Return the best price of n of the part specified by the reference id, ref + + The part prices are searched according to the currently set price model + including whitelisted and blacklisted packaging types and distributors. + To find out information about what types of distributors and packaging a + part is available in, use lookup. + """ + + part = self.find(ref, variant) + matcher = self._get_matcher() + matcher.add_part(part) + matcher.match_all() + + result = matcher.price_advanced(ref, n, self._build_pricemodel()) + + if 'error' in result: + raise DataError("Could not get price", error=error, result=result) + + if total: + result['price'] *= n + + return type_system.convert_to_type(result, 'unit_price') + + @return_type("map(string, unit_price)") + @param("n", "integer", "nonnegative", desc="number of complete boards to quote") + @param("variant", "string", desc="Assembly variant to price") + @param("excess", "integer", "nonnegative", desc="percent excess to order of each part") + def detailed_prices(self, n, excess, variant=None): + """ + Return the price and seller for each part that minimizes cost for n boards + + Optionally order excess percent extra of each part to account for assembly + losses. + """ + + quote_quant = n + (excess/100.0) + + out_prices = {line[0].desc: self.price(line[0].name, int(quote_quant*line[1] + 0.5), variant, total=True) for line in self._iterate_lines(variant)} + return out_prices + + @return_type("map(string, price)") + @param("n", "integer", "nonnegative", desc="number of complete boards to quote") + @param("variant", "string", desc="Assembly variant to price") + @param("excess", "integer", "nonnegative", desc="percent excess to order of each part") + def total_price(self, n, excess, variant=None): + """ + Return the total cost and unit costs of n boards with excess percent excess + + The returned list contains 2 items, the first is the total price, the second + """ + + prices = self.detailed_prices(n, excess, variant) + + total = Decimal('0.0') + + for price in prices.itervalues(): + total += price.price + + return {'total': total, 'unit': total/n} + + @annotated + def clear_pricemodel(self): + """ + Remove all pricing restrictions + """ + + self.required_dists = [] + self.excluded_dists = [] + self.required_packages = [] + self.excluded_packages = [] + self.prices = False + self.require_stock = False + self.quote_n = 1 + self.quote_quant = 1.0 + self.quote_excess = 0 + + @param("quantity", "integer", "positive", desc="number of units to quote") + @param("excess", "integer", "nonnegative", desc="required excess in %") + def include_prices(self, quantity, excess): + """ + Include prices in generated BOMs + """ + + self.quote_n = quantity + self.quote_excess = excess + self.quote_quant = quantity * (1.0 + excess/100.) + self.prices = True + + @param("engine", "string", desc="name of component lookup engine to use") + def set_match_engine(self, engine): + """ + Select the service used to lookup components. + + Currently supported values are CacheOnlyMatcher and OctopartMatcher + to use only cached values or the Octopart API. Other methods will be + supported in the future. + """ + + if not hasattr(match_engines, engine): + raise ArgumentError("unknown component matching engine specified", engine=engine) + + matcher = getattr(match_engines, engine) + if not issubclass(matcher, BOMMatcher): + raise ArgumentError("invalid component matching engine specified", engine=engine, classname=matcher, object=matcher) + + self._match_engine = matcher + + @return_type("list(bom_line)") + @param("variant", "string", desc='assembly variant to build') + def bom_lines(self, variant=None): + """ + Get all of the BOM lines for the specified assembly variant + """ + + variant = self._assure_valid_variant(variant) + + outlines = [] + for line in self._iterate_linegroups(variant): + outline = type_system.convert_to_type(line, 'bom_line') + outlines.append(outline) + + return self._order_bom_lines(outlines) + + @return_type("list(string)") + def get_variants(self): + return self.variants.keys() + + @param("path", "path", "writeable", desc="The path to the BOM that should be created") + @param("variant", "string", desc='assembly variant to build') + @param("format", "string", ("list", ['excel']), desc="The file format that should be saved") + @param("hide_problems", "bool", desc="do not include errors or warnings processing this file") + def export_bom(self, path, variant=None, format="excel", hide_problems=False): + """ + Expore a Bill of Materials for this board in the specified format. + + BOMs can either be formatted as an industry standard excel spreadsheet + or using a custom Cheetah based template. Currently only excel spreadsheets + are supported. The optional format parameter chooses the format and the + result is saved to a file at path. + """ + + if format == 'excel': + self._save_excel_bom(variant, path, hide_problems=hide_problems) + + def _save_excel_bom(self, variant, path, hide_problems): + """ + Save a BOM in excel format. + """ + + import xlsxwriter + import reference + + lib = reference.PCBReferenceLibrary() + lines = self.bom_lines(variant) + + bk = xlsxwriter.Workbook(path) + sht = bk.add_worksheet("Bill of Materials") + + #formats + large = bk.add_format({'font_size': 16, 'bold':True}) + med = bk.add_format({'font_size': 14, 'bold':True}) + bold = bk.add_format({'bold': True}) + red = bk.add_format({'bg_color': 'red'}) + orange= bk.add_format({'bg_color': 'orange'}) + + #Write header + sht.write(0, 0, "Bill of Materials", large) + sht.write(1, 0, self.part, med) + sht.write(2, 0, self.company) + sht.write(3, 0, "Revision") + sht.write(3, 1, self.revision) + sht.write(4, 0, "Width") + sht.write(4, 1, self.width) + sht.write(4, 2, self.units) + sht.write(5, 0, "Height") + sht.write(5, 1, self.height) + sht.write(5, 2, self.units) + + #Write out all of the BOM lines + headers = ['', 'Qty', 'References', 'Value', 'Footprint', 'Description', 'Manufacturer', 'Part Number', 'Distributor', 'Dist. Part Number'] + row = 7 + col = 0 + + for h in headers: + sht.write(row, col, h, bold) + col += 1 + + row += 1 + for i,line in enumerate(lines): + sht.write(row, 0, i+1) + sht.write(row, 1, line.count) + sht.write(row, 2, ", ".join(line.refs)) + + if lib.has_value(line.type): + sht.write(row, 3, line.value) + + sht.write(row, 4, line.package.name) + sht.write(row, 5, line.desc) + + if line.manu is not None: + sht.write(row, 6, line.manu) + if line.mpn is not None: + sht.write(row, 7, line.mpn) + if line.dist is not None: + sht.write(row, 8, line.dist) + if line.distpn is not None: + sht.write(row, 9, line.distpn) + + row += 1 + + row += 1 + + #Write out all of the unpopulated parts + sht.write(row, 0, "Unpopulatd Parts", red) + sht.write(row, 1, ", ".join(self.nonpopulated_parts(variant)), red) + + if not hide_problems: + row += 2 + if len(self.errors) > 0: + errors = self.get_errors() + sht.write(row, 0, "BOM Errors", bold) + row += 1 + for error in errors: + sht.write(row, 0, error) + row += 1 + + row += 1 + if len(self.warnings) > 0: + warnings = self.get_warnings() + sht.write(row, 0, "BOM Warnings", bold) + row += 1 + for warning in warnings: + sht.write(row, 0, warning) + row += 1 + + bk.close() + + def _order_bom_lines(self, lines): + """ + Sort bom lines based on the type of each line + + Follow a standard order: R, C, L, D, U, other + This routine assumes that there will never be more than 100,000 + parts per board, which seems safe for the foreseeable future. + """ + + order_dict = {'R': 0, 'C': 1, 'L': 2, 'D': 3, 'U': 4} + + def ref_key(line): + mult = 100000 + ref = line.type + num = line.lowest + if ref in order_dict: + return order_dict[ref]*mult + num + + if ref in ref_key.others: + return ref_key.others[ref]*mult + num + + ind = ref_key.other_index + ref_key.others[ref] = ref_key.other_index + ref_key.other_index += 1 + return ind*mult + num + + ref_key.other_index = 20 + ref_key.others = {} + + return sorted(lines, key=ref_key) + + def _build_pricemodel(self): + return OfferRequirements(self.required_dists, self.excluded_dists, + self.required_packages, self.excluded_packages, + self.require_stock) + + def _default_variant(self): + """ + If there is only one variant, choose it by default + """ + + variants = self.variants.keys() + if len(variants) == 1: + return variants[0] + + return None + + def _assure_valid_variant(self, variant): + """ + If variant is None, attempt to use the default variant. + """ + + if variant is None: + variant = self._default_variant() + if variant is None: + raise ArgumentError('you must specify a variant when there is more than one option', variants=self.board.variants.keys()) + + return variant + + def _process_variant(self, parts): + """ + Group items by unique key and return tuples of identical parts so that you can + turn a list of parts into a BOM with identical line items. + """ + + bomitems = [] + + sparts = sorted(parts, key=lambda x:x.unique_id()) + for k,g in itertools.groupby(sparts, lambda x:x.unique_id()): + bomitems.append(list(g)) + + return bomitems + + def _add_warning(self, attribute, msg): + """ + Log a warning about something missing or not correct about this circuit board. + """ + + self.warnings.append(('WARNING', attribute, msg)) + + def _add_error(self, attribute, msg): + """ + Log an error about something missing or not correct about this circuit board. + """ + + self.errors.append(('ERROR', attribute, msg)) + + def _format_msg(self, msg_tuple): + """ + Format an error or warning message into a string + """ + + return "%s on attribute/part %s: %s" % msg_tuple + + def _set_attribute(self, attribute, data, default="Unknown Attribute", required=True): + value = default + + if required: + logger = self._add_error + else: + logger = self._add_warning + + if attribute not in data: + logger(attribute, "Board data did not contain attribute in dictionary") + else: + brd_value = data[attribute] + if brd_value is None: + logger(attribute, "Attribute was empty or not defined") + else: + value = brd_value + + setattr(self, attribute, value) + + def _build_lookup_table(self): + """ + Build a map to look up part information given its reference number + """ + + part_table = {} + + for variant, lines in self.variants.iteritems(): + for line in lines: + for part in line: + if part.name not in part_table: + part_table[part.name] = {} + + part_table[part.name][variant] = part + + self.part_index = part_table + + def _iterate_parts(self, variant=None): + """ + Iterate over all parts in the assembly variant specified + """ + + variant = self._assure_valid_variant(variant) + lines = self.variants[variant] + + for line in lines: + for part in line: + yield part + + def _iterate_lines(self, variant=None): + """ + Iterate over each distinct BOM line on the board returning + the first logical part and the number of identical parts + """ + + variant = self._assure_valid_variant(variant) + lines = self.variants[variant] + + for line in lines: + yield line[0], len(line) + + def _iterate_linegroups(self, variant=None): + """ + Iterate over all parts grouping them by their bom line + + This returns lists of parts corresponding to each BOM line + """ + + variant = self._assure_valid_variant(variant) + lines = self.variants[variant] + + for line in lines: + yield line + + def _iterate_all_parts(self): + """ + Iterate over all parts defined in any assembly variant of this board + """ + + for variant, lines in self.variants.iteritems(): + for line in lines: + for part in line: + yield part + + def _get_matcher(self): + """ + Get an instance of the currently supported match engine + """ + + return self._match_engine() + diff --git a/pymomo/pcb/bom_matcher.py b/pymomo/pcb/bom_matcher.py new file mode 100644 index 0000000..8fe95cc --- /dev/null +++ b/pymomo/pcb/bom_matcher.py @@ -0,0 +1,364 @@ +#bom_matcher.py +#A generic interface for services that take in identifying information about a +#part and return price offerings for that part. + +from pymomo.utilities.typedargs import * +from pymomo.exceptions import * +from partcache import PartCache +import uuid +from decimal import Decimal + +class BOMMatcher(object): + def __init__(self): + self.requests = [] + self.matched = {} + self.errors = {} + self.cache = PartCache() + + self.ref_index = {} + + def _canonicalize_name(self, name): + """ + Given a name, like a distributor's name, convert it to + lowercase and remove all spaces,_ or - characters + """ + + return name.lower().replace(' ', '').replace('-', '').replace('_', '') + + def _build_reference(self, part): + if "distpn" in part: + return "__SKU__" + self._canonicalize_name(str(part['distpn'])) + self._canonicalize_name(str(part['dist'])) + + else: + mpn = part['mpn'] + manu = part['manu'] + + return "__MPN__" + self._canonicalize_name(str(part['mpn'])) + self._canonicalize_name(str(part['manu'])) + + @param("mpn", "string", "not_empty", desc="manufacturer's part number") + @param("manu", "string", desc="manufacturer") + @param("ref", "string", desc="reference number") + def add_by_mpn(self, mpn, manu=None, ref=""): + """ + Find a part by its manufacturer's part number + + Optionally filter to make sure that the manufacturer is equal to the specified + 'manu' string in case the mpn is not globally unique. Comparison between + manufacturer strings, if given, is done in a case insensitive manner after removing + all - and _ characters from both strings, so e.g. Digi-Key matches digikey. + + If ref is given, the returned information will contain the reference number so that + it can be unambiguously matched with the source that generated the request for pricing + """ + + part = {'mpn': mpn, 'manu': manu, 'external_ref': ref} + part['ref'] = self._build_reference(part) + + self.requests.append(part) + if ref != "": + self._add_reference(part['ref'], ref) + + @param("distpn", "string", "not_empty", desc="distributor part number") + @param("dist", "string", desc="distributor name") + @param("ref", "string", desc="reference identifier") + def add_by_distpn(self, distpn, dist="", ref=""): + """ + Find a part by its distributor part number + + Optionally filter to make sure that the distributor is equal to the specified + 'dist' string in case the distpn is not globally unique. Comparison between + distributor strings, if given, is done in a case insensitive manner after removing + all - and _ characters from both strings, so e.g. Digi-Key matches digikey. + + If ref is given, the returned information will contain the reference number so that + it can be unambiguously matched with the source that generated the request for pricing + """ + + part = {'distpn': distpn, 'dist': dist, 'external_ref': ref} + part['ref'] = self._build_reference(part) + + self.requests.append(part) + if ref != "": + self._add_reference(part['ref'], ref) + + @param("part", "logical_part", "matchable", desc="component to look up") + def add_part(self, part): + """ + Find a logical part based on its embedded metadata + """ + + if part.manu is not None and part.mpn is not None: + self.add_by_mpn(part.mpn, part.manu, ref=part.name) + + #If we have information about both a distributor and a mpn + #save both bits of information so that we can filter in case the MPN + #is not uniquely identifying + if part.dist is not None: + self.requests[-1]['require_dist'] = part.dist + if part.distpn is not None: + self.requests[-1]['require_distpn'] = part.distpn + else: + self.add_by_distpn(part.distpn, part.dist, ref=part.name) + + def _add_reference(self, internal, external): + if external in self.ref_index: + raise ArgumentError("attempted to add same reference number twice", reference=external, old=self.ref_index, new=internal) + + self.ref_index[external] = internal + + @annotated + def clear(self): + """ + Clear all pending requests and results + """ + + self.requests = [] + self.matched = {} + self.errors = {} + self.ref_index = {} + + @annotated + def match_all(self): + """ + Attempt to match all parts. + + Parts that are present in the part cache already are not + passed on to the matching engine but instead returned directly + from the cache. Parts that are uniquely matched are cached + for future reference. + """ + + #Figure out which items are cached + cache_status = map(lambda x: self.cache.try_get(x['ref']), self.requests) + uncached = [x[1] for x in enumerate(self.requests) if cache_status[x[0]] is None] + + reqs = [x for x in self.requests] + self.requests = uncached + self._match() + + #Perform additional checks to make sure that the match engines are returning + #just the results that we want and not extras. + for req in reqs: + if req['ref'] in self.matched: + parts = self.matched[req['ref']] + + #Some match engines only match the MPN, not the manufacturer's name, so enforce that here + if 'manu' in req and req['manu'] is not None: + self.matched[req['ref']] = filter(lambda x: self._canonicalize_name(req['manu']) == self._canonicalize_name(x.manu), parts) + + #Make sure that if we specified a specific distributor that this part is carried by that distributor + dist = None + if 'require_dist' in req: + dist = req['require_dist'] + if 'dist' in req and req['dist'] is not None: + dist = req['dist'] + if dist is not None: + self.matched[req['ref']] = filter(lambda x: self._dist_in_offers(x, dist), parts) + + #Update the cache with the new results + for key, value in self.matched.iteritems(): + if len(value) == 1: + self.cache.set(key, value[0]) + + #Add in all of the cached results + for i, x in enumerate(cache_status): + if x is not None: + self.matched[reqs[i]['ref']] = [x] + + #Reset the list of requests to include cached and uncached values + self.requests = reqs + + @return_type("map(string, integer)") + def match_summary(self): + """ + Show which parts have been added, matched or had errors + """ + + total_reqs = len(self.requests) + total_matched = len(self.matched) + total_errors = len(self.errors) + + multimatch = filter(lambda x: len(x) > 1, self.matched.itervalues()) + total_multi = len(multimatch) + + ret = {'Requested Parts': total_reqs, 'Matched Parts': total_matched, + 'Parts with Errors': total_errors, 'Multiple Matches': total_multi} + + return ret + + @return_type("string") + def all_matched(self): + """ + Check if all requested parts have been matched to exactly 1 physical part + """ + + if len(self.matched) == len(self.requests): + multimatch = filter(lambda x: len(x) > 1, self.matched.itervalues()) + total_multi = len(multimatch) + + if total_multi == 0: + return True + + return False + + @param("reference", "string", "not_empty", desc="reference number") + @return_type("string") + def is_matched(self, reference): + """ + Check if reference number is matched to a physical part + """ + + if reference not in self.ref_index: + return False + + intref = self.ref_index[reference] + if intref in self.matched: + return True + + return False + + @return_type("map(string, string)") + def match_details(self): + """ + List the matching status of each part according to its reference number + """ + + stats = {} + + for r in self.requests: + refnum = r['ref'] + if 'external_ref' in r and r['external_ref'] != '': + refnum = r['external_ref'] + + if r['ref'] in self.matched: + if len(self.matched[r['ref']]) == 1: + stats[refnum] = 'Matched to a unique item' + else: + stats[refnum] = '%d separate matches' % (len(self.matched[r['ref']]),) + + elif r['ref'] in self.errors: + stats[refnum] = self.errors[r['ref']]['msg'] + else: + stats[refnum] = 'Match has not been attempted' + + return stats + + @param("reference", "string", "not_empty", desc="user reference number") + @return_type("list(physical_part)") + def match_info(self, reference): + """ + Return matched part info by reference number + + If the part matched multiple items, they are all returned. + """ + + if reference not in self.ref_index: + raise ArgumentError("unknown reference number passed", reference=reference) + + intref = self.ref_index[reference] + + if intref not in self.matched: + raise ArgumentError("part was not matched", reference=reference, internal_reference=intref) + + info = self.matched[intref] + + return info + + @param("quantity", "integer", "positive", desc="requested number of parts") + @param("reference", "string", "not_empty", desc="reference number") + @param("seller", "string", desc="required seller of the part") + @return_type("string") + def price(self, reference, quantity, seller): + """ + Return the best price for N parts from seller as a Decimal + """ + + info = self.match_info(reference) + can_seller = self._canonicalize_name(seller) + + if len(info) > 1: + raise ArgumentError("part is not uniquely matched", reference=reference) + + offers = filter(lambda x: self._canonicalize_name(x.seller)==can_seller, info[0].offers) + + best = Decimal.from_float(1e15) + for offer in offers: + price = offer.best_price(quantity) + if price is not None and price < best: + best = price + + return best + + def price_advanced(self, reference, quantity, requirements): + """ + Find the price of a single item subject to various requirements + + requirements must have a validate function that takes two arguments: an offer and the quantity + """ + + info = self.match_info(reference) + + if len(info) > 1: + return {'error': 'Part not uniquely matched'} + + offers = filter(lambda x: requirements.validate(x, quantity), info[0].offers) + + best = Decimal.from_float(1e15) + best_offer = None + for offer in offers: + price = offer.best_price(quantity) + if price is not None and price < best: + best = price + best_offer = offer + + if best_offer is None: + return {'error': 'No valid offers found'} + else: + return {'price': best, 'offer': offer} + + def prices(self, quantities, requirements): + """ + Return the prices for specified parts at specified quantities. + + requirements must have a validate function that takes two arguments: an offer and the quantity + quantities must be a dictionary mapping reference numbers to quantities + """ + + result = {} + + for ref,quantity in self.quantities.iteritems(): + info = self.match_info(ref) + + if len(info) > 1: + result[ref] = {'error': 'Part not uniquely matched'} + continue + + offers = filter(lambda x: requirements.validate(x, quantity), info[0].offers) + + best = Decimal.from_float(1e15) + best_offer = None + for offer in offers: + price = offer.best_price(quantity) + if price is not None and price < best: + best = price + best_offer = offer + + if best_offer is None: + result[ref] = {'error', 'No valid offers found'} + else: + result[ref] = {'price': best, 'offer': offer} + + return result + + def _dist_in_offers(self, part, dist): + """ + Given a physical_part, make sure it contains an offer from distributor dist + """ + + dist = self._canonicalize_name(dist) + + for offer in part.offers: + if self._canonicalize_name(offer.seller) == dist: + return True + + return False diff --git a/pymomo/pcb/eagle/__init__.py b/pymomo/pcb/eagle/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymomo/pcb/eagle/board.py b/pymomo/pcb/eagle/board.py new file mode 100644 index 0000000..b019802 --- /dev/null +++ b/pymomo/pcb/eagle/board.py @@ -0,0 +1,292 @@ +#board.py +#TODO +# - check for location of eagle in standard locations on Windows + +import itertools +import xml.etree.cElementTree as ElementTree + +import platform +import os +import subprocess + +from part import part_from_board_element +from ..reference import PCBReferenceLibrary +from ..package import Package +from pymomo.exceptions import * + +class EagleBoard: + def __init__(self, brd_file): + """ + Create a Board from EAGLE Board file by parsing attribute tags embedded with the + components. Create different assembly variants for each different configuration + specified in the file + """ + + tree = ElementTree.parse(brd_file) + if tree is None: + raise DataError("Eagle board file does not contain valid XML", file=brd_file) + + root = tree.getroot() + + #Find all of the defined packages in this file + packages =root.findall('.//packages') + + elems = root.find("./drawing/board/elements") + if elems is None: + raise DataError("Eagle board file does not contain a list of components", file=brd_file) + + parts = list(elems) + + unknown = [] + nopop = [] + constant = [] + variable = [] + variants = find_variants(root) + + all_vars = set(variants) + + #Create a part object for each part that has a valid digikey-pn + #If there are multiple digikey part numbers, create a part for each one + for part in parts: + valid_part = False + found_vars = set() + nopop_vars = set() + + for v in variants: + p,found = part_from_board_element(part, v, packages) + if p and found: + variable.append( (v, p) ) + found_vars.add(v) + valid_part = True + elif found: + #If this part has digipn == "" or Populate=no, then it should not be populated in this variant + valid_part = True + nopop_vars.add(v) + nopop.append(part.get('name',"Unknown Name")) + + #See if this is a constant part (with no changes in different variants) + p,found = part_from_board_element(part, "", packages) + if p and len(found_vars) == 0 and len(nopop_vars) == 0: + constant.append(p) + valid_part = True + elif p and len(found_vars) != len(all_vars): + #there are some variants that want the default part and some that want a change + for var in (all_vars - found_vars) - nopop_vars: + variable.append( (var, p) ) + valid_part = True + + #Make sure this part has information for at least one assembly variant + if not valid_part: + unknown.append(part.get('name',"Unknown Name")) + + vars = {} + #Create multiple variants + for var in variants: + vars[var] = constant + filter_sublists(variable, var) + + props = {} + props['company'] = find_attribute(root, 'COMPANY', None) + props['part'] = find_attribute(root, 'PARTNAME', None) + props['width'] = find_attribute(root, 'WIDTH', None) + props['height'] = find_attribute(root, 'HEIGHT', None) + props['units'] = find_attribute(root, 'DIM_UNITS', None) + props['revision'] = find_attribute(root, 'REVISION', None) + props['no_populate'] = nopop + props['unknown_parts'] = unknown + props['variants'] = vars + props['fab_engine'] = 'eagle' #Used by production.py to locate the templates for gerber generation + props['fab_template'] = find_attribute(root, 'FAB_TEMPLATE', None) + + self.data = props + self.file = brd_file + + def start_update(self): + """ + Open the underlying file for appending additional attributes + + Returns an opaque datatype that should be passed to set_metadata. + """ + + tree = ElementTree.parse(self.file) + if tree is None: + raise DataError("Eagle board file does not contain valid XML", file=brd_file) + + return tree + + def finish_update(self, handle): + """ + Save the changes to the board file specified in handle. + + handle must be the result of a call to start_update + """ + + handle.write(self.file) + + def set_metadata(self, part, variant, key, value, handle=None): + """ + Update this board file with additional attributes. + + The board must be reloaded for the attributes to be visible. + """ + + if handle is None: + tree = self.start_update() + root = tree.getroot() + else: + root = handle.getroot() + + element = root.find(".//element[@name='%s']" % part.name) + if element is None: + raise ArgumentError("Part was not found in board to update") + + att_name = key.upper() + + if att_name not in set(['MANU', 'MPN', 'DIST', 'DIST-PN', 'DIGIKEY-PN', 'FOOTPRINT', 'DESCRIPTION']): + raise ArgumentError("attempting to set unknown metadata attribute", file=self.file, attribute=att_name, value=value, variant=variant) + + if variant is not None and variant != 'MAIN': + att_name += '-%s' % variant.upper() + + att_elem = element.find("./attribute[@name='%s']" % att_name) + if att_elem is not None: + att_elem.set('value', value.upper()) + else: + x = element.get('x', '0.0') + y = element.get('y', '0.0') + + #Add in other default attributes for this xml element. + attribs = { 'x': x, 'y': y, 'name': att_name, 'value': value.upper(), + 'size': "1.778", 'layer': "27", 'display': "off"} + + ElementTree.SubElement(element, 'attribute', **attribs) + + #Update the file with the modification if we were called in standalone mode + if handle is None: + tree.write(self.file) + + def build_production_file(self, path, layers, type, **kwargs): + """ + Use the data in this board file to produce Gerbers and Excellon files + + layers should be the eagle layers to include in the file + type should be gerber, excellon or drawing + optional kwargs may be supported in the future to adjust output + """ + + #Options from EAGLE's built in gerber file for producing 2 layer boards + #and argument names from EAGLE -? command + args = ['-X', '-f+', '-c+', '-O+'] + + if type == 'gerber': + args.append('-dGERBER_RS274X') + elif type == 'excellon': + args.append('-dEXCELLON') + elif type == 'drawing': + args.append('-dPS') + args.append('-h11') + args.append('-w7.75') + args.append('-s2.5') + else: + raise ArgumentError("Invalid type specified for production file (must be gerber, excellon or drawing)", type=type) + + args.append('-o%s' % path) + args.append(self.file) + + for layer in layers: + args.append(layer) + + self._execute_eagle(args) + + def _find_eagle(self): + """ + Try to find the eagle executable on this system. + + If it's in the path, just return that, otherwise search in standard + locations on each architecture or fail. + """ + + from distutils.spawn import find_executable + + if platform.system() == 'Darwin': + eagle = find_executable('EAGLE') + if eagle is not None: + return eagle + + entries = os.listdir('/Applications') + possible_eagles = filter(lambda x: x.startswith('EAGLE-') and os.path.isdir(os.path.join('/Applications', x)), entries) + + #Try to get the latest EAGLE version if there are multiple installed + try: + possible_eagles.sort(key=lambda x: map(int, x[6:].split('.'))) + + eagle_dir = possible_eagles[-1] + eagle_path = os.path.join('/Applications', eagle_dir, 'EAGLE.app', 'Contents', 'MacOS', 'EAGLE') + + if os.path.isfile(eagle_path) and os.access(eagle_path, os.X_OK): + return eagle_path + except ValueError: + #Value errors mean we could not convert the version string to an int, indicating that it wasn't in the form X.Y.Z + #ignore this error and just say we couldn't find EAGLE since an eagle directory without an X.Y.Z form could be a + #different program altogether. + pass + elif platform.system() == 'Windows': + eagle = find_executable('EAGLECON') + if eagle is not None: + return eagle + + #TODO: Check for eagle location on windows and autofind + #Check for program files directory in 32 or 64 bit mode like so: + #http://stackoverflow.com/questions/1283664/python-get-wrong-value-for-os-environprogramfiles-on-64bit-vista + else: + #Otherwise assume that Eagle will be in $PATH + eagle = find_executable('EAGLECON') + if eagle is not None: + return eagle + + raise EnvironmentError("could not find installed version of Cadsoft EAGLE", suggestion='install EAGLE and add it to your $PATH') + + def _execute_eagle(self, args): + eagle = self._find_eagle() + + #TODO: Check what kinds of exceptions this can throw + with open(os.devnull, 'wb') as DEVNULL: + subprocess.check_call([eagle] + args, stdout=DEVNULL, stderr=DEVNULL) + + +#Helper functions +def remove_prefix(s, prefix): + if not s.startswith(prefix): + return s + + return s[len(prefix):] + +def filter_sublists(master, key): + """ + Given a list of lists where each of the sublist elements are tuples (key, value), + return a concatenated list of all values in tuples that match key + """ + + concat = itertools.chain(master) + + filtered = filter(lambda x: x[0] == key, concat) + + return map(lambda x:x[1], filtered) + +MISSING = object() +def find_attribute(root, name, default=MISSING): + attr = root.find(".//attribute[@name='%s']" % name) + if attr is None: + if default is not MISSING: + return default + + raise DataError("required board attribute not found", name=name) + + return attr.get('value') + +def find_variants(root): + vars = root.findall(".//attribute[@value='%s']" % 'ASSY-VARIANT') + + if len(vars) == 0: + return ["MAIN"] + else: + return map(lambda x: x.get('name'), vars) diff --git a/pymomo/pcb/eagle/part.py b/pymomo/pcb/eagle/part.py new file mode 100644 index 0000000..2f2bc8c --- /dev/null +++ b/pymomo/pcb/eagle/part.py @@ -0,0 +1,186 @@ +#Routines for building part objects from eagle board files +from pint import UnitRegistry +from pymomo.exceptions import * +from pymomo.utilities.typedargs import type_system + +from ..package import Pad, Pin, Package, Placement + +ureg = UnitRegistry() + +def part_from_board_element(elem, variant, packages): + """ + Create a Part object from an element in an EAGLE board file. + + Automatically extract all of the known attributes of the part to fill in the + reference identifier, package information, number of pads/pins, footprint, + description, etc. + + packages should be a list of XML elements corresponding to the various lists of packages + for this board. + + Returns a tuple of Part,Boolean + The Part describes this part and may be None if it is not populated in this variant + The Boolean is true is this part is valid and populated in this variant and false otherwise. + """ + + pkg = elem.get('package', None) + + name = elem.get('name', 'No Name') + value = elem.get('value', 'No Value') + + value_override = find_attribute(elem, 'VALUE', variant) + if value_override is not None: + value = value_override + + #allow overriding this package with a custom name + pkgname = find_attribute(elem, 'FOOTPRINT', variant, pkg) + if pkg is not None: + pkg_info = find_package(packages, pkg, display_name=pkgname) + else: + pkg_info = Package(str(pkgname), [], []) + + desc = find_attribute(elem, 'DESCRIPTION', variant, None) + pop_attr = find_attribute(elem, 'POPULATE', variant, "yes") + + if pop_attr.lower() == "no": + return None, True + elif pop_attr != "yes": + raise ValidationError("Unknown value in POPULATE attribute in element %s: %s" % (name, pop_attr)) + + manu, mpn = find_mpn(elem, variant) + dist, distpn = find_distpn(elem, variant) + + #Get physical location information + x = float(elem.get('x')) * ureg.millimeter + y = float(elem.get('y')) * ureg.millimeter + + rot = elem.get('rot') + if rot is None: + rot = 0.0 + else: + rot = float(elem.get('rot')[1:]) #Rotation is specified at R## where ## is the angle in degrees. + + placement = Placement(x, y, rot) + + if (mpn is not None and manu is not None) or (dist is not None and distpn is not None): + args = {} + args['name'] = name + args['package'] = pkg_info + args['placement'] = placement + args['dist'] = dist + args['distpn'] = distpn + args['mpn'] = mpn + args['manu'] = manu + args['value'] = value + args['desc'] = desc + + return type_system.convert_to_type(args, 'logical_part'), True + + return None, False + +def attrib_name(name, variant): + if variant == "MAIN" or variant == "": + return name + + return name + '-' + variant + +def find_attribute(part, name, variant, default=None): + """ + Find the attribute corresponding to the given variant, or if that doesn't exist, + the value corresponding to MAIN, otherwise return the given default + """ + + attrib_var = part.find("./attribute[@name='%s']" % attrib_name(name, variant)) + attrib_main = part.find("./attribute[@name='%s']" % attrib_name(name, 'MAIN')) + + if attrib_var is not None: + return attrib_var.get('value') + + if attrib_main is not None: + return attrib_main.get('value') + + return default + +def find_mpn(part, variant): + """ + See if this part has the variables MPN[-variant] and MANU[-variant] defined + return None if either is undefined. Otherwise return the tuple (manu, mpn) + """ + + mpn = find_attribute(part, 'MPN', variant) + manu = find_attribute(part, 'MANU', variant) + + if mpn is None or manu is None: + return (None, None) + + return (manu, mpn) + +def find_distpn(part, variant): + """ + Try to find a distributor part number for this part + + This can be specified either using a single combined attribute for Digikey + parts (DIGIKEY-PN) or by using 2 attributes specifying the distributor and + the part number (DIST and DIST-PN). If any of these are blank, they are returned + as None. + """ + + #Look for a digikey pn attribute as a shortcut for digikey parts + pn_elem = find_attribute(part, 'DIGIKEY-PN', variant) + if pn_elem is not None: + return ("Digikey", pn_elem) + + pn_elem = find_attribute(part, 'DIST-PN', variant) + dist_elem = find_attribute(part, 'DIST', variant) + + dist = None + pn = None + + if pn_elem is not None: + pn = pn_elem + if dist_elem is not None: + dist = dist_elem + + return (dist, pn) + +def find_package(packages, find_name, display_name): + """ + Create a Package object from the named package + """ + + found = False + + for packagelist in packages: + package = packagelist.find("./package[@name='%s']" % str(find_name)) + if package is not None: + found = True + break + + if found is False: + raise ValidationError("Invalid board file, a package was used but not defined", name=find_name) + + eagle_pads = package.findall("./smd") + eagle_pins = package.findall("./pad") + + pads = [] + pins = [] + + #Eagle boards store all coordinates in mm regardless of how it is displayed + for epad in eagle_pads: + x = float(epad.get('x')) * ureg.millimeter + y = float(epad.get('y')) * ureg.millimeter + w = float(epad.get('dx')) * ureg.millimeter + h = float(epad.get('dy')) * ureg.millimeter + + pad = Pad(x, y, w, h) + pads.append(pad) + + for epin in eagle_pins: + x = float(epin.get('x')) * ureg.millimeter + y = float(epin.get('y')) * ureg.millimeter + drill = float(epin.get('drill')) * ureg.millimeter + + pin = Pin(x,y, drill) + pins.append(pin) + + return Package(display_name, pins, pads) diff --git a/pymomo/pcb/match_engines/__init__.py b/pymomo/pcb/match_engines/__init__.py new file mode 100644 index 0000000..e2762f3 --- /dev/null +++ b/pymomo/pcb/match_engines/__init__.py @@ -0,0 +1,4 @@ +#__init__.py + +from octopart import OctopartMatcher +from cache_only import CacheOnlyMatcher \ No newline at end of file diff --git a/pymomo/pcb/match_engines/cache_only.py b/pymomo/pcb/match_engines/cache_only.py new file mode 100644 index 0000000..8a82ad4 --- /dev/null +++ b/pymomo/pcb/match_engines/cache_only.py @@ -0,0 +1,17 @@ +from pymomo.pcb.bom_matcher import BOMMatcher + +class CacheOnlyMatcher (BOMMatcher): + """ + Only return matches from the existing cache. + + If a component is not in the cache, do not attempt to match + it in any way. This is useful for offline purposes and writing + unit tests. + """ + + + def __init__(self): + super(CacheOnlyMatcher, self).__init__() + + def _match(self): + pass diff --git a/pymomo/pcb/match_engines/octopart.py b/pymomo/pcb/match_engines/octopart.py new file mode 100644 index 0000000..2387b72 --- /dev/null +++ b/pymomo/pcb/match_engines/octopart.py @@ -0,0 +1,137 @@ +#octopart.py +#Wrapper arround Octopart REST API + +import os +import json +import urllib +from pymomo.pcb.bom_matcher import BOMMatcher +from pymomo.utilities.typedargs import * +from pymomo.exceptions import * + +@context() +class OctopartMatcher (BOMMatcher): + """ + A service for pricing parts using the Octopart API. + + Currently parts can be matched using either a distributor's name and part number + or directly using manufacturer's part numbers and the manufacturer's + name. Realtime pricing information is returned as are any errors + during the matching process. + """ + + URL = 'http://octopart.com/api/v3/' + + @param("key", "string", desc="Octopart API Key") + def __init__(self, key=None): + super(OctopartMatcher, self).__init__() + + if key is not None: + self.api_key = key + elif 'OCTOPART_KEY' in os.environ: + self.api_key = os.environ['OCTOPART_KEY'] + else: + raise ArgumentError('You need to either specify an Octopart API Key or have the OCTOPART_KEY environment variable set') + + def _build_call(self, method, args): + """ + Build a URL call with arg=val pairs including the apikey to the url specifying the method + given. + """ + + url = OctopartMatcher.URL + method + '?' + + out_args = [] + out_args.extend(args) + out_args.append(('apikey', self.api_key)) + out_args.append(('include[]', 'short_description')) + out_strings = map(lambda x: urllib.quote(x[0])+'='+urllib.quote(x[1]), out_args) + + query_string = "&".join(out_strings) + + return url + query_string + + def _build_query(self, part): + query = {} + if "distpn" in part: + query['sku'] = part["distpn"] + elif "mpn" in part: + query['mpn'] = part["mpn"] + else: + raise ArgumentError("Octopart API cannot match a part without either a digikey part number or a manufacturer's part number", part=part) + + query['reference'] = part['ref'] + return query + + def _call(self, url): + data = urllib.urlopen(url).read() + response = json.loads(data) + + return response + + def _call_method(self, method, args): + url = self._build_call(method, args) + return self._call(url) + + def _build_offer(self, resp): + """ + Create a part_offer from an octopart api response + """ + + offer = {} + offer['seller'] = resp['seller']['name'] + offer['moq'] = resp['moq'] + offer['stock_quantity'] = resp['in_stock_quantity'] + offer['packaging'] = resp['packaging'] + offer['breaks'] = self._build_breaks(resp['prices']) + + return type_system.convert_to_type(offer, 'part_offer') + + def _build_breaks(self, resp): + """ + Create a price_list object from an octopart API response + """ + + if 'USD' in resp: + return type_system.convert_to_type(resp['USD'], 'price_list') + + return type_system.convert_to_type([], 'price_list') + + def _build_part(self, response): + """ + Create a physical_part object from an octopart api response + """ + + part = {} + + manuname = "" + if 'name' in response['manufacturer']: + manuname = response['manufacturer']['name'] + + part['manu'] = manuname + part['mpn'] = response['mpn'] + + part['desc'] = response['short_description'] + + offers = map(lambda x: self._build_offer(x), response['offers']) + part['offers'] = offers + + return type_system.convert_to_type(part, 'physical_part') + + def _match(self): + for i in xrange(0, len(self.requests), 20): + req = self.requests[i:i+20] + queries = map(lambda x: self._build_query(x), req) + + query = json.dumps(queries) + + resp = self._call_method('parts/match',[('queries',query), ('exact_only', 'true')]) + + for result in resp['results']: + ref = result['reference'] + + if len(result['items']) == 0: + self.errors[ref] = {'msg': 'Did not match'} + continue + + parts = map(lambda x: self._build_part(x), result['items']) + self.matched[ref] = parts diff --git a/pymomo/pcb/package.py b/pymomo/pcb/package.py new file mode 100644 index 0000000..d25c5c4 --- /dev/null +++ b/pymomo/pcb/package.py @@ -0,0 +1,159 @@ +#Objects for dealing with PCB packages, pads and pins + +from pymomo.exceptions import * +from reference import PCBReferenceLibrary +import itertools + +class Package: + """ + A pcb component package. + + This contains information on the pins and pads in the package + as well as the package type. + """ + + def __init__(self, name, pins, pads): + """ + Build a package from a name and a list of pins and pads + + - pins should be a list of Pin objects + - pads should be a list of Pad objects + - name should be an identifier that will let us look up information + about this package in our database of standard packages. + """ + + ref = PCBReferenceLibrary() + + self.pins = pins + self.pads = pads + + refined_name, found = ref.find_package(name) + + if found: + self.name = refined_name + else: + self.name = name + + def __str__(self): + return "%s Package (%d pins, %d pads)" % (self.name, len(self.pins), len(self.pads)) + + def __eq__(self, other): + try: + if self.name != other.name: + return False + + for p1, p2 in itertools.izip_longest(self.pins, other.pins, fillvalue=None): + if p1 != p2: + return False + + for p1, p2 in itertools.izip_longest(self.pads, other.pads, fillvalue=None): + if p1 != p2: + print p1 + print p2 + return False + except AttributeError: + return False + + return True + + def __ne__(self, other): + return not self == other + + +class Pin: + """ + A through hole pin + """ + + def __init__(self, x, y, drill=None, ring=None): + if is_quantity(x) and is_quantity(y): + self.x = x + self.y = y + else: + raise ValidationError("pin position is not specified as a dimensioned quantity", x=x, y=y) + + if drill is not None and not is_quantity(drill): + raise ValidationError("pin drill size is not specified as a dimensioned quantity", x=x, y=y) + + if ring is not None and not is_quantity(ring): + raise ValidationError("pin annular ring is not specified as a dimensioned quantity", x=x, y=y) + + self.drill = drill + self.ring = ring + + def __eq__(self, other): + try: + return self.x == other.x and self.y == other.y and self.drill == other.drill and self.ring == other.ring + except AttributeError: + return False + + def __ne__(self, other): + return not self == other + + +class Pad: + """ + A surface mount pad + """ + + def __init__(self, x, y, width, height): + if is_quantity(x) and is_quantity(y): + self.x = x + self.y = y + else: + raise ValidationError("smt pad position is not specified as a dimensioned quantity", x=x, y=y) + + if is_quantity(width) and is_quantity(height): + self.width = width + self.height = height + else: + raise ValidationError("width and heigh is not specified as a dimensioned quantity", width=width, height=height) + + def __str__(self): + return "SMT Pad at (%s, %s) size: (%s, %s)" % (str(self.x), str(self.y), str(self.width), str(self.height)) + + def __eq__(self, other): + try: + return (self.x == other.x) and (self.y == other.y) and (self.width == other.width) and (self.height == other.height) + except AttributeError: + return False + + def __ne__(self, other): + return not self == other + +class Placement: + """ + A class describing the physical placement and rotation of a PCB component. + """ + + def __init__(self, x, y, rotation=0.0): + """ + Store an x,y location and a rotation (in degrees) + """ + + if is_quantity(x) and is_quantity(y): + self.x = x + self.y = y + else: + raise ValidationError("placement position is not specified as a dimensioned quantity", x=x, y=y) + + self.rotation = rotation + + def __eq__(self, other): + try: + return self.x == other.x and self.y == other.y and self.rotation == other.rotation + except AttributeError: + return False + + def __ne__(self, other): + return not self == other + + def __str__(self): + return "(%s, %s) rotated %d degrees" % (str(self.x), str(self.y), self.rotation) + + +def is_quantity(val): + if hasattr(val, 'magnitude') and hasattr(val, 'units'): + return True + + return False diff --git a/pymomo/pcb/partcache.py b/pymomo/pcb/partcache.py new file mode 100644 index 0000000..9fd0b28 --- /dev/null +++ b/pymomo/pcb/partcache.py @@ -0,0 +1,123 @@ +#partcache.py +#A simple expiring cache built on top of sqlite + +import sys +import os.path + +from pymomo.utilities.paths import MomoPaths +from time import time +import sqlite3 +import cPickle + +overriden_cache = None +overriden_expiry = None + +class PartCache(object): + """ + A simple persistent key-value store with expiration times + + Persistence comes from a sqlite backend and each time the + file is opened, all entries are checked to see if they should + be expired and deleted if so. + """ + + DefaultCachePath = os.path.join(MomoPaths().settings, 'pcb_part_cache.db') + DefaultExpiry = 1*24*60*60 #Default expiration date is 1 day + + def __init__(self, cache=None, no_expire=None, expiration=None): + if cache is None: + if overriden_cache is not None: + cache = overriden_cache + else: + cache = PartCache.DefaultCachePath + + if expiration is None: + expiration = PartCache.DefaultExpiry + else: + expiration = int(expiration) + + self.connection = sqlite3.connect(cache) + self.cursor = self.connection.cursor() + + self.file = cache + self.expiration = expiration + + self._setup_table() + + if no_expire is None: + if overriden_expiry is None or overriden_expiry is False: + self._expire_old() + elif not no_expire: + self._expire_old() + + def _setup_table(self): + query = 'create table if not exists PartCache (key TEXT PRIMARY KEY, created INTEGER, part blob);' + self.cursor.execute(query) + self.connection.commit() + + def _expire_old(self): + query = "delete from PartCache where strftime('%%s','now') - created > %d" % self.expiration + self.cursor.execute(query) + self.connection.commit() + + return self.cursor.rowcount + + def size(self): + """ + Return the number of entries in this cache + """ + + query = 'select count(*) from PartCache' + self.cursor.execute(query) + return self.cursor.fetchone()[0] + + def _check_valid(self, obj): + exp = obj['expiration'] + + cur = time() + + if exp > cur: + return True + + return False + + def get(self, id): + query = 'select part from PartCache where key is ?' + self.cursor.execute(query, (id,)) + + val = self.cursor.fetchone() + if val is None: + raise KeyError("id not in cache: %s", str(id)) + + return cPickle.loads(str(val[0])) + + def try_get(self, id): + try: + return self.get(id) + except KeyError: + return None + + def set(self, id, obj): + now = time() + data = cPickle.dumps(obj) + + query = "insert into PartCache values (?, strftime('%s','now'), ?)" + self.cursor.execute(query, (id, sqlite3.Binary(data))) + self.connection.commit() + + +def default_cachefile(path): + """ + Set the default cache file used to back PartCache objects + """ + + global overriden_cache + overriden_cache = path + +def default_noexpire(do_expire): + """ + Set whether cache should not expire by default + """ + + global overriden_expiry + overriden_expiry = do_expire diff --git a/pymomo/pybom/pricing.py b/pymomo/pcb/pricing.py similarity index 52% rename from pymomo/pybom/pricing.py rename to pymomo/pcb/pricing.py index 863c2c5..3ea94dd 100644 --- a/pymomo/pybom/pricing.py +++ b/pymomo/pcb/pricing.py @@ -17,30 +17,44 @@ def __init__(self, valid_sellers=None, invalid_sellers=[], def _wrap_single(self, s): if (isinstance(s, basestring)): - return [s] + return [self._canonicalize_name(s)] if s is None: return [] - return s + return map(lambda x: self._canonicalize_name(x), s) + + def _canonicalize_name(self, name): + """ + Given a name, like a distributor's name, convert it to + lowercase and remove all spaces,_ or - characters + """ + + if name is None: + return "None" + + return name.lower().replace(' ', '').replace('-', '').replace('_', '') def validate(self, offer, quantity): if offer.invalid: return False - if offer.seller_name in self.invalid_sellers: + if offer.moq is not None and quantity < offer.moq: + return False + + if self._canonicalize_name(offer.seller) in self.invalid_sellers: return False - if offer.packaging in self.invalid_packages: + if self._canonicalize_name(offer.packaging) in self.invalid_packages: return False - if len(self.valid_sellers)>0 and offer.seller_name not in self.valid_sellers: + if len(self.valid_sellers)>0 and self._canonicalize_name(offer.seller) not in self.valid_sellers: return False - if len(self.valid_packages)>0 and offer.packaging not in self.valid_packages: + if len(self.valid_packages)>0 and self._canonicalize_name(offer.packaging) not in self.valid_packages: return False - if self.in_stock and quantity > offer.in_stock_quant: + if self.in_stock and quantity > offer.stock_quantity: return False return True \ No newline at end of file diff --git a/pymomo/pcb/production.py b/pymomo/pcb/production.py new file mode 100644 index 0000000..7e3af92 --- /dev/null +++ b/pymomo/pcb/production.py @@ -0,0 +1,125 @@ +#production.py +#A set of routines for building a set of production files for fabricating and assembling +#circuit boards + +import os.path +import zipfile +from pymomo.exceptions import * +from pymomo.utilities.paths import MomoPaths +from datetime import date +import platform +import subprocess + +class ProductionFileGenerator: + """ + Helpful routines for building production files for PCB fabrication and assembly + """ + + def __init__(self, board): + config = MomoPaths().config + + template_file = os.path.join(config, 'pcb', board.fab_engine + ".json") + self.template = self._load_template(template_file, board.fab_template) + self.board = board + + def save_readme(self, path): + """ + Create a README file for this board based on the information it contains + and the template for the production files. + """ + + basename = self._basename() + + with open(path, "w") as f: + f.write("PCB Fabrication Files\n") + f.write("%s\n" % self.board.company) + f.write("Name: %s\n" % self.board.part) + f.write("Revision: %s\n" % self.board.revision) + f.write("Dimensions: %sx%s %s\n" % (self.board.width, self.board.height, self.board.units)) + f.write("Date Created: %s\n\n" % str(date.today())) + f.write("Folder Contents:\n") + + for name, layer in self.template['layers'].iteritems(): + f.write("%s.%s: %s\n" % (basename, layer['extension'], name)) + + def _build_production(self, outdir, layer): + basename = self._basename() + path = os.path.join(outdir, "%s.%s" %(basename, layer['extension'])) + + + self.board.board.build_production_file(path, layer['program_layers'], layer['type']) + + if 'remove' in layer: + remname = "%s.%s" % (basename, layer['remove']) + os.remove(os.path.join(outdir, remname)) + + def build_fab(self, fab_dir): + """ + Create production Gerber and Excellon files for pcb fabrication + """ + + self._ensure_dir_exists(fab_dir) + self.save_readme(os.path.join(fab_dir, 'README.txt')) + + for name, layer in self.template['layers'].iteritems(): + self._build_production(fab_dir, layer) + + def build_assembly(self, ass_dir): + self._ensure_dir_exists(ass_dir) + self._build_production(ass_dir, self.template['assembly']) + + def build_production(self, variant, output_dir): + basename = self._basename() + + fab_dir = os.path.join(output_dir, 'fabrication') + ass_dir = os.path.join(output_dir, 'assembly') + + #Create fabrication files + self.build_fab(fab_dir) + self.zipfab(fab_dir, os.path.join(output_dir, basename + '_fab')) + + #Create assembly files + self.build_assembly(ass_dir) + + #Create BOM + bompath = os.path.join(ass_dir, "BOM_%s.xlsx" % basename) + self.board.export_bom(bompath, variant, format='excel') + + def _basename(self): + return self.board.part.replace(' ', '_').lower() + + def _ensure_dir_exists(self, output_dir): + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + + def _load_template(self, templatefile, template_name): + """ + Load a list of possible templates for making gerbers from a json file + """ + + import json + with open(templatefile, "r") as f: + templates = json.load(f) + + if "templates" not in templates: + raise DataError("Unknown file format for gerber generation template file", path=templatefile, reason="did not contain a 'templates' key") + + options = templates['templates'] + + if template_name not in options: + raise ArgumentError("Unknown template specified for gerber generation", possible_names=options.keys(), name=template_name) + + return options[template_name] + + def zipfab(self, path, output): + """ + Create a zipfile of the direction path with the name output.zip that will expand into a directory + with the same name as output containing all of the files in path. + """ + + zip = zipfile.ZipFile(output+'.zip', 'w', zipfile.ZIP_DEFLATED) + for root, dirs, files in os.walk(path): + for file in files: + zip.write(os.path.join(root, file), os.path.join(os.path.basename(output), file)) + + zip.close() diff --git a/pymomo/pybom/reference.py b/pymomo/pcb/reference.py similarity index 95% rename from pymomo/pybom/reference.py rename to pymomo/pcb/reference.py index f5230a2..5398a11 100644 --- a/pymomo/pybom/reference.py +++ b/pymomo/pcb/reference.py @@ -7,7 +7,7 @@ import re import sys -from ..utilities.config import ConfigFile +from pymomo.utilities.config import ConfigFile data_file = ConfigFile('pcb_library') @@ -69,7 +69,7 @@ def has_value(self, name): def find_package(self, pkg): """ Look for the footprint in our list of known packages. Return a tuple - of (package name, bool found). + of (package name, bool found). """ for name, val in self.packages.iteritems(): if pkg in val: diff --git a/pymomo/pcb/types/__init__.py b/pymomo/pcb/types/__init__.py new file mode 100644 index 0000000..c98a49c --- /dev/null +++ b/pymomo/pcb/types/__init__.py @@ -0,0 +1,7 @@ +import physical_part +import part_offer +import price_list +import logical_part +import bom_line +import unit_price +import price \ No newline at end of file diff --git a/pymomo/pcb/types/bom_line.py b/pymomo/pcb/types/bom_line.py new file mode 100644 index 0000000..988fdb4 --- /dev/null +++ b/pymomo/pcb/types/bom_line.py @@ -0,0 +1,62 @@ +import collections +import string + +def convert(arg, **kwargs): + if arg is None: + return None + + if isinstance(arg, BOMLine): + return arg + elif isinstance(arg, collections.Sequence) and not isinstance(arg, basestring): + return BOMLine(arg) + + raise ValueError("Creating a BOMLine from any type other than a BOMLine object or list of parts is not supported") + +def default_formatter(arg, **kwargs): + return str(arg) + +class BOMLine: + digits = frozenset(string.digits) + + def __init__(self, parts): + self.parts = parts + + self.count = len(parts) + proto = parts[0] + + self.manu = proto.manu + self.mpn = proto.mpn + self.package = proto.package + self.desc = proto.desc + self.dist = proto.dist + self.distpn = proto.distpn + self.value = proto.value + self.refs = sorted([x.name for x in parts], key=self._ref_number) + self.type = self._extract_ref_type() + + if len(self.refs) > 0: + self.lowest = self._ref_number(self.refs[0]) + else: + self.lowest = 0 + + def _ref_number(self, id): + """ + Extract the numeric part of a reference number + """ + + return int(''.join([c for c in id if c in BOMLine.digits])) + + def _extract_ref_type(self): + if len(self.refs) == 0: + return None + + r = self.refs[0] + + for i in xrange(0, len(r)): + if not r[i].isalpha(): + break + + return r[:i] + + def __str__(self): + return "%d %s: %s" % (self.count, self.desc, self.refs) diff --git a/pymomo/pcb/types/logical_part.py b/pymomo/pcb/types/logical_part.py new file mode 100644 index 0000000..6ab57bf --- /dev/null +++ b/pymomo/pcb/types/logical_part.py @@ -0,0 +1,109 @@ +#logical_part.py + +import re +from pymomo.pcb import reference + +def convert(arg, **kwargs): + if arg is None: + return None + + if isinstance(arg, Part): + return arg + + elif isinstance(arg, dict): + return Part(**arg) + + raise ValueError("Creating a logical_part from any type other than a Part object is not supported") + +def validate_matchable(arg, **kwargs): + if not arg.matchable(): + raise ValueError("part does not have enough metadata to be matchable") + +def default_formatter(arg, **kwargs): + return str(arg) + +class Part: + """ + An electronic component. + """ + + ref = reference.PCBReferenceLibrary() + + def __init__(self, name, package, placement, mpn=None, manu=None, dist=None, distpn=None, value=None, desc=None): + """ + Create a part object from the data passed + """ + + self.name = name + self.package = package + self.mpn = mpn + self.manu = manu + self.value = value + self.distpn = distpn + self.dist = dist + self.placement = placement + self.desc = desc + + #If no description is given try creating a generic one based on the type of the part (resistor, etc) + if self.desc is None: + self.desc = Part.ref.find_description(self.name, self.value) + + def matchable(self): + """ + Return true if this part has enough information to be matched and priced online + """ + + return self.name != None and ((self.manu != None and self.mpn != None) or (self.dist != None and self.distpn != None)) + + def placeable(self): + """ + Return true if this part has enough information to be physically located on a board + """ + + return (self.package != None and self.placement != None) + + def complete(self): + """ + Return true if this part has all required information for BOM matching and placing + """ + + return self.matchable() and self.placeable() + + def unique_id(self): + """ + Return a unique key that can be used to group multiple parts that are identical. + """ + + if self.manu and self.mpn: + return "%s_%s" % (self._canonicalize(self.manu), self._canonicalize(self.mpn)) + + return "%s_%s" % (self._canonicalize(self.dist), self._canonicalize(self.distpn)) + + def _canonicalize(self, string): + """ + Given a string, remove all spaces, - and _ and make it lower case. + """ + + s = string.lower() + return s.replace(' ', '').replace('-','').replace('_','') + + def __str__(self): + string = "" + string += "Part %s" % str(self.name) + '\n' + string += " Manufacturer: " + str(self.manu) + '\n' + string += " MPN: " + str(self.mpn) + '\n' + string += " Distributor: " + str(self.dist) + '\n' + string += " Distributor PN: " + str(self.distpn) + '\n' + string += " Package: " + str(self.package) + '\n' + string += " Location: " + str(self.placement) + '\n' + string += " Description: " + str(self.desc) + + return string + + def __eq__(self, other): + try: + return self.name == other.name and self.package == other.package and self.mpn == other.mpn and \ + self.manu == other.manu and self.value == other.value and self.distpn == other.distpn and \ + self.dist == other.dist and self.placement == other.placement and self.desc == other.desc + except AttributeError: + raise False diff --git a/pymomo/pcb/types/part_offer.py b/pymomo/pcb/types/part_offer.py new file mode 100644 index 0000000..ae46a2a --- /dev/null +++ b/pymomo/pcb/types/part_offer.py @@ -0,0 +1,50 @@ +#partoffer.py +from pymomo.utilities.formatting import indent_block + +def convert(arg, **kwargs): + if isinstance(arg, PartOffer): + return arg + + if not isinstance(arg, dict): + raise ValueError("conversion to part_offer from anything other than dict not supported") + + req = ['seller', 'moq', 'packaging', 'breaks', 'stock_quantity'] + for r in req: + if r not in arg: + raise ValueError('invalid dictionary used to initialize part_offer') + + return PartOffer(arg['seller'], arg['packaging'], arg['breaks'], arg['stock_quantity'], arg['moq']) + +def default_formatter(arg, **kwargs): + return str(arg) + +class PartOffer: + def __init__(self, seller, packaging, breaks, stock, moq): + self.seller = seller + self.packaging = packaging + self.invalid = False + + self.breaks = breaks + self.stock_quantity = stock + + if moq is not None: + self.moq = moq + elif len(self.breaks.breaks) > 0: + self.moq = self.breaks.breaks[0][0] + else: + self.moq = None + + def best_price(self, quantity): + if self.invalid: + return None + + return self.breaks.unit_price(quantity) + + def __str__(self): + res = "Seller: %s\n Packaging: %s\n Stock: %d\n Minimum Order: %s" % (self.seller, self.packaging, self.stock_quantity, str(self.moq)) + breakstr = str(self.breaks) + if breakstr != "": + res += "\n Breaks\n" + res += indent_block(breakstr, 4) + + return res diff --git a/pymomo/pcb/types/physical_part.py b/pymomo/pcb/types/physical_part.py new file mode 100644 index 0000000..2641138 --- /dev/null +++ b/pymomo/pcb/types/physical_part.py @@ -0,0 +1,82 @@ +#physical_part.py +#An object representing a unique physical circuit part that can be sold by +#zero or more sellers + +def convert(arg, **kwargs): + if isinstance(arg, PhysicalPart): + return arg + + if not isinstance(arg, dict): + raise ValueError("conversion to physical_part from anything other than dict not supported") + + req = ['manu', 'mpn'] + for r in req: + if r not in arg: + raise ValueError('invalid dictionary used to initialize physical_part') + + desc = None + if 'desc' in arg: + desc = arg['desc'] + + offers = [] + if 'offers' in arg: + offers = arg['offers'] + + return PhysicalPart(arg['manu'], arg['mpn'], offers, desc=desc) + +def default_formatter(arg, **kwargs): + return str(arg) + +class PhysicalPart: + """ + A unique physical circuit component that can be purchased from zero or more suppliers. + This would correspond to a single BOM line on a Bill of Materials for a circuit board. + """ + + def __init__(self, manu, mpn, offers, desc=None): + self.offers = offers + self.manu = manu + self.mpn = mpn + self.desc = desc + + def best_price(self, quantity, requirements): + valid_offers = filter(lambda x: requirements.validate(x, quantity), self.offers) + prices = map(lambda x: (x.best_price(quantity), x), valid_offers) + valid_prices = filter(lambda x: x[0] is not None, prices) + + sorted_prices = sorted(valid_prices, key=lambda x:x[0]) + + if len(sorted_prices) > 0: + return sorted_prices[0] + + return None + + def __str__(self): + manu = self.manu + mpn = self.mpn + desc = self.desc + + #Make sure we get rid of non-ascii characters in case this + #string is printed since it would choke. + if isinstance(manu, unicode): + manu = manu.encode('ascii','ignore') + if isinstance(mpn, unicode): + mpn = mpn.encode('ascii','ignore') + if isinstance(desc, unicode): + desc = desc.encode('ascii','ignore') + + header = "Manufacturer: %s\nMPN: %s" % (manu, mpn) + + if self.desc is not None: + header += "\nDescription: %s" % desc + + offers = map(str, self.offers) + + if len(offers) > 0: + offerstr = "\n".join(offers) + offerstr = " " + offerstr.replace('\n', '\n ') + header += '\nOffers\n' + header += offerstr + + return header + \ No newline at end of file diff --git a/pymomo/pcb/types/price.py b/pymomo/pcb/types/price.py new file mode 100644 index 0000000..a59baf6 --- /dev/null +++ b/pymomo/pcb/types/price.py @@ -0,0 +1,15 @@ +#price.py +#An object encapsulating the price of something + +from decimal import Decimal + +two_places = Decimal('0.01') + +def convert(arg, **kwargs): + if isinstance(arg, Decimal): + return arg + + return Decimal(arg) + +def default_formatter(arg, **kwargs): + return str(arg.quantize(two_places)) diff --git a/pymomo/pybom/octopart/prices.py b/pymomo/pcb/types/price_list.py similarity index 52% rename from pymomo/pybom/octopart/prices.py rename to pymomo/pcb/types/price_list.py index 9253eb8..9ebcd5b 100644 --- a/pymomo/pybom/octopart/prices.py +++ b/pymomo/pcb/types/price_list.py @@ -3,22 +3,24 @@ from decimal import Decimal -class PriceList: - def __init__(self, octo_pricelist): - """ - Create from an octopart PriceList json object - Assume that we want to work in USD - """ +def convert(arg, **kwargs): + if isinstance(arg, PriceList): + return arg - self.breaks = [] + if isinstance(arg, basestring): + raise ValueError("Converting to price_list from string is not yet supported") - if 'USD' not in octo_pricelist: - raise ValueError('USD Not in Price Object') + return PriceList(arg) - plist = octo_pricelist['USD'] +def default_formatter(arg, **kwargs): + return str(arg) - for i in xrange(0, len(plist)): - q,val = plist[i] +class PriceList: + def __init__(self, breaks): + self.breaks = [] + + for i in xrange(0, len(breaks)): + q,val = breaks[i] price = Decimal(val) self.breaks.append((q, price)) @@ -41,4 +43,9 @@ def total_price(self, quantity): if up is None: return None - return quantity*up \ No newline at end of file + return quantity*up + + def __str__(self): + breaklist = map(lambda x: "%d: %s" % (x[0], x[1]), self.breaks) + + return "\n".join(breaklist) \ No newline at end of file diff --git a/pymomo/pcb/types/unit_price.py b/pymomo/pcb/types/unit_price.py new file mode 100644 index 0000000..983fae4 --- /dev/null +++ b/pymomo/pcb/types/unit_price.py @@ -0,0 +1,26 @@ +#unit_price.py +#An object encapsulating a list of pricebreaks + +from decimal import Decimal, getcontext + +def convert(arg, **kwargs): + if isinstance(arg, UnitPrice): + return arg + + if isinstance(arg, basestring): + raise ValueError("Converting to price_list from string is not yet supported") + + return UnitPrice(**arg) + +def default_formatter(arg, **kwargs): + return str(arg) + +class UnitPrice: + def __init__(self, price, offer): + self.price = price + self.offer = offer + + def __str__(self): + tp = Decimal('0.01') + pricestr = str(self.price.quantize(tp)) + return "$%s from %s as %s" % (pricestr, self.offer.seller, self.offer.packaging) diff --git a/pymomo/pybom/board.py b/pymomo/pybom/board.py deleted file mode 100644 index 46f9759..0000000 --- a/pymomo/pybom/board.py +++ /dev/null @@ -1,415 +0,0 @@ -#board.py - -import csv -import itertools -from part import Part -from xml.etree import ElementTree -from datetime import date -from octopart.identifier import PartIdentifier -from octopart.octopart import Octopart -from reference import PCBReferenceLibrary -from package import Package - -class Board: - def __init__(self, name, file, variants, partname, width, height, revision, unknown, nopop): - """ - Create a Board Object with 1 or more assembly variants from the variant dictionary passed in. - """ - - self.name = name - self.file = file - self.variants = {varname: self._process_variant(var) for (varname,var) in variants.iteritems()} - self.partname = partname - self.width = width - self.height = height - self.revision = revision - self.unknown = unknown - self.no_pop = nopop - - def list_variants(self): - print "Assembly Variants" - - for k in self.variants.iterkeys(): - print k - - def get_ids(self, variant=None): - """ - Return a set containing all of the reference identifiers (C1, R7, etc.) in - the variant passed, or in all variants if no variant is passed - """ - - if variant is None: - vars = self.variants.keys() - else: - vars = [variant] - - ids = set() - - for var in vars: - for line in self.variants[var]: - refs = map(lambda x: x.name, line) - ids.update(refs) - - #If we're asked for all of the ids on this board, also include those that - #may never be populated - if variant is None: - ids.update(self.no_pop) - - return ids - - def print_variant(self, key): - var = self.variants[key] - - print "Variant", key - for v in var: - print map(lambda x: x.name, v) - - def get_unique_parts(self): - parts = {} - - for v in self.variants.iterkeys(): - for line in self.variants[v]: - part = PartIdentifier(line[0]) - parts[part.build_reference()] = part - - return parts - - def lookup_parts(self): - parts = self.get_unique_parts() - - op = Octopart() - - self.oracle = op.match_identifiers(parts.values()) - - def price_variant(self, variant, multiplier, requirements=None): - if variant not in self.variants: - raise ValueError("Invalid variant passed to price_variant") - - self.lookup_parts() - unmatched = [] - - prices = [] - - for line in self.variants[variant]: - id = PartIdentifier(line[0]).build_reference() - - if id not in self.oracle: - unmatched.append(line) - continue - - quant = int(len(line)*multiplier + 0.5) #round up - unit_price = self.oracle[id].best_price(quant, requirements) - - if unit_price is None: - unmatched.append(line) - continue - - prices.append((line, unit_price)) - - return (prices, unmatched) - - def export_list(self, variant): - """ - Return an array with all of the digikey part numbers in this variant so that - it can be concatenated and used to order parts - """ - - export = [] - ignored = [] - - for line in self.variants[variant]: - pn = line[0].digipn - if pn is None or pn == "": - ignored.append(line[0].name) - continue - - export += [pn]*len(line) - - print "Ignoring Parts" - print ignored - - return export - - def variant_data(self, variant, include_costs=False, cost_quantity=1, requirements=None): - """ - Return a dictionary describing this variant, suitable for using to fill a template. - Create an array of BOM line items corresponding to this assembly variant - """ - - res = {} - - if include_costs: - self.lookup_parts() - res['include_costs'] = True - res['cost_quantity'] = cost_quantity - else: - res['include_costs'] = False - - var = self.variants[variant] - lib = PCBReferenceLibrary() - today = date.today() - - res['company'] = "WellDone International" - res['part'] = self.partname - res['variant'] = variant - res['revision'] = self.revision - res['date'] = today.strftime('%x') - res['lines'] = [] - for line in var: - num = len(line) - refs = map(lambda x: x.name, line) - foot = lib.find_package(line[0].package)[0] - value = line[0].value - manu = line[0].manu - mpn = line[0].mpn - distpn = line[0].digipn - dist = "" - descr = line[0].desc - if distpn: - dist = "Digikey" - - unitprice = "" - lineprice = "" - - if include_costs: - quantity = num*cost_quantity - id = PartIdentifier(line[0]) - if id.build_reference() in self.oracle: - price = self.oracle[id.build_reference()].best_price(quantity, requirements) - if price is not None: - unitprice = price[0] - lineprice = num*unitprice - - ldict = {} - ldict['qty'] = num - ldict['refs'] = refs - ldict['footprint'] = foot - ldict['value'] = value - ldict['manu'] = manu - ldict['mpn'] = mpn - ldict['dist'] = dist - ldict['description'] = descr - ldict['distpn'] = distpn - ldict['unitprice'] = unitprice - ldict['lineprice'] = lineprice - ldict['pkg_info'] = line[0].pkg_info - - res['lines'].append(ldict) - - return res - - def export_bom(self, variant, stream, include_costs=False, cost_quantity=1, requirements=None): - var = self.variants[variant] - - lib = PCBReferenceLibrary() - - lineno = 1 - - today = date.today() - - if include_costs: - self.lookup_parts() - - if isinstance(stream, str): - bom = open(stream, "wb") - close = True - else: - bom = stream - close = False - - writ = csv.writer(bom) - - writ.writerow(["WellDone"]) - writ.writerow(["BOM: %s (%s)" % (self.partname, variant)]) - writ.writerow(["Revision: %s" % self.revision]) - writ.writerow(['Date Created: %s' % (today.strftime('%x'))]) - - if include_costs: - writ.writerow(['Costs calculated for %d units' % cost_quantity]) - - writ.writerow([]) - writ.writerow([]) - - headers = ["Item", "Qty", "Reference Design", "Value", "Footprint", "Description", "Manufacturer", "Manu. Part", "Distributor", "Dist. Part"] - if include_costs: - headers += ['Unit Price', 'Line Price'] - - writ.writerow(headers) - - for line in var: - num = len(line) - refs = ", ".join(map(lambda x: x.name, line)) - foot = lib.find_package(line[0].package)[0] - value = line[0].value - manu = line[0].manu - mpn = line[0].mpn - distpn = line[0].digipn - dist = "" - descr = line[0].desc - if distpn: - dist = "Digikey" - - row = [lineno, num, refs, value, foot, descr, manu, mpn, dist, distpn] - - if include_costs: - quantity = num*cost_quantity - id = PartIdentifier(line[0]) - if id.build_reference() in self.oracle: - price = self.oracle[id.build_reference()].best_price(quantity, requirements) - if price is not None: - unitprice = price[0] - lineprice = num*unitprice - - row += [str(unitprice), str(lineprice)] - - writ.writerow(row) - - lineno += 1 - - #If there are unpopulated parts, list them here - unpopulated = self.get_ids() - self.get_ids(variant) - - if len(unpopulated) > 0: - writ.writerow([]) - - s = ", ".join(unpopulated) - writ.writerow(["Unpopulated Parts", s]) - - if close: - bom.close() - - @staticmethod - def FromEagle(brd_file): - """ - Create a Board from EAGLE Board file by parsing attribute tags embedded with the - components. Create different assembly variants for each different configuration - specified in the file - """ - - tree = ElementTree.parse(brd_file) - if tree is None: - raise ValueError("File %s is not a valid XML file" % brd_file) - - root = tree.getroot() - - #Find all of the defined packages in this file - packages = {x.name: x for x in map(lambda x: Package(x), root.findall('.//package'))} - - elems = root.find("./drawing/board/elements") - if elems is None: - raise ValueError("File %s does not contain a list of elements. This is an invalid board file" % brd_file) - - parts = list(elems) - - unknown = [] - nopop = [] - constant = [] - variable = [] - variants = find_variants(root) - - all_vars = set(variants) - - #Create a part object for each part that has a valid digikey-pn - #If there are multiple digikey part numbers, create a part for each one - for part in parts: - valid_part = False - found_vars = set() - nopop_vars = set() - - for v in variants: - p,found = Part.FromBoardElement(part, v, packages) - if p and found: - variable.append( (v, p) ) - found_vars.add(v) - valid_part = True - elif found: - #If this part has digipn == "" or Populate=no, then it should not be populated in this variant - valid_part = True - nopop_vars.add(v) - nopop.append(part.get('name',"Unknown Name")) - - #See if this is a constant part (with no changes in different variants) - p,found = Part.FromBoardElement(part, "", packages) - if p and len(found_vars) == 0 and len(nopop_vars) == 0: - constant.append(p) - valid_part = True - elif p and len(found_vars) != len(all_vars): - #there are some variants that want the default part and some that want a change - for var in (all_vars - found_vars) - nopop_vars: - variable.append( (var, p) ) - valid_part = True - - #Make sure this part has information for at least one assembly variant - if not valid_part: - unknown.append(part.get('name',"Unknown Name")) - - vars = {} - #Create multiple variants - for var in variants: - vars[var] = constant + filter_sublists(variable, var) - - return Board("test", 'test', vars, - partname=find_attribute(root, 'PARTNAME'), - width=find_attribute(root, 'WIDTH'), - height=find_attribute(root, 'HEIGHT'), - revision=find_attribute(root, 'REVISION'), - unknown=unknown, - nopop=nopop) - - def _process_variant(self, parts): - """ - Group items by unique key and return tuples of identical parts - """ - - bomitems = [] - - sparts = sorted(parts, key=lambda x:x.unique_id()) - for k,g in itertools.groupby(sparts, lambda x:x.unique_id()): - bomitems.append(list(g)) - - return bomitems - - -def remove_prefix(s, prefix): - if not s.startswith(prefix): - return s - - return s[len(prefix):] - -def filter_sublists(master, key): - """ - Given a list of lists where each of the sublist elements are tuples (key, value), - return a concatenated list of all values in tuples that match key - """ - - concat = itertools.chain(master) - - filtered = filter(lambda x: x[0] == key, concat) - - return map(lambda x:x[1], filtered) - -def get_variant_id(pn_name): - known_prefixes = ['DIGIKEY-PN'] - - for p in known_prefixes: - pn_name = remove_prefix(pn_name, p) - - if pn_name.startswith('-'): - pn_name = pn_name[1:] - - return pn_name - -def find_attribute(root, name): - attr = root.find(".//attribute[@name='%s']" % name) - if attr is None: - raise ValueError("Required global board attribute not found: %s" % name) - - return attr.get('value') - -def find_variants(root): - vars = root.findall(".//attribute[@value='%s']" % 'ASSY-VARIANT') - - if len(vars) == 0: - return ["MAIN"] - else: - return map(lambda x: x.get('name'), vars) \ No newline at end of file diff --git a/pymomo/pybom/octopart/__init__.py b/pymomo/pybom/octopart/__init__.py deleted file mode 100644 index 18cd8bb..0000000 --- a/pymomo/pybom/octopart/__init__.py +++ /dev/null @@ -1 +0,0 @@ -#__init__.py \ No newline at end of file diff --git a/pymomo/pybom/octopart/identifier.py b/pymomo/pybom/octopart/identifier.py deleted file mode 100644 index 08b729c..0000000 --- a/pymomo/pybom/octopart/identifier.py +++ /dev/null @@ -1,27 +0,0 @@ -#identifier.py - -class PartIdentifier: - def __init__(self, part=None, digipn=None): - - if digipn is not None: - self.sku = digipn - self.mpn = None - self.manu = None - else: - self.sku = part.digipn - self.mpn = part.mpn - self.manu = part.manu - - def build_reference(self): - if self.sku: - return "__SKU__" + self.sku - - else: - return "__MPN__" + str(self.mpn) - - def build_query(self): - if self.sku is not None: - return {'sku': self.sku, 'reference': self.build_reference()} - - else: - return {'mpn': self.mpn, 'reference': self.build_reference()} diff --git a/pymomo/pybom/octopart/octopart.py b/pymomo/pybom/octopart/octopart.py deleted file mode 100644 index 2f9916f..0000000 --- a/pymomo/pybom/octopart/octopart.py +++ /dev/null @@ -1,94 +0,0 @@ -#octopart.py -#Wrapper arround Octopart REST API - -import os -import json -import urllib -import pprint -from physicalpart import PhysicalPart -import sys -from partcache import PartCache - -from pymomo.utilities.paths import MomoPaths - -class Octopart: - URL = 'http://octopart.com/api/v3/' - - def __init__(self, key=None): - if key is not None: - self.api_key = key - elif 'OCTOPART_KEY' in os.environ: - self.api_key = os.environ['OCTOPART_KEY'] - else: - raise ValueError('You need to either specify an Octopart API Key or have OCTOPART_KEY set in the shell') - - self.cache = PartCache() - - def _build_call(self, method, args): - """ - Build a URL call with arg=val pairs including the apikey to the url specifying the method - given. - """ - - url = Octopart.URL + method + '?' - - out_args = [] - out_args.extend(args) - out_args.append(('apikey', self.api_key)) - out_strings = map(lambda x: urllib.quote(x[0])+'='+urllib.quote(x[1]), out_args) - - query_string = "&".join(out_strings) - - return url + query_string - - def _call(self, url): - data = urllib.urlopen(url).read() - response = json.loads(data) - - return response - - def _call_method(self, method, args): - url = self._build_call(method, args) - return self._call(url) - - def match_identifiers(self, skus): - matched = {} - unmatched = [] - uncached_skus = [] - - #Check if we have these parts in our cache - for sku in skus: - id = sku.build_reference() - part = self.cache.get(id) - - if part is None: - uncached_skus.append(sku) - else: - matched[id] = part - - #Otherwise fetch from Octopart and cache the results - for i in xrange(0, len(uncached_skus), 20): - req = uncached_skus[i:i+20] - queries = map(lambda x: x.build_query(), req) - - query = json.dumps(queries) - - resp = self._call_method('parts/match',[('queries',query)]) - - for result in resp['results']: - ref = result['reference'] - - if len(result['items']) == 0: - unmatched.append(ref) - print "Did not match %s" % ref - continue - - if len(result['items']) > 1: - print "Found %d parts for %s" % (len(result['items']), ref) - - part = PhysicalPart(result['items'][0]) - self.cache.set(ref, part) - - matched[ref] = part - - return matched \ No newline at end of file diff --git a/pymomo/pybom/octopart/partcache.py b/pymomo/pybom/octopart/partcache.py deleted file mode 100644 index d6921bc..0000000 --- a/pymomo/pybom/octopart/partcache.py +++ /dev/null @@ -1,60 +0,0 @@ -#partcache.py -#A simple expiring cache built on top of zodb - -import sys -import os.path - -from pymomo.utilities.paths import MomoPaths -from ZODB.FileStorage import FileStorage -from ZODB.DB import DB -from time import time -import transaction - -class PartCache(object): - URL = 'http://octopart.com/api/v3/' - CachePath = os.path.join(MomoPaths().config, 'part_lookup.cache') - DefaultExpiry = 10*24*60*60 #Default expiration date is 10 days - - def __init__(self): - storage = FileStorage(PartCache.CachePath) - self.db = DB(storage) - self.connection = self.db.open() - self.root = self.connection.root() - - def _check_valid(self, obj): - exp = obj['expiration'] - - cur = time() - - if exp > cur: - return True - - return False - - def get(self, id): - if id not in self.root: - return None - - obj = self.root[id] - - if self._check_valid(obj): - obj['last_used'] = time() - self.root[id] = obj - transaction.commit() - return obj['data'] - - #If it's expired, delete it - del self.root[id] - return None - - def set(self, id, obj, expire=None): - if expire is None: - expire = PartCache.DefaultExpiry - - entry = {} - entry['expiration'] = time() + expire - entry['data'] = obj - entry['last_used'] = time() - - self.root[id] = entry - transaction.commit() \ No newline at end of file diff --git a/pymomo/pybom/octopart/partoffer.py b/pymomo/pybom/octopart/partoffer.py deleted file mode 100644 index b69ddd0..0000000 --- a/pymomo/pybom/octopart/partoffer.py +++ /dev/null @@ -1,30 +0,0 @@ -#partoffer.py -#Class encapsulating an Octopart PartOffer response, exposing the -#price breaks - -from prices import PriceList -import utilities - -class PartOffer: - def __init__(self, response): - utilities.assert_class(response, 'PartOffer') - - self.seller_name = response['seller']['name'] - self.seller_uid = response['seller']['uid'] - self.packaging = response['packaging'] - self.invalid = False - - try: - self.breaks = PriceList(response['prices']) - except ValueError, e: - self.breaks = None - self.invalid = True - - self.in_stock_quant = response['in_stock_quantity'] - - - def best_price(self, quantity): - if self.invalid: - return None - - return self.breaks.unit_price(quantity) \ No newline at end of file diff --git a/pymomo/pybom/octopart/physicalpart.py b/pymomo/pybom/octopart/physicalpart.py deleted file mode 100644 index 0ee88e7..0000000 --- a/pymomo/pybom/octopart/physicalpart.py +++ /dev/null @@ -1,23 +0,0 @@ -#physicalpart.py -#A class representing a Part in the Octopart API - -from partoffer import PartOffer -import utilities - -class PhysicalPart: - def __init__(self, response): - utilities.assert_class(response, 'Part') - - self.offers = map(lambda x: PartOffer(x), response['offers']) - - def best_price(self, quantity, requirements): - valid_offers = filter(lambda x: requirements.validate(x, quantity), self.offers) - prices = map(lambda x: (x.best_price(quantity), x.seller_name, x.packaging), valid_offers) - valid_prices = filter(lambda x: x[0] is not None, prices) - - sorted_prices = sorted(valid_prices, key=lambda x:x[0]) - - if len(sorted_prices) > 0: - return sorted_prices[0] - - return None \ No newline at end of file diff --git a/pymomo/pybom/octopart/utilities.py b/pymomo/pybom/octopart/utilities.py deleted file mode 100644 index 0366475..0000000 --- a/pymomo/pybom/octopart/utilities.py +++ /dev/null @@ -1,5 +0,0 @@ -#utilities.py - -def assert_class(resp, classname): - if '__class__' not in resp or resp['__class__'] != classname: - raise ValueError('Creating class from invalid response dictionary') \ No newline at end of file diff --git a/pymomo/pybom/package.py b/pymomo/pybom/package.py deleted file mode 100644 index 550ba49..0000000 --- a/pymomo/pybom/package.py +++ /dev/null @@ -1,18 +0,0 @@ - -class Package: - """ - A pcb component package containing the number of pads and pins in the package - as well as its name. - """ - - def __init__(self, element): - if element.tag != 'package': - raise TypeError('You must pass an ElementTree element to Package.__init__') - - self.name = element.get('name', default="Unknown Name") - - pads = element.findall('./smd') - self.num_pads = len(pads) - - pins = element.findall('./pad') - self.num_pins = len(pins) \ No newline at end of file diff --git a/pymomo/pybom/part.py b/pymomo/pybom/part.py deleted file mode 100644 index ca2de49..0000000 --- a/pymomo/pybom/part.py +++ /dev/null @@ -1,159 +0,0 @@ -#part.py - -import re -import reference - -known_types = { - "C": "capacitor", - "R": "resistor", - "D": "diode", - "JP": "connector", - "U": "ic", - "XTAL": "crystal", - "L": "inductor", - "LED": "led" -} - -class_table = { - "" -} - -class Part: - """ - An electronic component. - """ - - ref = reference.PCBReferenceLibrary() - - @staticmethod - def FromBoardElement(elem, variant, packages): - """ - Create a Part object from an element in an EAGLE board file. - pn_attrib specifies the attribute name that contains the correct - manufacturer or distributor part number for this part. - - @TODO: - - parse pn_attrib to determine whether its a distributor pn or a manu pn - and set attributes accordingly - """ - - pkg = elem.get('package', 'Unknown Package') - pkg_info = None - - if pkg in packages: - pkg_info = packages[pkg] - - name = elem.get('name', 'Unnamed') - value = elem.get('value', 'No Value') - - #allow overriding this package with a custom attribute - pkg = find_attribute(elem, 'FOOTPRINT', variant, pkg) - desc = find_attribute(elem, 'DESCRIPTION', variant, None) - pop_attr = find_attribute(elem, 'POPULATE', variant, "yes") - - if pop_attr.lower() == "no": - return None, True - elif pop_attr != "yes": - raise ValueError("Unknown value in POPULATE attribute in element %s: %s" % (name, pop_attr)) - - #Only keep the value for part types where that is meaningful - if not Part.ref.has_value(name): - value = None - - (manu, mpn) = find_mpn(elem, variant) - digipn = find_digipn(elem, variant) - - if (mpn is not None and manu is not None) or (digipn is not None and digipn != ""): - return Part(name, pkg, digipn=digipn, mpn=mpn, manu=manu, value=value, desc=desc, pkg_info=pkg_info), True - elif mpn == "" or digipn == "": - return None, True - - return None, False - - def __init__(self, name, package, mpn=None, manu=None, digipn=None, value=None, desc=None, pkg_info=None): - """ - Create a part object from the data passed - """ - - self.name = name - self.package = package - self.mpn = mpn - self.manu = manu - self.value = value - self.digipn = digipn - self.desc = desc - self.pkg_info = pkg_info - - #If no description is given try creating a generic one based on the type of the part (resistor, etc) - if self.desc is None: - self.desc = Part.ref.find_description(self.name, self.value) - - def _parse_type(self, type): - refs = type.split(',') - alpha_pat = re.compile('[a-zA-Z]+') - - prefix = re.match(alpha_pat, refs[0]) - - if prefix is None: - return "unknown" - - def unique_id(self): - """ - Return a unique key that can be used to group multiple parts that are identical. - """ - - if self.manu and self.mpn: - return "%s_%s" % (self.manu, self.mpn) - - return "Digikey_%s" % self.digipn - - def package_info(self): - if self.pkg_info is None: - return {'pins': 0, 'pads': 0} - - return {'pins': self.pkg_info.num_pins, 'pads': self.pkg_info.num_pads} - -def attrib_name(name, variant): - if variant == "MAIN" or variant == "": - return name - - return name + '-' + variant - -def find_attribute(part, name, variant, default=None): - """ - Find the attribute corresponding the given variant, or if that doesn't exist, - the value corresponding to MAIN, otherwise return the given default - """ - - attrib_var = part.find("./attribute[@name='%s']" % attrib_name(name, variant)) - attrib_main = part.find("./attribute[@name='%s']" % attrib_name(name, 'MAIN')) - - if attrib_var is not None: - return attrib_var.get('value') - - if attrib_main is not None: - return attrib_main.get('value') - - return default - -def find_mpn(part, variant): - """ - See if this part has the variables MPN[-variant] and MANU[-variant] defined - return None if either is undefined. Otherwise return the tuple (manu, mpn) - """ - - mpn = part.find("./attribute[@name='%s']" % attrib_name('MPN', variant)) - manu = part.find("./attribute[@name='%s']" % attrib_name('MANU', variant)) - - if mpn is None or manu is None: - return (None, None) - - return (manu.get('value', 'Unknown'), mpn.get('value', 'Unknown')) - -def find_digipn(part, variant): - pn_elem = part.find("./attribute[@name='%s']" % attrib_name('DIGIKEY-PN', variant)) - - if pn_elem is None: - return None - - return pn_elem.get('value', "") \ No newline at end of file diff --git a/pymomo/pyeagle.py b/pymomo/pyeagle.py deleted file mode 100644 index 884fab1..0000000 --- a/pymomo/pyeagle.py +++ /dev/null @@ -1,181 +0,0 @@ -#pyeagle.py - -#Routines that encapsulate the use of EAGLE to automatically generate CAM files -#and assembly drawings. - -import shutil -import subprocess -import os.path -import os -from pybom.board import Board -import zipfile -import utilities.config - -settings = utilities.config.ConfigFile('settings') - -def execute_eagle(args): - eagle = 'eagle' - - with open(os.devnull, 'wb') as DEVNULL: - subprocess.check_call([eagle] + args, stdout=DEVNULL, stderr=DEVNULL) - -def export_image(board, output, layers): - process_section(board, output, 'PS', layers) - -def process_section(board, output, type, layers): - """ - Call the EAGLE program to process a board file and produce a - CAM output with the given layers on it. - - @param type can be either "gerber" or "excellon" to generate - either Gerber 274X files or excellon drill files - @param output complete path to the output file location - @param layers the EAGLE layers to include in this CAM file - @param board the complete path to the board file - """ - - #Options from EAGLE's built in gerber file for producing 2 layer boards - #and argument names from EAGLE -? command - args = ['-X', '-f+', '-c+', '-O+'] - - if type == 'gerber': - args.append('-dGERBER_RS274X') - remext = '.gpi' - elif type == 'excellon': - args.append('-dEXCELLON') - remext = '.dri' - elif type == 'PS': - args.append('-dPS') - args.append('-h11') - args.append('-w7.75') - args.append('-s2.5') - else: - raise ValueError("Invalid type specified (must be gerber or excellon), was: %s" % type) - - args.append('-o%s' % output) - - args.append(board) - - for layer in layers: - args.append(layer) - - execute_eagle(args) - - #if it's gerber, remove gpi file - #if it's excellon, remove drd file - if type is not 'PS': - (base, ext) = os.path.splitext(output) - remfile = base + remext - - os.remove(remfile) - -def process_2layer(board, output_dir, basename, paste=False): - """ - Process the eagle file board specified to produce the correct gerber files for - fabrication and assembly. All output files will have the same basename with - different extensions - """ - - top_silk = os.path.join(output_dir, basename + '.plc') - top_copper = os.path.join(output_dir, basename + '.cmp') - bot_copper = os.path.join(output_dir, basename + '.sol') - bot_mask = os.path.join(output_dir, basename + '.sts') - top_mask = os.path.join(output_dir, basename + '.stc') - drill = os.path.join(output_dir, basename + '.drd') - top_cream = os.path.join(output_dir, basename + '.crm') - - #Make sure the output dir exists - ensure_dir_exists(output_dir) - - process_section(board, top_copper, 'gerber', ['Top', 'Pads', 'Vias']) - process_section(board, bot_copper, 'gerber', ['Bottom', 'Pads', 'Vias']) - process_section(board, top_silk, 'gerber', ['Dimension', 'tPlace', 'tNames']) - process_section(board, top_mask, 'gerber', ['tStop']) - process_section(board, bot_mask, 'gerber', ['bStop']) - process_section(board, drill, 'excellon', ['Drills', 'Holes']) - - if paste: - process_section(board, top_cream, 'gerber', ['tCream']) - -def ensure_dir_exists(output_dir): - if not os.path.isdir(output_dir): - os.makedirs(output_dir) - -def create_readme(output_dir, basename, brd_obj, paste=False): - with open(os.path.join(output_dir, 'README.txt'), "w") as f: - f.write('WellDone\n') - f.write("PCB Fabrication Files\n") - f.write("Name: %s\n" % brd_obj.partname) - f.write("Revision: %s\n" % brd_obj.revision) - f.write("Dimensions: %sx%s inches\n" % (brd_obj.width, brd_obj.height)) - f.write("Contact: Tim Burke \n\n") - f.write("Folder Contents:\n") - f.write('%s: Top Silkscreen\n' % (basename+'.plc')) - f.write('%s: Top Copper\n' % (basename+'.cmp')) - f.write('%s: Top Soldermask\n' % (basename+'.stc')) - f.write('%s: Bottom Soldermask\n' % (basename+'.sts')) - f.write('%s: Bottom Copper\n' % (basename+'.sol')) - - if paste: - f.write('%s: Top Cream\n' % (basename+'.crm')) - - f.write('%s: Excellon Drill File\n' % (basename+'.drd')) - -def build_assembly_drawing(board, output): - """ - Create an assembly drawing for this board. File created will by a PS file - """ - - export_image(board, output, ['tPlace', 'tNames', 'tDocu', 'Document', 'Reference','Dimension']) - -def build_production(board, output_dir, paste=False): - """ - Build the set of production files associated with this EAGLE board file. - Directory structure will be: - output_dir - - fabrication - + CAM files - + README - - basename_fab.zip - - assembly - + basename_bom.csv - + basename_drawing.ps - """ - - board_obj = Board.FromEagle(board) - - basename = board_obj.partname - - fab_dir = os.path.join(output_dir, 'fabrication') - ass_dir = os.path.join(output_dir, 'assembly') - - #Ensure old fabrication and assembly files are removed - if os.path.isdir(fab_dir): - shutil.rmtree(fab_dir) - if os.path.isdir(ass_dir): - shutil.rmtree(ass_dir) - - #Create fabrication files - process_2layer(board, fab_dir, basename, paste=paste) - create_readme(fab_dir, basename, board_obj, paste=paste) - zipfab(fab_dir, os.path.join(output_dir, basename + '_fab')) - - #Create assembly files - ensure_dir_exists(ass_dir) - build_assembly_drawing(board, os.path.join(ass_dir, basename + '_drawing.ps')) - - #Build a BOM for each assembly variant - for var in board_obj.variants.keys(): - board_obj.export_bom(var,os.path.join(ass_dir, basename + "_" + var + "_bom.csv")) - -def zipfab(path, output): - """ - Create a zipfile of the direction path with the name output.zip that will expand into a directory - with the same name as output containing all of the files in path. - """ - zip = zipfile.ZipFile(output+'.zip', 'w', zipfile.ZIP_DEFLATED) - for root, dirs, files in os.walk(path): - for file in files: - zip.write(os.path.join(root, file), os.path.join(os.path.basename(output), file)) - - zip.close() \ No newline at end of file diff --git a/pymomo/scripts/modtool.py b/pymomo/scripts/modtool.py index 492073c..d1bceec 100644 --- a/pymomo/scripts/modtool.py +++ b/pymomo/scripts/modtool.py @@ -10,6 +10,7 @@ from pymomo.commander.meta import * from pymomo.commander.exceptions import * from pymomo.hex16.convert import * +from pymomo.utilities.typedargs import type_system import cmdln from colorama import Fore, Style import pytest @@ -430,5 +431,6 @@ def error(self, text): sys.exit(1) def main(): + type_system.interactive = True modtool = ModTool() return modtool.main() \ No newline at end of file diff --git a/pymomo/scripts/momo.py b/pymomo/scripts/momo.py index e322b66..69dde6e 100644 --- a/pymomo/scripts/momo.py +++ b/pymomo/scripts/momo.py @@ -6,11 +6,10 @@ from pymomo.exceptions import * from pymomo.utilities.typedargs import annotate from pymomo.commander.meta import initialization -from pymomo.hex import ControllerBlock, HexFile -from pymomo.sim.simulator import Simulator from pymomo.utilities import build from pymomo.utilities.rcfile import RCFile -import pymomo.syslog +from multiprocessing import freeze_support +import traceback def main(): line = sys.argv[1:] @@ -20,29 +19,32 @@ def main(): norc = True line = line[1:] - if len(line) > 0 and line[0] == '--rcdir': + if len(line) > 0 and line[0] == '--rcfile': rc = RCFile('momo') print rc.path return 0 shell = HierarchicalShell('momo', no_rc=norc) + shell.root_update(annotate.find_all(initialization)) - shell.root_update(annotate.find_all(build)) - name,con = annotate.context_from_module(pymomo.syslog) - shell.root_add(name, con) - shell.root_add('ControllerBlock', ControllerBlock) - shell.root_add('HexFile', HexFile) - shell.root_add('Simulator', Simulator) + shell.root_add("build", "pymomo.utilities.build,build") + shell.root_add("SystemLog", "pymomo.syslog") + shell.root_add("pcb", "pymomo.pcb") + shell.root_add('ControllerBlock', "pymomo.hex,ControllerBlock") + shell.root_add('HexFile', "pymomo.hex,HexFile") + shell.root_add('Simulator', "pymomo.sim,Simulator") finished = False try: while len(line) > 0: line, finished = shell.invoke(line) + except APIError: + traceback.print_exc() + return 1 except MoMoException as e: print e.format() - #if the command passed on the command line fails, then we should #just exit rather than drop the user into a shell. return 1 @@ -67,7 +69,7 @@ def complete(text, state): readline.set_completer_delims(' \t\n;') #Handle Mac OS X special libedit based readline #See: http://stackoverflow.com/questions/7116038/python-tab-completion-mac-osx-10-7-lion - if 'libedit' in readline.__doc__: + if readline.__doc__ is not None and 'libedit' in readline.__doc__: readline.parse_and_bind("bind ^I rl_complete") else: readline.parse_and_bind("tab: complete") @@ -86,6 +88,8 @@ def complete(text, state): try: while len(line) > 0: line, finished = shell.invoke(line) + except APIError as e: + traceback.print_exc() except MoMoException as e: print e.format() diff --git a/pymomo/scripts/pcbtool.py b/pymomo/scripts/pcbtool.py index c09ffa2..b583944 100644 --- a/pymomo/scripts/pcbtool.py +++ b/pymomo/scripts/pcbtool.py @@ -82,18 +82,14 @@ def do_bom(self, subcmd, opts, id): reqs = self.build_pricemodel(opts) brd = self.get_board(id) - if opts.format == 'csv': - out,close = self.build_outstream(opts) - brd.export_bom(opts.variant, out, include_costs=opts.prices, cost_quantity=opts.units, requirements=reqs) - if close: - out.close() - elif opts.format == 'pdf' or opts.format == 'html': + if opts.format == 'pdf' or opts.format == 'html': data = brd.variant_data(opts.variant, include_costs=opts.prices, cost_quantity=opts.units, requirements=reqs) - templ = RecursiveTemplate('bom_template.html') - templ.add(data) - formatted = templ.format_temp() + print data + #templ = RecursiveTemplate('bom_template.html') + #templ.add(data) + #formatted = templ.format_temp() - shutil.move(formatted, opts.output) + #shutil.move(formatted, opts.output) @cmdln.option('-v', '--variant', action='append', default=None, help="Print detailed information about the listed assembly variants.") def do_info(self, subcmd, opts, id): @@ -216,20 +212,20 @@ def do_quote(self, subcmd, opts, id): prices, unmatched = brd.price_variant(opts.variant, multiplier, model) for line in unmatched: - print Fore.RED + "\nCould not find:", map(lambda x: x.name, line), Style.RESET_ALL + '\n' + print Fore.RED + "Could not find:", map(lambda x: x.name, line), Style.RESET_ALL print "Price for %d Units with %.0f%% excess" % (units, opts.excess) total_price = 0.0 for i, line in enumerate(prices): parts, offer = line - price = float(offer[0])*multiplier + price = float(offer['price'])*multiplier if opts.lines: - desc = "from %s" % offer[1] - if offer[2] is not None: - desc += " in %s" % (offer[2]) + desc = "from %s" % offer['offer'].seller + if offer['offer'].packaging is not None and offer['offer'].packaging is not "": + desc += " in %s" % (offer['offer'].packaging,) - print "Line %d: $%.2f (%d @ $%.2f) %s" % (i+1, price, len(parts), float(offer[0]), desc), map(lambda x: x.name, parts) + print "Line %d: $%.2f (%d @ $%.2f) %s" % (i+1, price, len(parts), float(offer['price']), desc), map(lambda x: x.name, parts) total_price += price diff --git a/pymomo/syslog/logdefinition.py b/pymomo/syslog/logdefinition.py index 5e604a5..5b4ee0e 100644 --- a/pymomo/syslog/logdefinition.py +++ b/pymomo/syslog/logdefinition.py @@ -65,10 +65,10 @@ def is_valid_length(self, params): return True def add_data(self, name, type, format, is_list=False): - if not typedargs.is_known_type(type): + if not typedargs.type_system.is_known_type(type): raise ArgumentError("Parameter has unknown type", param=name, type=type) - if format is not None and not typedargs.is_known_format(type, format): + if format is not None and not typedargs.type_system.is_known_format(type, format): raise ArgumentError("Parameter has unknown format", param=name, type=type, format=format) definition = LogDataItem(name, type, format, is_list) diff --git a/pymomo/syslog/syslog.py b/pymomo/syslog/syslog.py index eeb5351..ae8c269 100644 --- a/pymomo/syslog/syslog.py +++ b/pymomo/syslog/syslog.py @@ -31,7 +31,7 @@ def __init__(self, raw_entries): self.entries = [] entries = [] - self.mapper = LogDefinitionMap() + self._mapper = LogDefinitionMap() for entry in raw_entries: if len(entries) != 0 and entry.typecode == RawLogEntry.ContinuationType: @@ -111,13 +111,13 @@ def add_ldf(self, file): Add an ldf file to help parse log entries """ - self.mapper.add_ldf(file) + self._mapper.add_ldf(file) def __str__(self): val = "" for entry in self.entries: - ldf = self.mapper.map(entry.header.msg) + ldf = self._mapper.map(entry.header.msg) if ldf.message is not None: desc = ldf.message else: diff --git a/pymomo/utilities/config.py b/pymomo/utilities/config.py index ffc6591..6347c4e 100644 --- a/pymomo/utilities/config.py +++ b/pymomo/utilities/config.py @@ -2,8 +2,9 @@ import os import json +from pymomo.utilities.paths import MomoPaths -conf_dir = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'config') +conf_dir = MomoPaths().config class ConfigFile: """ diff --git a/pymomo/utilities/console.py b/pymomo/utilities/console.py index 7b7f59c..29775f7 100644 --- a/pymomo/utilities/console.py +++ b/pymomo/utilities/console.py @@ -1,8 +1,12 @@ import sys +from pymomo.utilities.typedargs import type_system class ProgressBar: """ - A simple progress bar that updates itself in the console + A simple progress bar that updates itself in the console. + + If the program is being run in interactive mode, display the progress_bar + otherwise do not display it """ def __init__(self, title, count=100): @@ -11,16 +15,22 @@ def __init__(self, title, count=100): self.count = count def start(self): - sys.stdout.write(self.title + ": [" + "-"*40 + "]" + chr(8)*41) - sys.stdout.flush() + if type_system.interactive: + sys.stdout.write(self.title + ": [" + "-"*40 + "]" + chr(8)*41) + sys.stdout.flush() + self.prog = 0 def progress(self, x): x = int(x * 40 // self.count) - sys.stdout.write("#" * (x - self.prog)) - sys.stdout.flush() + + if type_system.interactive: + sys.stdout.write("#" * (x - self.prog)) + sys.stdout.flush() + self.prog = x def end(self): - sys.stdout.write("#" * (40 - self.prog) + "]\n") - sys.stdout.flush() \ No newline at end of file + if type_system.interactive: + sys.stdout.write("#" * (40 - self.prog) + "]\n") + sys.stdout.flush() diff --git a/pymomo/utilities/formatting.py b/pymomo/utilities/formatting.py new file mode 100644 index 0000000..3c0bb9c --- /dev/null +++ b/pymomo/utilities/formatting.py @@ -0,0 +1,19 @@ +#formatting.py + +def indent_block(string_block, level): + indent = ' '*level + repstr = '\n' + indent + + retval = string_block.replace('\n', repstr) + return indent + retval + +def indent_list(list, level): + """ + Join a list of strings, one per line with 'level' spaces before each one + """ + + indent = ' '*level + joinstr = '\n' + indent + + retval = joinstr.join(list) + return indent + retval \ No newline at end of file diff --git a/pymomo/utilities/typedargs/__init__.py b/pymomo/utilities/typedargs/__init__.py index 669cba3..fdeb766 100644 --- a/pymomo/utilities/typedargs/__init__.py +++ b/pymomo/utilities/typedargs/__init__.py @@ -1,4 +1,4 @@ #External API functions from this package -from annotate import param, returns, context, finalizer, takes_cmdline, annotated -from typeinfo import is_known_type, is_known_format, convert_to_type, format_value, get_type_size, inject_type, load_external_types \ No newline at end of file +from annotate import param, returns, context, finalizer, takes_cmdline, annotated, return_type +from typeinfo import type_system \ No newline at end of file diff --git a/pymomo/utilities/typedargs/annotate.py b/pymomo/utilities/typedargs/annotate.py index a289c11..1998be3 100644 --- a/pymomo/utilities/typedargs/annotate.py +++ b/pymomo/utilities/typedargs/annotate.py @@ -1,10 +1,10 @@ #annotate.py - from decorator import decorator from pymomo.exceptions import * import inspect -import types +from typeinfo import type_system from collections import namedtuple +import sys class BasicContext(dict): pass @@ -31,10 +31,10 @@ def _check_and_execute(f, *args, **kwargs): #Ensure that only MoMoException subclasses are passed by the caller try: retval = f(*convargs, **convkw) - except MoMoException as e: - raise e + except MoMoException: + raise except Exception as unknown: - raise APIError(str(unknown.args)) + raise APIError(str(unknown.args)), None, sys.exc_info()[2] return retval @@ -47,7 +47,7 @@ def _process_arg(f, arg, value): """ if arg in f.params: - val = getattr(f.params[arg], 'convert')(value) + val = type_system.convert_to_type(value, f.types[arg]) else: val = value @@ -201,8 +201,13 @@ def print_help(f): print " - %s (%s): %s" % (key, type, desc) def print_retval(f, value): + if hasattr(f, 'typed_retval') and f.typed_retval == True: + print type_system.format_return_value(f, value) + return + if not hasattr(f, 'retval'): print str(value) + elif f.retval.printer[0] is not None: f.retval.printer[0](value) elif f.retval.desc != "": @@ -217,8 +222,8 @@ def find_all(container): context = BasicContext() for name in names: - #Ignore __ names - if name.startswith('__'): + #Ignore _ and __ names + if name.startswith('_'): continue if isinstance(container, dict): @@ -229,7 +234,12 @@ def find_all(container): #Check if this is an annotated object that should be included. Check the type of #annotated to avoid issues with module imports where someone did from annotate import * #into the module causing an annotated symbol to be defined as a decorator - if hasattr(obj, 'annotated') and isinstance(getattr(obj, 'annotated'), int): + + #If we are in a dict context then strings point to lazily loaded modules so include them + #too. + if isinstance(container, dict) and isinstance(obj, basestring): + context[name] = obj + elif hasattr(obj, 'annotated') and isinstance(getattr(obj, 'annotated'), int): context[name] = obj return context @@ -259,6 +269,9 @@ def context_from_module(module): def check_returns_data(f): + if hasattr(f, 'typed_retval') and f.typed_retval == True: + return True + if not hasattr(f, 'retval'): return False @@ -269,10 +282,7 @@ def param(name, type, *validators, **kwargs): def _param(f): f = annotated(f) - if not hasattr(types, type): - raise AttributeError('Unknown parameter type: %s' % str(type)) - - f.params[name] = getattr(types, type) + f.params[name] = type_system.get_type(type) f.types[name] = type f.valids[name] = _parse_validators(f.params[name], validators) @@ -304,6 +314,25 @@ def _returns(f): return _returns +def return_type(type, formatter=None): + """ + Specify that this function returns a typed value + + type must be a type known to the MoMo type system and formatter + must be a valid formatter for that type + """ + + def _returns(f): + annotated(f) + f.typed_retval = True + f.retval_type = type_system.get_type(type) + f.retval_typename = type + f.retval_formatter = formatter + + return f + + return _returns + def context(name=None): """ Declare that a class defines a MoMo context for use with the momo function for discovering diff --git a/pymomo/utilities/typedargs/shell.py b/pymomo/utilities/typedargs/shell.py index a41ce3b..ef34d7f 100644 --- a/pymomo/utilities/typedargs/shell.py +++ b/pymomo/utilities/typedargs/shell.py @@ -6,10 +6,11 @@ import annotate import inspect import shlex -import typeinfo +from typeinfo import type_system from pymomo.utilities.rcfile import RCFile import os.path import platform +import importlib posix_lex = platform.system() != 'Windows' @@ -57,7 +58,7 @@ def import_types(package, module=None): else: path = os.path.join(package, module) - typeinfo.load_external_types(path) + type_system.load_external_types(path) def print_dir(context): doc = inspect.getdoc(context) @@ -114,11 +115,35 @@ def _do_help(context, line): return [], True +def deferred_add(add_action): + """ + Perform a lazy import of a context so that we don't have a huge initial startup time + loading all of the modules that someone might want even though they probably only + will use a few of them. + """ + + module, sep, obj = add_action.partition(',') + + mod = importlib.import_module(module) + if obj == "": + name, con = annotate.context_from_module(mod) + return con + + if hasattr(mod, obj): + return getattr(mod, obj) + + raise ArgumentError("Attempted to import nonexistent object from module", module=module, object=obj) + def find_function(context, funname): func = None if isinstance(context, dict): if funname in context: func = context[funname] + + #Allowed lazy loading of functions + if isinstance(func, basestring): + func = deferred_add(func) + context[funname] = func elif hasattr(context, funname): func = getattr(context, funname) diff --git a/pymomo/utilities/typedargs/typeinfo.py b/pymomo/utilities/typedargs/typeinfo.py index 5090a13..b1a75a0 100644 --- a/pymomo/utilities/typedargs/typeinfo.py +++ b/pymomo/utilities/typedargs/typeinfo.py @@ -2,138 +2,278 @@ #Basic routines for converting information from string or other binary #formats to python types and for displaying those types in supported #formats +#TODO: +#- Extend the type system to use a recursive parser to allow complex +# types to be built from other complex types. from pymomo.exceptions import * -import annotate import types import os.path import imp -def convert_to_type(value, type, **kwargs): - """ - Convert value to type 'type' - - If the conversion routine takes various kwargs to - modify the conversion process, **kwargs is passed - through to the underlying conversion function - """ +#Start working on recursive parser +#import pyparsing +#symbolchars = pyparsing.Regex('[_a-zA-Z][_a-zA-Z0-9]*') +#typename = pyparsing.Word(symbolchars) - if not is_known_type(type): - raise ArgumentError("type is not known to type system", type=type) +#simpletype = typename +#complextype = pyparsing.Forward() - typeobj = getattr(types, type) +#typelist = pyparsing.delimitedList(simpletype | complextype, ',') +#complextype << typename + pyparsing.Literal('(').suppress() + typelist + pyparsing.Literal(')').suppress() - conv = typeobj.convert(value, **kwargs) - return conv +#statement = -def get_type_size(type): +class TypeSystem(object): """ - Get the size of this type for converting a hex string to the - type. Return 0 if the size is not known. + TypeSystem permits the inspection of defined types and supports + converted string and binary values to and from these types. """ - if not is_known_type(type): - raise ArgumentError("type is not known to type system", type=type) + def __init__(self, *args): + """ + Create a TypeSystem by importing all of the types defined in modules passed + as arguments to this function. Each module is imported using + """ - typeobj = getattr(types, type) + self.interactive = False + self.known_types = {} + self.type_factories = {} - if hasattr(typeobj, 'size'): - return typeobj.size() + for arg in args: + self.load_type_module(arg) - return 0 + def convert_to_type(self, value, type, **kwargs): + """ + Convert value to type 'type' -def format_value(value, type, format=None, **kwargs): - """ - Convert value to type and format it as a string + If the conversion routine takes various kwargs to + modify the conversion process, **kwargs is passed + through to the underlying conversion function + """ - type must be a known type in the type system and format, - if given, must specify a valid formatting option for the - specified type. - """ + typeobj = self.get_type(type) - typed_val = convert_to_type(value, type, **kwargs) - typeobj = getattr(types, type) + conv = typeobj.convert(value, **kwargs) + return conv - #Allow types to specify default formatting functions as 'default_formatter' - #otherwise if not format is specified, just convert the value to a string - if format is None: - if hasattr(typeobj, 'default_formatter'): - format_func = getattr(typeobj, 'default_formatter') - return format_func(typed_val, **kwargs) + def get_type_size(self, type): + """ + Get the size of this type for converting a hex string to the + type. Return 0 if the size is not known. + """ - return str(typed_val) + typeobj = self.get_type(type) - formatter = "format_%s" % str(format) - if not hasattr(typeobj, formatter): - raise ArgumentError("Unknown format for type", type=type, format=format, formatter_function=formatter) + if hasattr(typeobj, 'size'): + return typeobj.size() - format_func = getattr(typeobj, formatter) - return format_func(typed_val, **kwargs) + return 0 -def is_known_type(type): - """ - Check if type is known to the type system. + def format_value(self, value, type, format=None, **kwargs): + """ + Convert value to type and format it as a string - Returns boolean indicating if type is known. - """ + type must be a known type in the type system and format, + if given, must specify a valid formatting option for the + specified type. + """ - if not isinstance(type, basestring): - raise ArgumentError("type must be a string naming a known type", type=type) + typed_val = self.convert_to_type(value, type, **kwargs) + typeobj = self.get_type(type) - if not hasattr(types, type): - return False + #Allow types to specify default formatting functions as 'default_formatter' + #otherwise if not format is specified, just convert the value to a string + if format is None: + if hasattr(typeobj, 'default_formatter'): + format_func = getattr(typeobj, 'default_formatter') + return format_func(typed_val, **kwargs) - return True + return str(typed_val) -def is_known_format(type, format): - """ - Check if format is known for given type. + formatter = "format_%s" % str(format) + if not hasattr(typeobj, formatter): + raise ArgumentError("Unknown format for type", type=type, format=format, formatter_function=formatter) - Returns boolean indicating if format is valid for the specified type. - """ + format_func = getattr(typeobj, formatter) + return format_func(typed_val, **kwargs) + + def _validate_type(self, typeobj): + """ + Validate that all required type methods are implemented. + + At minimum a type must have: + - a convert() or convert_binary() function + - a default_formatter() function + + Raises an ArgumentError if the type is not valid + """ + + if not (hasattr(typeobj, "convert") or hasattr(typeobj, "convert_binary")): + raise ArgumentError("type is invalid, does not have convert or convert_binary function", type=typeobj, methods=dir(typeobj)) + + if not hasattr(typeobj, "default_formatter"): + raise ArgumentError("type is invalid, does not have default_formatter function", type=typeobj, methods=dir(typeobj)) + + def is_known_type(self, type): + """ + Check if type is known to the type system. + + Returns boolean indicating if type is known. + """ + + if not isinstance(type, basestring): + raise ArgumentError("type must be a string naming a known type", type=type) + + if type in self.known_types: + return True - if not is_known_type(type): return False - typeobj = getattr(types, type) - formatter = "format_%s" % str(format) - if not hasattr(typeobj, formatter): + def split_type(self, typename): + """ + Given a potentially complex type, split it into its base type and specializers + """ + + name = self._canonicalize_type(typename) + if '(' not in name: + return name, False, [] + + base,sub = name.split('(') + if len(sub) == 0 or sub[-1] != ')': + raise ArgumentError("syntax error in complex type, no matching ) found", passed_type=typename, basetype=base, subtype_string=sub) + + sub = sub[:-1] + + subs = sub.split(',') + return base, True, subs + + def instantiate_type(self, typename, base, subtypes): + """ + Instantiate a complex type + """ + + if base not in self.type_factories: + raise ArgumentError("unknown complex base type specified", passed_type=typename, base_type=base) + + BaseType = self.type_factories[base] + + #Make sure all of the subtypes are valid + for s in subtypes: + try: + self.get_type(s) + except MoMoException as e: + raise ArgumentError("could not instantiate subtype for complex type", passed_type=typename, sub_type=s, error=e) + + typeobj = BaseType.Build(*subtypes, type_system=self) + self.inject_type(typename, typeobj) + + def _canonicalize_type(self, typename): + return typename.replace(' ', '') + + def get_type(self, typename): + """ + Return the type object corresponding to a type name. + """ + + typename = self._canonicalize_type(typename) + + type, is_complex, subtypes = self.split_type(typename) + if not self.is_known_type(typename): + if is_complex: + self.instantiate_type(typename, type, subtypes) + else: + raise ArgumentError("get_type called on unknown type", type=typename) + + return self.known_types[typename] + + def is_known_format(self, type, format): + """ + Check if format is known for given type. + + Returns boolean indicating if format is valid for the specified type. + """ + + typeobj = self.get_type(type) + + formatter = "format_%s" % str(format) + if not hasattr(typeobj, formatter): + return False + + return True + + def _is_factory(self, typeobj): + """ + Determine if typeobj is a factory for producing complex types + """ + + if hasattr(typeobj, 'Build'): + return True + return False - return True + def format_return_value(self, function, value): + """ + Format the return value of a function based on the annotated type information + """ -def inject_type(name, typeobj): - """ - Given a module-like object that defines a type, add it to our type system so that - it can be used with the momo tool and with other annotated API functions. - """ + return self.format_value(value, function.retval_typename, function.retval_formatter) - #TODO add checking each type for the minimum required content like a default formatter, - #a conversion function, etc. + def inject_type(self, name, typeobj): + """ + Given a module-like object that defines a type, add it to our type system so that + it can be used with the momo tool and with other annotated API functions. + """ - if is_known_type(name): - raise ArgumentError("attempting to inject a type that is already defined", type=name) + name = self._canonicalize_type(name) + base,is_complex,subs = self.split_type(name) - setattr(types, name, typeobj) + if self.is_known_type(name): + raise ArgumentError("attempting to inject a type that is already defined", type=name) -def load_external_types(path, verbose=False): - """ - Given a path to a python package or module, load that module, search for all defined variables - inside of it that do not start with _ or __ and inject them into the type system. If any of the - types cannot be injected, silently ignore them unless verbose is True. If path points to a module - it should not contain the trailing .py since this is added automatically by the python import system - """ + if (not is_complex) and self._is_factory(typeobj): + if name in self.type_factories: + raise ArgumentError("attempted to inject a complex type factory that is already defined", type=name) + self.type_factories[name] = typeobj + else: + self._validate_type(typeobj) + self.known_types[name] = typeobj + + if not hasattr(typeobj, "default_formatter"): + raise ArgumentError("type is invalid, does not have default_formatter function", type=typeobj, methods=dir(typeobj)) + + def load_type_module(self, module, verbose=False): + """ + Given a module that contains a list of some types find all symbols in the module that + do not start with _ and attempt to import them as types. + """ + + for name in filter(lambda x: not x.startswith('_'), dir(module)): + typeobj = getattr(module, name) + self.inject_type(name, typeobj) + + def load_external_types(self, path, verbose=False): + """ + Given a path to a python package or module, load that module, search for all defined variables + inside of it that do not start with _ or __ and inject them into the type system. If any of the + types cannot be injected, silently ignore them unless verbose is True. If path points to a module + it should not contain the trailing .py since this is added automatically by the python import system + """ + + d,p = os.path.split(path) + + try: + fileobj,pathname,description = imp.find_module(p, [d]) + mod = imp.load_module(p, fileobj, pathname, description) + except ImportError as e: + raise ArgumentError("could not import module in order to load external types", module_path=path, parent_directory=p, module_name=p, error=str(e)) - d,p = os.path.split(path) + self.load_type_module(mod, verbose) - try: - fileobj,pathname,description = imp.find_module(p, [d]) - mod = imp.load_module(p, fileobj, pathname, description) - except ImportError: - raise ArgumentError("could not import module in order to load external types", module_path=path, parent_directory=p, module_name=p) + #TODO add checking for types that could not be injected and report them - for name in filter(lambda x: not x.startswith('_'), dir(mod)): - typeobj = getattr(mod, name) - inject_type(name, typeobj) +#In order to support function annotations that must be resolved to types when modules +#are imported, create a default TypeSystem object that is used globally to store type +#information - #TODO add checking for types that could not be injected and report them +type_system = TypeSystem(types) diff --git a/pymomo/utilities/typedargs/types/__init__.py b/pymomo/utilities/typedargs/types/__init__.py index 48ff018..9cddd98 100644 --- a/pymomo/utilities/typedargs/types/__init__.py +++ b/pymomo/utilities/typedargs/types/__init__.py @@ -1,4 +1,8 @@ #Known Types import integer import string -import path \ No newline at end of file +import path +import bool + +from map import map +from list import list diff --git a/pymomo/utilities/typedargs/types/bool.py b/pymomo/utilities/typedargs/types/bool.py new file mode 100644 index 0000000..359567d --- /dev/null +++ b/pymomo/utilities/typedargs/types/bool.py @@ -0,0 +1,20 @@ +#bool.py +#Simple boolean type + +def convert(arg, **kwargs): + if arg is None: + return arg + + if isinstance(arg, basestring): + comp = arg.lower() + if comp == 'true': + return True + elif comp == 'false': + return False + else: + raise ValueError("Unknown boolean value (should be true or false): %s" % arg) + + return bool(arg) + +def default_formatter(arg, **kwargs): + return str(arg) diff --git a/pymomo/utilities/typedargs/types/integer.py b/pymomo/utilities/typedargs/types/integer.py index 38356ca..cc832f0 100644 --- a/pymomo/utilities/typedargs/types/integer.py +++ b/pymomo/utilities/typedargs/types/integer.py @@ -34,6 +34,9 @@ def validate_range(arg, lower, upper): raise ValueError("not in required range [%d, %d]" %(int(lower), int(upper))) #Formatting functions +def default_formatter(arg, **kwarg): + return str(arg) + def format_unsigned(arg, **kwarg): return format(arg, 'd') diff --git a/pymomo/utilities/typedargs/types/list.py b/pymomo/utilities/typedargs/types/list.py new file mode 100644 index 0000000..1206d65 --- /dev/null +++ b/pymomo/utilities/typedargs/types/list.py @@ -0,0 +1,30 @@ +#list.py + +class list(object): + def __init__(self, valuetype, **kwargs): + + self.valuetype = valuetype + self.type_system = kwargs['type_system'] + + @staticmethod + def Build(*types, **kwargs): + if len(types) != 1: + raise ValueError("list must be created with 1 argument, a value type") + + return list(types[0], **kwargs) + + def convert(self, value, **kwargs): + converted = [] + for x in value: + y = self.type_system.convert_to_type(x, self.valuetype, **kwargs) + converted.append(y) + + return converted + + def default_formatter(self, value, **kwargs): + lines = [] + for x in value: + line = self.type_system.format_value(x, self.valuetype, **kwargs) + lines.append(line) + + return "\n".join(lines) diff --git a/pymomo/utilities/typedargs/types/map.py b/pymomo/utilities/typedargs/types/map.py new file mode 100644 index 0000000..fb3f476 --- /dev/null +++ b/pymomo/utilities/typedargs/types/map.py @@ -0,0 +1,31 @@ +#map.py +#a complex type wrapping a python dictionary + +class map(object): + def __init__(self, keytype, valuetype, **kwargs): + + self.keytype = keytype + self.valuetype = valuetype + self.type_system = kwargs['type_system'] + + @staticmethod + def Build(*types, **kwargs): + if len(types) != 2: + raise ValueError("map must be created with 2 arguments, a keytype and a valuetype") + + return map(types[0], types[1], **kwargs) + + def convert(self, value, **kwargs): + if isinstance(value, dict): + return value + + raise ValueError("Converting to map from string not yet supported") + + def default_formatter(self, value, **kwargs): + forms = [] + for key,val in value.iteritems(): + keyform = self.type_system.format_value(key, self.keytype) + valform = self.type_system.format_value(val, self.valuetype) + forms.append("%s: %s" % (keyform, valform)) + + return "\n".join(forms) \ No newline at end of file diff --git a/pymomo/utilities/typedargs/types/path.py b/pymomo/utilities/typedargs/types/path.py index aef5e4a..c94bf81 100644 --- a/pymomo/utilities/typedargs/types/path.py +++ b/pymomo/utilities/typedargs/types/path.py @@ -33,4 +33,7 @@ def validate_writeable(arg): parent = os.path.dirname(arg) if not os.path.isdir(parent): raise ValueError("Parent directory does not exist and path must be writeable") - \ No newline at end of file + +#Formatting functions +def default_formatter(arg, **kwargs): + return str(arg) \ No newline at end of file diff --git a/pymomo/utilities/typedargs/types/string.py b/pymomo/utilities/typedargs/types/string.py index da621f5..4b011bf 100644 --- a/pymomo/utilities/typedargs/types/string.py +++ b/pymomo/utilities/typedargs/types/string.py @@ -12,4 +12,15 @@ def validate_list(arg, choices): choice_set = set(choices) if arg not in choices: - raise ValueError('Value not in list: %s' % str(choices)) \ No newline at end of file + raise ValueError('Value not in list: %s' % str(choices)) + +def validate_not_empty(arg): + """ + Make sure the string is not empty + """ + + if len(arg) == 0: + raise ValueError("String cannot be empty") + +def default_formatter(arg, **kwargs): + return arg diff --git a/setup.py b/setup.py index 563725f..aed87c0 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,8 @@ +#Caveats and possible issues +#Mac OS X +# - when using a virtualenv, readline is not properly installed into the virtualenv +# and cannot be imported. You need to install it using easy_install as described here +# http://calvinx.com/tag/readline/ from setuptools import setup, find_packages import os @@ -30,7 +35,6 @@ def list_data_files(): license = "LGPLv3", install_requires=[ "beautifulsoup4==4.3.2", - "BTrees==4.1.1", "Cheetah==2.4.4", "cmdln==1.1.2", "colorama==0.3.3", @@ -38,21 +42,14 @@ def list_data_files(): "intelhex==1.5", "Markdown==2.5.2", "nose==1.3.4", - "persistent==4.0.8", "py==1.4.26", "pycparser==2.10", "pyparsing==2.0.3", "pyserial==2.7", "pytest==2.6.4", "six==1.9.0", - "transaction==1.4.3", - "zc.lockfile==1.1.0", - "ZConfig==3.0.4", - "zdaemon==4.0.1", - "ZEO==4.1.0", - "ZODB==4.1.0", - "ZODB3==3.11.0", - "zope.interface==4.1.2" + "xlsxwriter>=0.6.7", + "pint>=0.6" ], package_data={ #This could be better 'pymomo': list_data_files() diff --git a/test/test_pcb/__init__.py b/test/test_pcb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_pcb/eagle/assyvars.brd b/test/test_pcb/eagle/assyvars.brd new file mode 100644 index 0000000..7e0d86e --- /dev/null +++ b/test/test_pcb/eagle/assyvars.brd @@ -0,0 +1,1328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>CAPACITOR</b> + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>EAGLE Design Rules</b> +<p> +The default Design Rules have been set to cover +a wide range of applications. Your particular design +may have different requirements, so please make the +necessary adjustments and save your customized +design rules under a new name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/test_pcb/eagle/blank_board.brd b/test/test_pcb/eagle/blank_board.brd new file mode 100644 index 0000000..a24ac9b --- /dev/null +++ b/test/test_pcb/eagle/blank_board.brd @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>EAGLE Design Rules</b> +<p> +The default Design Rules have been set to cover +a wide range of applications. Your particular design +may have different requirements, so please make the +necessary adjustments and save your customized +design rules under a new name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/test_pcb/eagle/controller_complete.brd b/test/test_pcb/eagle/controller_complete.brd new file mode 100644 index 0000000..880619c --- /dev/null +++ b/test/test_pcb/eagle/controller_complete.brd @@ -0,0 +1,3164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +BATT +3.6V ++ + + + + + + + + + + + + +WellDone +Controller v4.1 +11/20/2014 + +Solar ++ +- + + + + +Microchip PIC24ka101 +CAD and Schematic files for the PIC24Fka101 series of microcontrollers. + + +<b>SMALL OUTLINE PACKAGE</b> + + + + + + + + + + + + + + + + +>NAME +>VALUE +1 + + + + + + + + + + +<b>SMALL OUTLINE TRANSISTOR</b><p> +reflow soldering + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +1 + + + + +>NAME + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Thin Quad Flat Pack</b><p> +package type TQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +<b>CRYSTAL</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>Ceramic Chip Capacitor KEMET 0805 reflow solder</b><p> +Metric Code Size 2012 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 0204 reflow solder</b><p> +Metric Code Size 1005 + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + +>NAME +>VALUE + + + + + +<b>Diodes</b><p> +Based on the following sources: +<ul> +<li>Motorola : www.onsemi.com +<li>Fairchild : www.fairchildsemi.com +<li>Philips : www.semiconductors.com +<li>Vishay : www.vishay.de +</ul> +<author>Created by librarian@cadsoft.de</author> + + +<b>Diode Package</b> Reflow soldering<p> +INFINEON, www.infineon.com/cmc_upload/0/000/010/257/eh_db_5b.pdf + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +RN4020 Bluetooth 4.0 module, surface mount castellation form-factor 19.5mm x 11.5mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Antenna + + + + +>NAME + + +SOT-23 package, 3 leads + + + + + + + +>NAME + + + + + + + + + + +<b>Small Outline Transistor</b><p> +package type OT + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>PNP Transistors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>SOT-23</b> + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>EAGLE Design Rules</b> +<p> +The default Design Rules have been set to cover +a wide range of applications. Your particular design +may have different requirements, so please make the +necessary adjustments and save your customized +design rules under a new name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Since Version 6.2.2 text objects can contain more than one line, +which will not be processed correctly with this version. + + + diff --git a/test/test_pcb/eagle/controller_dist_distpn.brd b/test/test_pcb/eagle/controller_dist_distpn.brd new file mode 100644 index 0000000..4db5b1f --- /dev/null +++ b/test/test_pcb/eagle/controller_dist_distpn.brd @@ -0,0 +1,3162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +BATT +3.6V ++ + + + + + + + + + + + + +WellDone +Controller v4.1 +11/20/2014 + +Solar ++ +- + + + + +Microchip PIC24ka101 +CAD and Schematic files for the PIC24Fka101 series of microcontrollers. + + +<b>SMALL OUTLINE PACKAGE</b> + + + + + + + + + + + + + + + + +>NAME +>VALUE +1 + + + + + + + + + + +<b>SMALL OUTLINE TRANSISTOR</b><p> +reflow soldering + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +1 + + + + +>NAME + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Thin Quad Flat Pack</b><p> +package type TQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +<b>CRYSTAL</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>Ceramic Chip Capacitor KEMET 0805 reflow solder</b><p> +Metric Code Size 2012 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 0204 reflow solder</b><p> +Metric Code Size 1005 + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + +>NAME +>VALUE + + + + + +<b>Diodes</b><p> +Based on the following sources: +<ul> +<li>Motorola : www.onsemi.com +<li>Fairchild : www.fairchildsemi.com +<li>Philips : www.semiconductors.com +<li>Vishay : www.vishay.de +</ul> +<author>Created by librarian@cadsoft.de</author> + + +<b>Diode Package</b> Reflow soldering<p> +INFINEON, www.infineon.com/cmc_upload/0/000/010/257/eh_db_5b.pdf + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +RN4020 Bluetooth 4.0 module, surface mount castellation form-factor 19.5mm x 11.5mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Antenna + + + + +>NAME + + +SOT-23 package, 3 leads + + + + + + + +>NAME + + + + + + + + + + +<b>Small Outline Transistor</b><p> +package type OT + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>PNP Transistors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>SOT-23</b> + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>EAGLE Design Rules</b> +<p> +The default Design Rules have been set to cover +a wide range of applications. Your particular design +may have different requirements, so please make the +necessary adjustments and save your customized +design rules under a new name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Since Version 6.2.2 text objects can contain more than one line, +which will not be processed correctly with this version. + + + diff --git a/test/test_pcb/eagle/controller_missing_attrs.brd b/test/test_pcb/eagle/controller_missing_attrs.brd new file mode 100644 index 0000000..c1639c9 --- /dev/null +++ b/test/test_pcb/eagle/controller_missing_attrs.brd @@ -0,0 +1,3161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +BATT +3.6V ++ + + + + + + + + + + + + +WellDone +Controller v4.1 +11/20/2014 + +Solar ++ +- + + + + +Microchip PIC24ka101 +CAD and Schematic files for the PIC24Fka101 series of microcontrollers. + + +<b>SMALL OUTLINE PACKAGE</b> + + + + + + + + + + + + + + + + +>NAME +>VALUE +1 + + + + + + + + + + +<b>SMALL OUTLINE TRANSISTOR</b><p> +reflow soldering + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +1 + + + + +>NAME + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Thin Quad Flat Pack</b><p> +package type TQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +<b>CRYSTAL</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>Ceramic Chip Capacitor KEMET 0805 reflow solder</b><p> +Metric Code Size 2012 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 0204 reflow solder</b><p> +Metric Code Size 1005 + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + +>NAME +>VALUE + + + + + +<b>Diodes</b><p> +Based on the following sources: +<ul> +<li>Motorola : www.onsemi.com +<li>Fairchild : www.fairchildsemi.com +<li>Philips : www.semiconductors.com +<li>Vishay : www.vishay.de +</ul> +<author>Created by librarian@cadsoft.de</author> + + +<b>Diode Package</b> Reflow soldering<p> +INFINEON, www.infineon.com/cmc_upload/0/000/010/257/eh_db_5b.pdf + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +RN4020 Bluetooth 4.0 module, surface mount castellation form-factor 19.5mm x 11.5mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Antenna + + + + +>NAME + + +SOT-23 package, 3 leads + + + + + + + +>NAME + + + + + + + + + + +<b>Small Outline Transistor</b><p> +package type OT + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>PNP Transistors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>SOT-23</b> + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>EAGLE Design Rules</b> +<p> +The default Design Rules have been set to cover +a wide range of applications. Your particular design +may have different requirements, so please make the +necessary adjustments and save your customized +design rules under a new name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Since Version 6.2.2 text objects can contain more than one line, +which will not be processed correctly with this version. + + + diff --git a/test/test_pcb/eagle/controller_nodist.brd b/test/test_pcb/eagle/controller_nodist.brd new file mode 100644 index 0000000..2c35334 --- /dev/null +++ b/test/test_pcb/eagle/controller_nodist.brd @@ -0,0 +1,3161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +BATT +3.6V ++ + + + + + + + + + + + + +WellDone +Controller v4.1 +11/20/2014 + +Solar ++ +- + + + + +Microchip PIC24ka101 +CAD and Schematic files for the PIC24Fka101 series of microcontrollers. + + +<b>SMALL OUTLINE PACKAGE</b> + + + + + + + + + + + + + + + + +>NAME +>VALUE +1 + + + + + + + + + + +<b>SMALL OUTLINE TRANSISTOR</b><p> +reflow soldering + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + +1 + + + + +>NAME + + +<b>PIN HEADER</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Thin Quad Flat Pack</b><p> +package type TQ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +<b>CRYSTAL</b> + + + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + +<b>Resistors, Capacitors, Inductors</b><p> +Based on the previous libraries: +<ul> +<li>r.lbr +<li>cap.lbr +<li>cap-fe.lbr +<li>captant.lbr +<li>polcap.lbr +<li>ipc-smd.lbr +</ul> +All SMD packages are defined according to the IPC specifications and CECC<p> +<author>Created by librarian@cadsoft.de</author><p> +<p> +for Electrolyt Capacitors see also :<p> +www.bccomponents.com <p> +www.panasonic.com<p> +www.kemet.com<p> +http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> +<p> +for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> + +<table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> +<tr valign="top"> + +<! <td width="10">&nbsp;</td> +<td width="90%"> + +<b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> +<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> + <TR> + <TD COLSPAN=8> + <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> + </B> + </TD> + <TD ALIGN=CENTER> + <B> + <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> + </B> + </TD><TD>&nbsp;</TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > + 3005P<BR> + 3006P<BR> + 3006W<BR> + 3006Y<BR> + 3009P<BR> + 3009W<BR> + 3009Y<BR> + 3057J<BR> + 3057L<BR> + 3057P<BR> + 3057Y<BR> + 3059J<BR> + 3059L<BR> + 3059P<BR> + 3059Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 89P<BR> + 89W<BR> + 89X<BR> + 89PH<BR> + 76P<BR> + 89XH<BR> + 78SLT<BR> + 78L&nbsp;ALT<BR> + 56P&nbsp;ALT<BR> + 78P&nbsp;ALT<BR> + T8S<BR> + 78L<BR> + 56P<BR> + 78P<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + T18/784<BR> + 783<BR> + 781<BR> + -<BR> + -<BR> + -<BR> + 2199<BR> + 1697/1897<BR> + 1680/1880<BR> + 2187<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 8035EKP/CT20/RJ-20P<BR> + -<BR> + RJ-20X<BR> + -<BR> + -<BR> + -<BR> + 1211L<BR> + 8012EKQ&nbsp;ALT<BR> + 8012EKR&nbsp;ALT<BR> + 1211P<BR> + 8012EKJ<BR> + 8012EKL<BR> + 8012EKQ<BR> + 8012EKR<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 2101P<BR> + 2101W<BR> + 2101Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 2102L<BR> + 2102S<BR> + 2102Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVMCOG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 43P<BR> + 43W<BR> + 43Y<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 40L<BR> + 40P<BR> + 40Y<BR> + 70Y-T602<BR> + 70L<BR> + 70P<BR> + 70Y<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + RT/RTR12<BR> + RT/RTR12<BR> + RT/RTR12<BR> + -<BR> + RJ/RJR12<BR> + RJ/RJR12<BR> + RJ/RJR12<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3250L<BR> + 3250P<BR> + 3250W<BR> + 3250X<BR> + 3252P<BR> + 3252W<BR> + 3252X<BR> + 3260P<BR> + 3260W<BR> + 3260X<BR> + 3262P<BR> + 3262W<BR> + 3262X<BR> + 3266P<BR> + 3266W<BR> + 3266X<BR> + 3290H<BR> + 3290P<BR> + 3290W<BR> + 3292P<BR> + 3292W<BR> + 3292X<BR> + 3296P<BR> + 3296W<BR> + 3296X<BR> + 3296Y<BR> + 3296Z<BR> + 3299P<BR> + 3299W<BR> + 3299X<BR> + 3299Y<BR> + 3299Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66X&nbsp;ALT<BR> + -<BR> + 64W&nbsp;ALT<BR> + -<BR> + 64P&nbsp;ALT<BR> + 64W&nbsp;ALT<BR> + 64X&nbsp;ALT<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 66X&nbsp;ALT<BR> + 66P&nbsp;ALT<BR> + 66W&nbsp;ALT<BR> + 66P<BR> + 66W<BR> + 66X<BR> + 67P<BR> + 67W<BR> + 67X<BR> + 67Y<BR> + 67Z<BR> + 68P<BR> + 68W<BR> + 68X<BR> + 67Y&nbsp;ALT<BR> + 67Z&nbsp;ALT<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 5050<BR> + 5091<BR> + 5080<BR> + 5087<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + T63YB<BR> + T63XB<BR> + -<BR> + -<BR> + -<BR> + 5887<BR> + 5891<BR> + 5880<BR> + -<BR> + -<BR> + -<BR> + T93Z<BR> + T93YA<BR> + T93XA<BR> + T93YB<BR> + T93XB<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 8026EKP<BR> + 8026EKW<BR> + 8026EKM<BR> + 8026EKP<BR> + 8026EKB<BR> + 8026EKM<BR> + 1309X<BR> + 1309P<BR> + 1309W<BR> + 8024EKP<BR> + 8024EKW<BR> + 8024EKN<BR> + RJ-9P/CT9P<BR> + RJ-9W<BR> + RJ-9X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + 3103P<BR> + 3103Y<BR> + 3103Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3105P/3106P<BR> + 3105W/3106W<BR> + 3105X/3106X<BR> + 3105Y/3106Y<BR> + 3105Z/3105Z<BR> + 3102P<BR> + 3102W<BR> + 3102X<BR> + 3102Y<BR> + 3102Z<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMCBG<BR> + EVMCCG<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 55-1-X<BR> + 55-4-X<BR> + 55-3-X<BR> + 55-2-X<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 50-2-X<BR> + 50-4-X<BR> + 50-3-X<BR> + -<BR> + -<BR> + -<BR> + 64P<BR> + 64W<BR> + 64X<BR> + 64Y<BR> + 64Z<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RT/RTR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RJ/RJR22<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RT/RTR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RJ/RJR26<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RT/RTR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + RJ/RJR24<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=8>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=8> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> + </TD> + <TD ALIGN=CENTER> + <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3323P<BR> + 3323S<BR> + 3323W<BR> + 3329H<BR> + 3329P<BR> + 3329W<BR> + 3339H<BR> + 3339P<BR> + 3339W<BR> + 3352E<BR> + 3352H<BR> + 3352K<BR> + 3352P<BR> + 3352T<BR> + 3352V<BR> + 3352W<BR> + 3362H<BR> + 3362M<BR> + 3362P<BR> + 3362R<BR> + 3362S<BR> + 3362U<BR> + 3362W<BR> + 3362X<BR> + 3386B<BR> + 3386C<BR> + 3386F<BR> + 3386H<BR> + 3386K<BR> + 3386M<BR> + 3386P<BR> + 3386S<BR> + 3386W<BR> + 3386X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 25P<BR> + 25S<BR> + 25RX<BR> + 82P<BR> + 82M<BR> + 82PA<BR> + -<BR> + -<BR> + -<BR> + 91E<BR> + 91X<BR> + 91T<BR> + 91B<BR> + 91A<BR> + 91V<BR> + 91W<BR> + 25W<BR> + 25V<BR> + 25P<BR> + -<BR> + 25S<BR> + 25U<BR> + 25RX<BR> + 25X<BR> + 72XW<BR> + 72XL<BR> + 72PM<BR> + 72RX<BR> + -<BR> + 72PX<BR> + 72P<BR> + 72RXW<BR> + 72RXL<BR> + 72X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + T7YB<BR> + T7YA<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + TXD<BR> + TYA<BR> + TYP<BR> + -<BR> + TYD<BR> + TX<BR> + -<BR> + 150SX<BR> + 100SX<BR> + 102T<BR> + 101S<BR> + 190T<BR> + 150TX<BR> + 101<BR> + -<BR> + -<BR> + 101SX<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ET6P<BR> + ET6S<BR> + ET6X<BR> + RJ-6W/8014EMW<BR> + RJ-6P/8014EMP<BR> + RJ-6X/8014EMX<BR> + TM7W<BR> + TM7P<BR> + TM7X<BR> + -<BR> + 8017SMS<BR> + -<BR> + 8017SMB<BR> + 8017SMA<BR> + -<BR> + -<BR> + CT-6W<BR> + CT-6H<BR> + CT-6P<BR> + CT-6R<BR> + -<BR> + CT-6V<BR> + CT-6X<BR> + -<BR> + -<BR> + 8038EKV<BR> + -<BR> + 8038EKX<BR> + -<BR> + -<BR> + 8038EKP<BR> + 8038EKZ<BR> + 8038EKW<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 3321H<BR> + 3321P<BR> + 3321N<BR> + 1102H<BR> + 1102P<BR> + 1102T<BR> + RVA0911V304A<BR> + -<BR> + RVA0911H413A<BR> + RVG0707V100A<BR> + RVA0607V(H)306A<BR> + RVA1214H213A<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 3104B<BR> + 3104C<BR> + 3104F<BR> + 3104H<BR> + -<BR> + 3104M<BR> + 3104P<BR> + 3104S<BR> + 3104W<BR> + 3104X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + EVMQ0G<BR> + EVMQIG<BR> + EVMQ3G<BR> + EVMS0G<BR> + EVMQ0G<BR> + EVMG0G<BR> + -<BR> + -<BR> + -<BR> + EVMK4GA00B<BR> + EVM30GA00B<BR> + EVMK0GA00B<BR> + EVM38GA00B<BR> + EVMB6<BR> + EVLQ0<BR> + -<BR> + EVMMSG<BR> + EVMMBG<BR> + EVMMAG<BR> + -<BR> + -<BR> + EVMMCS<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + EVMM1<BR> + -<BR> + -<BR> + EVMM0<BR> + -<BR> + -<BR> + EVMM3<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + 62-3-1<BR> + 62-1-2<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67R<BR> + -<BR> + 67P<BR> + -<BR> + -<BR> + -<BR> + -<BR> + 67X<BR> + 63V<BR> + 63S<BR> + 63M<BR> + -<BR> + -<BR> + 63H<BR> + 63P<BR> + -<BR> + -<BR> + 63X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + RJ/RJR50<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P>&nbsp;<P> +<TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> + <TR> + <TD COLSPAN=7> + <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> + <P> + <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3224G<BR> + 3224J<BR> + 3224W<BR> + 3269P<BR> + 3269W<BR> + 3269X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 44G<BR> + 44J<BR> + 44W<BR> + 84P<BR> + 84W<BR> + 84X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST63Z<BR> + ST63Y<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + ST5P<BR> + ST5W<BR> + ST5X<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> + <TR> + <TD COLSPAN=7>&nbsp; + </TD> + </TR> + <TR> + <TD COLSPAN=7> + <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> + </TD> + </TR> + <TR> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> + </TD> + <TD> + <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> + </TD> + </TR> + <TR> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 3314G<BR> + 3314J<BR> + 3364A/B<BR> + 3364C/D<BR> + 3364W/X<BR> + 3313G<BR> + 3313J<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + 23B<BR> + 23A<BR> + 21X<BR> + 21W<BR> + -<BR> + 22B<BR> + 22A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST5YL/ST53YL<BR> + ST5YJ/5T53YJ<BR> + ST-23A<BR> + ST-22B<BR> + ST-22<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + ST-4B<BR> + ST-4A<BR> + -<BR> + -<BR> + -<BR> + ST-3B<BR> + ST-3A<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + EVM-6YS<BR> + EVM-1E<BR> + EVM-1G<BR> + EVM-1D<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + G4B<BR> + G4A<BR> + TR04-3S1<BR> + TRG04-2S1<BR> + -<BR> + -<BR> + -<BR></FONT> + </TD> + <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> + -<BR> + -<BR> + DVR-43A<BR> + CVR-42C<BR> + CVR-42A/C<BR> + -<BR> + -<BR></FONT> + </TD> + </TR> +</TABLE> +<P> +<FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> +<P> + +&nbsp; +<P> +</td> +</tr> +</table> + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + +<b>Ceramic Chip Capacitor KEMET 0805 reflow solder</b><p> +Metric Code Size 2012 + + + + +>NAME +>VALUE + + + + +<b>Ceramic Chip Capacitor KEMET 0204 reflow solder</b><p> +Metric Code Size 1005 + + + + +>NAME +>VALUE + + + + +<b>RESISTOR</b> + + + + + + + + +>NAME +>VALUE + + + + + + + +<b>Pin Header Connectors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>PIN HEADER</b> + + + + + + + + + +>NAME +>VALUE + + + + + +<b>Diodes</b><p> +Based on the following sources: +<ul> +<li>Motorola : www.onsemi.com +<li>Fairchild : www.fairchildsemi.com +<li>Philips : www.semiconductors.com +<li>Vishay : www.vishay.de +</ul> +<author>Created by librarian@cadsoft.de</author> + + +<b>Diode Package</b> Reflow soldering<p> +INFINEON, www.infineon.com/cmc_upload/0/000/010/257/eh_db_5b.pdf + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + + + + + +>NAME + + +RN4020 Bluetooth 4.0 module, surface mount castellation form-factor 19.5mm x 11.5mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Antenna + + + + +>NAME + + +SOT-23 package, 3 leads + + + + + + + +>NAME + + + + + + + + + + +<b>Small Outline Transistor</b><p> +package type OT + + + + + + + + + + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + +<b>PNP Transistors</b><p> +<author>Created by librarian@cadsoft.de</author> + + +<b>SOT-23</b> + + + + + + + +>NAME +>VALUE + + + + + + + + + + + + + + + + + + + + + +<b>EAGLE Design Rules</b> +<p> +Die Standard-Design-Rules sind so gewählt, dass sie für +die meisten Anwendungen passen. Sollte ihre Platine +besondere Anforderungen haben, treffen Sie die erforderlichen +Einstellungen hier und speichern die Design Rules unter +einem neuen Namen ab. +<b>EAGLE Design Rules</b> +<p> +The default Design Rules have been set to cover +a wide range of applications. Your particular design +may have different requirements, so please make the +necessary adjustments and save your customized +design rules under a new name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Since Version 6.2.2 text objects can contain more than one line, +which will not be processed correctly with this version. + + + diff --git a/test/test_pcb/eagle/pcb_part_cache.db b/test/test_pcb/eagle/pcb_part_cache.db new file mode 100644 index 0000000000000000000000000000000000000000..db2f6b76a7f5b1e2a34bb7272cc15298994ce18a GIT binary patch literal 147456 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 literal 0 HcmV?d00001 diff --git a/test/test_pcb/test_bommatch.py b/test/test_pcb/test_bommatch.py new file mode 100644 index 0000000..a24a5d5 --- /dev/null +++ b/test/test_pcb/test_bommatch.py @@ -0,0 +1,105 @@ +import unittest +import os.path +import os +from nose.tools import * +from pymomo.exceptions import * +from pymomo.pcb import CircuitBoard +import pymomo.pcb.partcache +import tempfile +import shutil +import atexit +import hashlib + +class TestBOMCreation(unittest.TestCase): + """ + Test to make sure that BOM matching and BOM generation work. + """ + + def _create_tempfile(self, src=None): + """ + Create a named temporary file that is a copy of the src file + and register for it to be deleted at program exit. + """ + dst = tempfile.NamedTemporaryFile(delete=False) + + if src is not None: + with open(src, "r+b") as f: + shutil.copyfileobj(f, dst) + + dst.close() + + atexit.register(os.remove, dst.name) + return dst.name + + def _hash_file(self, path): + hasher = hashlib.sha256() + + with open(path,'r') as f: + block = 2**16 - 1 + buf = f.read(block) + while len(buf) > 0: + hasher.update(buf) + buf = f.read(block) + + return hasher.hexdigest() + + def setUp(self): + self.srcbrd = self._create_tempfile(os.path.join(os.path.dirname(__file__), 'eagle', 'controller_missing_attrs.brd')) + cachefile = self._create_tempfile(os.path.join(os.path.dirname(__file__), 'eagle', 'pcb_part_cache.db')) + + #Make sure we use our cache file and don't let it expire so we don't delete all of the entries + pymomo.pcb.partcache.default_cachefile(cachefile) + pymomo.pcb.partcache.default_noexpire(True) + self.board = CircuitBoard(self.srcbrd) + self.board.set_match_engine("CacheOnlyMatcher") + + def tearDown(self): + pass + + def test_cached_matching(self): + """ + Make sure that the cache is working + """ + + self.board.match_status() + print self.board.match_details() + + assert self.board.match_status() == True + + def test_lookup(self): + part = self.board.lookup('U5') + assert part is not None + + def test_update_metadata(self): + self.board.update_metadata('U5') + + board1 = CircuitBoard(self.srcbrd) + part = board1.find('U5') + + print part.mpn + print part.manu + print part.desc + + assert part.mpn == 'M25PX80-VMN6TP' + assert part.manu == 'MICRON' + + def test_update_all_metadata(self): + self.board.update_all_metadata() + + board1 = CircuitBoard(self.srcbrd) + + for part in board1._iterate_parts(): + assert part.mpn is not None + assert part.manu is not None + + def test_export_excel(self): + """ + Make sure that the BOM exporter works for excel files without error + """ + + output = self._create_tempfile() + + self.board.export_bom(output, format='excel') + #We can't verify the output with a hash since there's an embedded timestamp + #in the file format so just make sure it runs without error. + diff --git a/test/test_pcb/test_cache.py b/test/test_pcb/test_cache.py new file mode 100644 index 0000000..611a86e --- /dev/null +++ b/test/test_pcb/test_cache.py @@ -0,0 +1,56 @@ +import unittest +import os.path +from nose.tools import * +from pymomo.exceptions import * +from pymomo.pcb.partcache import PartCache +import tempfile +import shutil +import atexit + +class TestCache(unittest.TestCase): + """ + Test to make sure that caching of part request responses is working. + """ + + def _create_tempfile(self, src=None): + """ + Create a named temporary file that is a copy of the src file + and register for it to be deleted at program exit. + """ + dst = tempfile.NamedTemporaryFile(delete=False) + + if src is not None: + with open(src, "r+b") as f: + shutil.copyfileobj(f, dst) + + dst.close() + + atexit.register(os.remove, dst.name) + return dst.name + + def setUp(self): + self.filled_cachefile = self._create_tempfile(os.path.join(os.path.dirname(__file__), 'eagle', 'pcb_part_cache.db')) + self.empty_cachefile = self._create_tempfile() + + def test_create_cache(self): + pc = PartCache(cache=self.empty_cachefile) + + assert pc.size() == 0 + + val = {'test':'test', 'hello': 2} + pc.set('hello', val) + assert pc.size() == 1 + + stored = pc.get('hello') + assert val == stored + + def test_expire_parts(self): + """ + Make sure parts are expired like they should be + """ + + pc1 = PartCache(cache=self.filled_cachefile, no_expire=True) + assert pc1.size() > 0 + + pc2 = PartCache(cache=self.filled_cachefile, no_expire=False, expiration=1) + assert pc2.size() == 0 diff --git a/test/test_pcb/test_circuitboard.py b/test/test_pcb/test_circuitboard.py new file mode 100644 index 0000000..861ddec --- /dev/null +++ b/test/test_pcb/test_circuitboard.py @@ -0,0 +1,23 @@ +import unittest +import os.path +import os +from nose.tools import * +from pymomo.exceptions import * +from pymomo.pcb import CircuitBoard +import tempfile +import shutil + +def test_check_attributes(): + #Board is missing Company and Dimension unit attributes + board = os.path.join(os.path.dirname(__file__), 'eagle', 'controller_missing_attrs.brd') + b = CircuitBoard(board) + + assert b.is_clean() == False + errors = b.get_errors() + warnings = b.get_warnings() + + print errors + print warnings + + assert len(errors) == 3 + assert len(warnings) == 0 diff --git a/test/test_pcb/test_eagleparsing.py b/test/test_pcb/test_eagleparsing.py new file mode 100644 index 0000000..053c8e4 --- /dev/null +++ b/test/test_pcb/test_eagleparsing.py @@ -0,0 +1,103 @@ +import unittest +import os.path +from nose.tools import * +from pymomo.exceptions import * +from pymomo.pcb.eagle import part +from pymomo.pcb import CircuitBoard +import tempfile +import shutil +import os + +def test_parse_distpn(): + #Board is missing Company and Dimension unit attributes + board1 = os.path.join(os.path.dirname(__file__), 'eagle', 'controller_missing_attrs.brd') + board2 = os.path.join(os.path.dirname(__file__), 'eagle', 'controller_dist_distpn.brd') + b1 = CircuitBoard(board1) + b2 = CircuitBoard(board2) + + p1 = b1.find('U5') + p2 = b2.find('U5') + + print str(p1) + print str(p2) + + assert p1 == p2 + +def test_assy_vars(): + board = os.path.join(os.path.dirname(__file__), 'eagle', 'assyvars.brd') + b2 = CircuitBoard(board) + variants = b2.get_variants() + print variants + + assert len(variants) == 3 + assert 'VAR1' in variants + assert 'VAR2' in variants + assert 'VAR3' in variants + + p1 = b2.find('R1', 'VAR1') + p2 = b2.find('R1', 'VAR2') + + print p1 + print p2 + + assert p1.value == '1K' + assert p2.value == '25k' + + assert p1.dist == 'Digikey' + assert p2.dist == 'Digikey' + + assert p1.distpn == '1' + assert p2.distpn == '2' + +@raises(ArgumentError) +def test_assy_nopop(): + board = os.path.join(os.path.dirname(__file__), 'eagle', 'assyvars.brd') + b2 = CircuitBoard(board) + + b2.find('R1', 'VAR3') + +def test_nopop_list(): + board = os.path.join(os.path.dirname(__file__), 'eagle', 'assyvars.brd') + b2 = CircuitBoard(board) + + p1 = b2.nonpopulated_parts('VAR1') + p2 = b2.nonpopulated_parts('VAR2') + p3 = b2.nonpopulated_parts('VAR3') + + assert len(p1) == 0 + assert len(p2) == 0 + assert len(p3) == 1 + + assert p3[0] == 'R1' + +def test_update_attribute(): + board = os.path.join(os.path.dirname(__file__), 'eagle', 'controller_missing_attrs.brd') + brdcopy = _copy_to_temp(board) + + b = CircuitBoard(brdcopy) + + u5 = b.find('U5') + assert u5.mpn == None + assert u5.manu == None + + b.board.set_metadata(u5, None, 'mpn', "test_mpn") + b.board.set_metadata(u5, None, 'manu', "test_manu") + b.board.set_metadata(u5, None, 'digikey-pn', "test_digipn") + bn = CircuitBoard(brdcopy) + + u5 = bn.find('U5') + assert u5.mpn == 'TEST_MPN' + assert u5.manu == 'TEST_MANU' + assert u5.distpn == 'TEST_DIGIPN' + + os.remove(brdcopy) + +def _copy_to_temp(src): + dst = tempfile.NamedTemporaryFile(delete=False) + + with open(src, "r+b") as f: + shutil.copyfileobj(f, dst) + + dst.close() + + return dst.name \ No newline at end of file diff --git a/test/test_pcb/test_production.py b/test/test_pcb/test_production.py new file mode 100644 index 0000000..d809890 --- /dev/null +++ b/test/test_pcb/test_production.py @@ -0,0 +1,76 @@ +import unittest +import os.path +import os +from nose.tools import * +from nose.plugins.skip import SkipTest +from pymomo.exceptions import * +from pymomo.pcb import CircuitBoard +from pymomo.pcb.production import ProductionFileGenerator +from distutils.spawn import find_executable +import pymomo +import tempfile +import shutil +import atexit + +class TestProductionFileGeneration(unittest.TestCase): + """ + Test to make sure that BOM matching and BOM generation work. + """ + + def _create_tempfile(self, src=None, ext=None): + """ + Create a named temporary file that is a copy of the src file + and register for it to be deleted at program exit. + """ + dst = tempfile.NamedTemporaryFile(delete=False) + + if src is not None: + with open(src, "r+b") as f: + shutil.copyfileobj(f, dst) + + dst.close() + + name = dst.name + if ext is not None: + name = dst.name+"." + ext + os.rename(dst.name, name) + + atexit.register(os.remove, name) + return name + + def _create_tempdir(self): + path = tempfile.mkdtemp() + atexit.register(shutil.rmtree, path) + return path + + def setUp(self): + self.srcbrd = self._create_tempfile(os.path.join(os.path.dirname(__file__), 'eagle', 'controller_complete.brd'), ext='brd') + cachefile = self._create_tempfile(os.path.join(os.path.dirname(__file__), 'eagle', 'pcb_part_cache.db')) + + #Make sure we use our cache file and don't let it expire so we don't delete all of the entries + pymomo.pcb.partcache.default_cachefile(cachefile) + pymomo.pcb.partcache.default_noexpire(True) + self.board = CircuitBoard(self.srcbrd) + self.board.set_match_engine("CacheOnlyMatcher") + + def tearDown(self): + pass + + def test_basic(self): + """ + Basic tests of ProductionFileGenerator + """ + + prod = ProductionFileGenerator(self.board) + prod.save_readme(self._create_tempfile()) + + def test_prod(self): + try: + eagle = self.board.board._find_eagle() + except EnvironmentError: + raise SkipTest("Eagle could not be found") + + print eagle + + out = self._create_tempdir() + self.board.generate_production(out) diff --git a/test/test_typedargs/extra_type_package/__init__.py b/test/test_typedargs/extra_type_package/__init__.py index 6e3ac21..00939db 100644 --- a/test/test_typedargs/extra_type_package/__init__.py +++ b/test/test_typedargs/extra_type_package/__init__.py @@ -1,2 +1,2 @@ -new_pkg_type = {} \ No newline at end of file +import extra_type as new_pkg_type \ No newline at end of file diff --git a/test/test_typedargs/extra_type_package/extra_type.py b/test/test_typedargs/extra_type_package/extra_type.py new file mode 100644 index 0000000..17b0fc1 --- /dev/null +++ b/test/test_typedargs/extra_type_package/extra_type.py @@ -0,0 +1,7 @@ +#extra_type.py + +def convert(arg, **kwargs): + return None + +def default_formatter(arg, **kwargs): + return "" \ No newline at end of file diff --git a/test/test_typedargs/extra_types.py b/test/test_typedargs/extra_types.py index 10fd277..ead2dbc 100644 --- a/test/test_typedargs/extra_types.py +++ b/test/test_typedargs/extra_types.py @@ -1,2 +1,8 @@ -new_type = {} \ No newline at end of file +class new_type: + def convert(cls, value): + pass + + def default_formatter(cls, value): + pass + diff --git a/test/test_typedargs/test_builtin_types.py b/test/test_typedargs/test_builtin_types.py new file mode 100644 index 0000000..303a3bf --- /dev/null +++ b/test/test_typedargs/test_builtin_types.py @@ -0,0 +1,72 @@ +from pymomo.utilities.typedargs import type_system +from pymomo.utilities import typedargs +from nose.tools import * +from pymomo.exceptions import * + +def test_builtins_exist(): + builtin = ['integer', 'path', 'string'] + + for b in builtin: + type_system.get_type(b) + +def test_builtin_conversions(): + val = type_system.convert_to_type('42', 'integer') + assert val == 42 + + val = type_system.convert_to_type('/users/timburke', 'path') + assert val == '/users/timburke' + + val = type_system.convert_to_type('hello', 'string') + assert val == 'hello' + +def test_annotation_correct(): + @typedargs.param("string_param", "string", desc='Hello') + def function_test(string_param): + pass + + function_test("hello") + +@raises(ArgumentError) +def test_annotation_unknown_type(): + @typedargs.param("string_param", "unknown_type", desc='Hello') + def function_test(string_param): + pass + + function_test("hello") + +@raises(ValidationError) +def test_annotation_validation(): + @typedargs.param("int_param", "integer", "nonnegative", desc="No desc") + def function_test(int_param): + pass + + function_test(-1) + +def test_bool_valid(): + val = type_system.convert_to_type('True', 'bool') + assert val == True + + val = type_system.convert_to_type('false', 'bool') + assert val == False + + val = type_system.convert_to_type(True, 'bool') + assert val == True + + val = type_system.convert_to_type(False, 'bool') + assert val == False + + val = type_system.convert_to_type(None, 'bool') + assert val is None + + val = type_system.convert_to_type(0, 'bool') + assert val == False + + val = type_system.convert_to_type(1, 'bool') + assert val == True + +def test_format_bool(): + val = type_system.format_value(True, 'bool') + assert val == 'True' + + val = type_system.format_value(False, 'bool') + assert val == 'False' diff --git a/test/test_typedargs/test_complex_types.py b/test/test_typedargs/test_complex_types.py new file mode 100644 index 0000000..3a61d40 --- /dev/null +++ b/test/test_typedargs/test_complex_types.py @@ -0,0 +1,32 @@ +from pymomo.utilities import typedargs +from pymomo.utilities.typedargs import type_system +import unittest +from nose.tools import * +import os.path +from pymomo.exceptions import * + +def test_splitting(): + base, is_complex, subs = type_system.split_type('map(string, integer)') + assert base == 'map' + assert is_complex == True + assert len(subs) == 2 + assert subs[0] == 'string' + assert subs[1] == 'integer' + +def test_map_type(): + mapper = type_system.get_type('map(string, string)') + +def test_map_formatting(): + val = {'hello': 5} + + formatted = type_system.format_value(val, 'map(string, integer)') + assert formatted == 'hello: 5' + +def test_list_type(): + mapper = type_system.get_type('list(integer)') + +def test_list_formatting(): + val = [10, 15] + + formatted = type_system.format_value(val, 'list(integer)') + assert formatted == "10\n15" diff --git a/test/test_typedargs/test_injection.py b/test/test_typedargs/test_injection.py index b603aad..435de69 100644 --- a/test/test_typedargs/test_injection.py +++ b/test/test_typedargs/test_injection.py @@ -1,16 +1,17 @@ from pymomo.utilities import typedargs +from pymomo.utilities.typedargs import type_system import unittest from nose.tools import * import os.path from pymomo.exceptions import * def test_type_injection(): - typeobj = {} + import extra_type_package.extra_type as typeobj - assert not typedargs.is_known_type("test_injected_type1") + assert not type_system.is_known_type("test_injected_type1") - typedargs.inject_type("test_injected_type1", typeobj) - assert typedargs.is_known_type("test_injected_type1") + type_system.inject_type("test_injected_type1", typeobj) + assert type_system.is_known_type("test_injected_type1") def test_external_module_injection(): """ @@ -19,9 +20,9 @@ def test_external_module_injection(): path = os.path.join(os.path.dirname(__file__), 'extra_types') - assert not typedargs.is_known_type('new_type') - typedargs.load_external_types(path) - assert typedargs.is_known_type('new_type') + assert not type_system.is_known_type('new_type') + type_system.load_external_types(path) + assert type_system.is_known_type('new_type') def test_external_package_injection(): """ @@ -30,9 +31,9 @@ def test_external_package_injection(): path = os.path.join(os.path.dirname(__file__), 'extra_type_package') - assert not typedargs.is_known_type('new_pkg_type') - typedargs.load_external_types(path) - assert typedargs.is_known_type('new_pkg_type') + assert not type_system.is_known_type('new_pkg_type') + type_system.load_external_types(path) + assert type_system.is_known_type('new_pkg_type') @raises(ArgumentError) def test_external_package_injection_failure(): @@ -41,4 +42,4 @@ def test_external_package_injection_failure(): """ path = os.path.join(os.path.dirname(__file__), 'extra_type_package_nonexistant') - typedargs.load_external_types(path) + type_system.load_external_types(path) diff --git a/test/test_typedargs/test_returntypes.py b/test/test_typedargs/test_returntypes.py new file mode 100644 index 0000000..eb2f86d --- /dev/null +++ b/test/test_typedargs/test_returntypes.py @@ -0,0 +1,24 @@ +from pymomo.utilities.typedargs import type_system +from pymomo.utilities import typedargs +from nose.tools import * +from pymomo.exceptions import * + +def test_simplereturntype(): + @typedargs.return_type("string") + def returns_string(): + return "hello" + + val = returns_string() + formed = type_system.format_return_value(returns_string, val) + + assert formed == "hello" + +def test_complexreturntype(): + @typedargs.return_type("map(string, integer)") + def returns_map(): + return {"hello": 5} + + val = returns_map() + formed = type_system.format_return_value(returns_map, val) + + assert formed == "hello: 5" diff --git a/test/test_utilities/test_settings_dir.py b/test/test_utilities/test_settings_dir.py index 9f90145..ca4abf0 100644 --- a/test/test_utilities/test_settings_dir.py +++ b/test/test_utilities/test_settings_dir.py @@ -29,7 +29,6 @@ def test_settings_windows(self): base = self.confdir settings_dir = os.path.abspath(os.path.join(base, 'WellDone-MoMo')) - assert settings_dir == self.paths.settings @unittest.skipIf(platform.system() != 'Darwin', 'Mac OS X specific test')