Support for extra data in the Value field

- Currently we just use the tolerance for the 3D resistors
- Uses a port of the JavaScript Electro-Grammar
This commit is contained in:
Salvador E. Tropea 2023-03-30 12:39:36 -03:00
parent 4de3152ac9
commit 158f267eb5
20 changed files with 840 additions and 136 deletions

View File

@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
using all pages or individually.
- Plot related outputs:
- All outputs now support scaling.
- BoM:
- Support for extra information in the *Value* field.
Currently just parsed, not rejected.
### Fixed
- Makefile: don't skip all preflights on each run, just the ones we generate
@ -33,6 +36,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Diff:
- Problems when using an output and no variant specified.
### Changed:
- Some R, L and C values that were rejected are accepted now. You just get a
warning about what part of the value was discarded.
## [1.6.1] - 2023-03-16
### Added

View File

@ -11,6 +11,7 @@ include kibot/resources/config_templates/panelize/*.yaml
include kibot/resources/config_templates/*.yaml
include kibot/resources/images/*.svg
include kibot/resources/images/*.ico
include kibot/resources/parsers/*.lark
include kibot/resources/pcbdraw/styles/*.json
include kibot/resources/pcbdraw/templates/*.handlebars
include kibot/blender_scripts/*.py

View File

@ -48,7 +48,7 @@ test: lint
rm -f tests/.local
$(PY_COV) erase
# python3-pytest-xdist
$(PYTEST) -m "not slow" -n 2 --test_dir=output
$(PYTEST) -m "not slow" -n 4 --test_dir=output
$(PYTEST) -m "slow" --test_dir=output
$(PY_COV) combine
$(PY_COV) report

1
debian/install vendored
View File

@ -4,3 +4,4 @@ kibot/resources/kicad_colors/ /usr/share/kibot/
kibot/resources/kicad_layouts/ /usr/share/kibot/
kibot/resources/pcbdraw/ /usr/share/kibot/
kibot/resources/report_templates/ /usr/share/kibot/
kibot/resources/parsers/ /usr/share/kibot/

View File

@ -218,6 +218,7 @@ This file comes from KiKit, but it has too much in common with `populate.py`.
## 2023-03-20 Various fixes and changes in resistor colors
```diff
diff --git a/kibot/PcbDraw/plot.py b/kibot/PcbDraw/plot.py
index 8ca660e6..9dc45ba9 100644
--- a/kibot/PcbDraw/plot.py
@ -288,10 +289,11 @@ index 8ca660e6..9dc45ba9 100644
ref = footprint.GetReference().strip()
center = footprint.GetPosition()
orient = math.radians(footprint.GetOrientation().AsDegrees())
```
## 2023-03-27 Fixe for KiCad 7.0.1 polygons
```diff
diff --git a/kibot/PcbDraw/plot.py b/kibot/PcbDraw/plot.py
index 9dc45ba9..8df84469 100644
--- a/kibot/PcbDraw/plot.py
@ -320,4 +322,82 @@ index 9dc45ba9..8df84469 100644
elif svg_element.tag == "circle":
# Convert circle to path
att = svg_element.attrib
```
## 2023-03-30 Removed the tolerance look-up, now using electro_grammar
So now *unit.py* is in charge of returning the tolerance.
Note that we still use a field, but in a very ridiculous way because we add it to the value, to then separate it.
```diff
diff --git a/kibot/PcbDraw/plot.py b/kibot/PcbDraw/plot.py
index 23b7d31f..65fbea66 100644
--- a/kibot/PcbDraw/plot.py
+++ b/kibot/PcbDraw/plot.py
@@ -938,21 +938,20 @@ class PlotComponents(PlotInterface):
return
def _get_resistance_from_value(self, value: str) -> Tuple[Decimal, str]:
- res, tolerance = None, str(GS.global_default_resistor_tolerance)+"%"
- value_l = value.split(" ", maxsplit=1)
+ res, tolerance = None, None
try:
- res = read_resistance(value_l[0])
+ res, tolerance = read_resistance(value)
except ValueError:
- raise UserWarning(f"Invalid resistor value {value_l[0]}")
- if len(value_l) > 1:
- t_string = value_l[1].strip().replace(" ", "")
- if "%" in t_string:
- s = self._plotter.get_style("tht-resistor-band-colors")
- if not isinstance(s, dict):
- raise RuntimeError(f"Invalid style specified, tht-resistor-band-colors should be dictionary, got {type(s)}")
- if t_string.strip() not in s:
- raise UserWarning(f"Invalid resistor tolerance {value_l[1]}")
- tolerance = t_string
+ raise UserWarning(f"Invalid resistor value {value}")
+ if tolerance is None:
+ tolerance = GS.global_default_resistor_tolerance
+ tolerance = str(tolerance)+"%"
+ s = self._plotter.get_style("tht-resistor-band-colors")
+ if not isinstance(s, dict):
+ raise RuntimeError(f"Invalid style specified, tht-resistor-band-colors should be dictionary, got {type(s)}")
+ if tolerance not in s:
+ raise UserWarning(f"Invalid resistor tolerance {tolerance}")
+ tolerance = "5%"
return res, tolerance
@@ -1113,7 +1112,7 @@ class PcbPlotter():
prop = footprint.GetProperties()
tol = next(filter(lambda x: x, map(prop.get, GS.global_field_tolerance)), None)
if tol:
- value = value+' '+tol
+ value = value+' '+tol.strip()
ref = footprint.GetReference().strip()
center = footprint.GetPosition()
orient = math.radians(footprint.GetOrientation().AsDegrees())
diff --git a/kibot/PcbDraw/unit.py b/kibot/PcbDraw/unit.py
index 2fad683c..0c5dfcab 100644
--- a/kibot/PcbDraw/unit.py
+++ b/kibot/PcbDraw/unit.py
@@ -1,10 +1,9 @@
# Author: Salvador E. Tropea
# License: MIT
-from decimal import Decimal
from ..bom.units import comp_match
-def read_resistance(value: str) -> Decimal:
+def read_resistance(value: str):
"""
Given a string, try to parse resistance and return it as Ohms (Decimal)
@@ -13,5 +12,4 @@ def read_resistance(value: str) -> Decimal:
res = comp_match(value, 'R')
if res is None:
raise ValueError(f"Cannot parse '{value}' to resistance")
- v, mul, uni = res
- return Decimal(str(v))*Decimal(str(mul[0]))
+ return res.get_decimal(), res.get_extra('tolerance')
```

View File

@ -938,21 +938,20 @@ class PlotComponents(PlotInterface):
return
def _get_resistance_from_value(self, value: str) -> Tuple[Decimal, str]:
res, tolerance = None, str(GS.global_default_resistor_tolerance)+"%"
value_l = value.split(" ", maxsplit=1)
res, tolerance = None, None
try:
res = read_resistance(value_l[0])
res, tolerance = read_resistance(value)
except ValueError:
raise UserWarning(f"Invalid resistor value {value_l[0]}")
if len(value_l) > 1:
t_string = value_l[1].strip().replace(" ", "")
if "%" in t_string:
s = self._plotter.get_style("tht-resistor-band-colors")
if not isinstance(s, dict):
raise RuntimeError(f"Invalid style specified, tht-resistor-band-colors should be dictionary, got {type(s)}")
if t_string.strip() not in s:
raise UserWarning(f"Invalid resistor tolerance {value_l[1]}")
tolerance = t_string
raise UserWarning(f"Invalid resistor value {value}")
if tolerance is None:
tolerance = GS.global_default_resistor_tolerance
tolerance = str(tolerance)+"%"
s = self._plotter.get_style("tht-resistor-band-colors")
if not isinstance(s, dict):
raise RuntimeError(f"Invalid style specified, tht-resistor-band-colors should be dictionary, got {type(s)}")
if tolerance not in s:
raise UserWarning(f"Invalid resistor tolerance {tolerance}")
tolerance = "5%"
return res, tolerance
@ -1113,7 +1112,7 @@ class PcbPlotter():
prop = footprint.GetProperties()
tol = next(filter(lambda x: x, map(prop.get, GS.global_field_tolerance)), None)
if tol:
value = value+' '+tol
value = value+' '+tol.strip()
ref = footprint.GetReference().strip()
center = footprint.GetPosition()
orient = math.radians(footprint.GetOrientation().AsDegrees())

View File

@ -1,10 +1,9 @@
# Author: Salvador E. Tropea
# License: MIT
from decimal import Decimal
from ..bom.units import comp_match
def read_resistance(value: str) -> Decimal:
def read_resistance(value: str):
"""
Given a string, try to parse resistance and return it as Ohms (Decimal)
@ -13,5 +12,4 @@ def read_resistance(value: str) -> Decimal:
res = comp_match(value, 'R')
if res is None:
raise ValueError(f"Cannot parse '{value}' to resistance")
v, mul, uni = res
return Decimal(str(v))*Decimal(str(mul[0]))
return res.get_decimal(), res.get_extra('tolerance')

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 Salvador E. Tropea
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2020-2023 Salvador E. Tropea
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2016-2020 Oliver Henry Walters (@SchrodingersGat)
# License: MIT
# Project: KiBot (formerly KiPlot)
@ -12,7 +12,7 @@ All the logic to convert a list of components into the rows and columns used to
import locale
from copy import deepcopy
from math import ceil
from .units import compare_values, comp_match, get_last_warning
from .units import compare_values, comp_match
from .bom_writer import write_bom
from .columnlist import ColumnList
from ..misc import DNF, W_FIELDCONF, W_MISSFPINFO
@ -382,14 +382,7 @@ def get_value_sort(comp, fallback_ref=False):
""" Try to better sort R, L and C components """
res = comp.value_sort
if res:
value, (mult, mult_s), unit = res
if comp.ref_prefix in "CL":
# femto Farads
value = "{0:15d}".format(int(value * 1e15 * mult + 0.1))
else:
# milli Ohms
value = "{0:15d}".format(int(value * 1000 * mult + 0.1))
return value
return res.get_sortable()
if fallback_ref:
return comp.ref_prefix + "{0:15d}".format(_suffix_to_num(comp.ref_suffix))
return comp.value
@ -397,14 +390,11 @@ def get_value_sort(comp, fallback_ref=False):
def normalize_value(c, decimal_point):
if c.value_sort is None:
return c.value
value, (mult, mult_s), unit = c.value_sort
ivalue = int(value)
if value == ivalue:
value = ivalue
elif decimal_point:
value = str(value).replace('.', decimal_point)
return '{} {}{}'.format(value, mult_s, unit)
return c.value.strip()
value = str(c.value_sort)
if decimal_point:
value = value.replace('.', decimal_point)
return value
def compute_multiple_stats(cfg, groups):
@ -443,14 +433,6 @@ def group_components(cfg, components):
# Cache the value used to sort
if c.ref_prefix in RLC_PREFIX and c.value.lower() not in DNF:
c.value_sort = comp_match(c.value, c.ref_prefix, c.ref)
if c.value_sort is None and (' ' in c.value):
# Try with the data before a space
value = c.value.split(' ')[0]
value_sort = comp_match(value, c.ref_prefix)
if value_sort is not None:
c.value_sort = value_sort
extra = ', only for sorting purposes' if not cfg.normalize_values else ''
logger.warning(get_last_warning() + "Using `{}` for {} instead{}".format(value, c.ref, extra))
else:
c.value_sort = None
# Try to add the component to an existing group

View File

@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2023 Salvador E. Tropea
# Copyright (c) 2023 Instituto Nacional de Tecnología Industrial
# License: MIT
# Project: KiBot (formerly KiPlot)
from decimal import Decimal
from lark import Lark, Transformer
import os
from ..gs import GS
from .. import log
logger = log.get_logger()
# Metric to imperial package sizes
TO_IMPERIAL = {'0402': '01005',
'0603': '0201',
'1005': '0402',
'1608': '0603',
'2012': '0805',
'2520': '1008',
'3216': '1206',
'3225': '1210',
'4516': '1806',
'4532': '1812',
'5025': '2010',
'6332': '2512'}
parser = None
class ComponentTransformer(Transformer):
""" Transforms a tree parsed by Lark to the electro-grammar dict """
def __init__(self):
self.parsed = {}
# Extra information, not in the original lib and needed for internal purposes
self.extra = {}
def value3(self, d, type):
""" VALUE [METRIC_PREFIX [MANTISSA]] """
v = Decimal(d[0])
c = len(d)
if c >= 3:
# We have something like 2n2
dec = d[2]
c_dec = len(dec)
v += Decimal(dec)/(Decimal(10)*c_dec)
self.extra['val'] = v
if c >= 2:
# Metric prefix
v *= d[1]
self.extra['mult'] = d[1]
else:
self.extra['mult'] = Decimal(1)
v = float(v)
self.parsed[type] = v
return v
def value2(self, d, type):
""" VALUE [MANTISSA] """
v = float(d[0])
c = len(d)
if c >= 2:
# We have something like 3V3
dec = d[1]
c_dec = len(dec)
v += float(dec)/(10.0*c_dec)
self.parsed[type] = v
return v
def value1(self, d, type):
""" VALUE """
v = float(d[0])
iv = int(d[0])
if iv == v:
v = iv
self.parsed[type] = v
return v
def tolerance(self, d):
return self.value1(d, 'tolerance')
def voltage_rating(self, d):
return self.value2(d, 'voltage_rating')
def temp_coef(self, d):
c_len = len(d)
if c_len == 3:
# Class 2: i.e. X7R
v = d[0].value+d[1].value+d[2].value
else:
# Class 1: i.e. C0G
v = d[0].type
self.parsed['characteristic'] = v.upper()
return v
def power_rating(self, d):
if len(d) == 1:
# 1 W
v = float(d[0])
elif d[0].type == 'INT':
# 1/4 W
v = float(d[0].value)/float(d[1].value)
else:
# 250 mW
v = float(Decimal(d[0].value)*d[1])
self.parsed['power_rating'] = v
return v
def color(self, d):
c = d[0].value.lower()
self.parsed['color'] = c
return c
def set_type(self, type, d):
self.parsed['type'] = type
return d
# Package size
def imperial_size(self, d):
s = d[0].value
self.parsed['size'] = s
return s
def unambigious_metric_size(self, d):
s = TO_IMPERIAL[d[0].value]
self.parsed['size'] = s
return s
metric_size_base = unambigious_metric_size
# RLC
def resistance(self, d):
return self.value3(d, 'resistance')
resistance_no_r = resistance
def inductance(self, d):
return self.value3(d, 'inductance')
inductance_no_henry = inductance
def capacitance(self, d):
return self.value3(d, 'capacitance')
capacitance_no_farad = capacitance
# Known components
def inductor(self, d):
return self.set_type('inductor', d)
def capacitor(self, d):
return self.set_type('capacitor', d)
def resistor(self, d):
return self.set_type('resistor', d)
def led(self, d):
return self.set_type('led', d)
# Metrix prefixes
def giga(self, _):
return Decimal('1e9')
def mega(self, _):
return Decimal('1e6')
def kilo(self, _):
return Decimal('1e3')
def unit(self, _):
return Decimal(1)
def milli(self, _):
return Decimal('1e-3')
def nano(self, _):
return Decimal('1e-9')
def micro(self, _):
return Decimal('1e-6')
def pico(self, _):
return Decimal('1e-12')
def femto(self, _):
return Decimal('1e-15')
def crap(self, v):
if 'discarded' in self.extra:
self.extra['discarded'].append(v[0].value)
else:
self.extra['discarded'] = [v[0].value]
return None
def initialize():
global parser
if parser is not None:
return
with open(os.path.join(GS.get_resource_path('parsers'), 'electro.lark'), 'rt') as f:
g = f.read()
parser = Lark(g, start='main') # , debug=DEBUG)
def parse(text, with_extra=False):
initialize()
try:
tree = parser.parse(text)
except Exception as e:
logger.debugl(2, str(e))
return {}
logger.debugl(3, tree.pretty())
res_o = ComponentTransformer()
res = res_o.transform(tree)
logger.debugl(3, res)
res = res_o.parsed
if with_extra:
res.update(res_o.extra)
return res

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Salvador E. Tropea
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2020-2023 Salvador E. Tropea
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2016-2020 Oliver Henry Walters (@SchrodingersGat)
# License: MIT
# Project: KiBot (formerly KiPlot)
@ -13,10 +13,13 @@ e.g.
0R1 = 0.1Ohm (Unit replaces decimal, different units)
Oriented to normalize and sort R, L and C values.
"""
from decimal import Decimal
import re
import locale
from math import log10
from .. import log
from ..misc import W_BADVAL1, W_BADVAL2, W_BADVAL3
from ..misc import W_BADVAL1, W_BADVAL2, W_BADVAL3, W_BADVAL4
from .electro_grammar import parse
logger = log.get_logger()
@ -30,25 +33,54 @@ PREFIX_GIGA = ["giga", "g"]
# All prefixes
PREFIX_ALL = PREFIX_PICO + PREFIX_NANO + PREFIX_MICRO + PREFIX_MILLI + PREFIX_KILO + PREFIX_MEGA + PREFIX_GIGA
MAX_POW_PREFIX = 9
MIN_POW_PREFIX = -12
PREFIXES = {-15: 'f', -12: 'p', -9: 'n', -6: u"µ", -3: 'm', 0: '', 3: 'k', 6: 'M', 9: 'G'}
# Common methods of expressing component units
# Note: we match lowercase string, so both: Ω and Ω become the lowercase omega
UNIT_R = ["r", "ohms", "ohm", u'\u03c9']
UNIT_C = ["farad", "f"]
UNIT_L = ["henry", "h"]
OHMS = u"Ω"
UNIT_ALL = UNIT_R + UNIT_C + UNIT_L
GRAM_TYPES = {'inductor': 'L', 'capacitor': 'C', 'resistor': 'R', 'led': ''}
# Compiled regex to match the values
match = None
# Current locale decimal point value
decimal_point = None
# Last warning
last_warning = ''
# Parser cache
parser_cache = {}
def get_last_warning():
return last_warning
class ParsedValue(object):
def __init__(self, v, pow, unit, extra=None):
# From a value that matched the regex
ival = int(v)
self.norm_val = int(v) if v == ival else v
self.exp = pow
self.unit = unit
self.prefix = PREFIXES[pow]
self.extra = extra
def __str__(self):
return '{} {}{}'.format(self.norm_val, self.prefix, self.unit)
def get_sortable(self):
mult = pow(10, self.exp)
if self.unit in "FH":
# femto Farads
return "{0:15d}".format(int(self.norm_val * 1e15 * mult + 0.1))
# milli Ohms
return "{0:15d}".format(int(self.norm_val * 1000 * mult + 0.1))
def get_decimal(self):
return Decimal(str(self.norm_val))*pow(10, Decimal(self.exp))
def get_extra(self, property):
return self.extra.get(property) if self.extra else None
def get_unit(unit, ref_prefix):
@ -58,42 +90,54 @@ def get_unit(unit, ref_prefix):
return "H"
if ref_prefix == 'C':
return "F"
return u"Ω"
return OHMS
unit = unit.lower()
if unit in UNIT_R:
return u"Ω"
return OHMS
if unit in UNIT_C:
return "F"
if unit in UNIT_L:
return "H"
def get_prefix(prefix):
def get_prefix_simple(prefix):
""" Return the (numerical) value of a given prefix """
if not prefix:
return 1, ''
return 0
# 'M' is mega, 'm' is milli
if prefix != 'M':
prefix = prefix.lower()
if prefix in PREFIX_PICO:
return 1.0e-12, 'p'
return -12
if prefix in PREFIX_NANO:
return 1.0e-9, 'n'
return -9
if prefix in PREFIX_MICRO:
return 1.0e-6, u"µ"
return -6
if prefix in PREFIX_MILLI:
return 1.0e-3, 'm'
return -3
if prefix in PREFIX_KILO:
return 1.0e3, 'k'
return 3
if prefix in PREFIX_MEGA:
return 1.0e6, 'M'
return 6
if prefix in PREFIX_GIGA:
return 1.0e9, 'G'
return 9
# Unknown, we shouldn't get here because the regex matched
# BUT: I found that sometimes unexpected things happen, like mu matching micro and then we reaching this code
# Now is fixed, but I can't be sure some bizarre case is overlooked
logger.error('Unknown prefix, please report')
return 1, ''
return 0
def get_prefix(val, prefix):
pow = get_prefix_simple(prefix)
# Try to normalize it
while val >= 1000.0 and pow < MAX_POW_PREFIX:
val /= 1000.0
pow += 3
while val < 1.0 and pow > MIN_POW_PREFIX:
val *= 1000.0
pow -= 3
return val, pow
def group_string(group): # Return a reg-ex string for a list of values
@ -104,14 +148,27 @@ def match_string():
return r"(\d*\.?\d*)\s*(" + group_string(PREFIX_ALL) + ")*(" + group_string(UNIT_ALL) + r")*(\d*)$"
def value_from_grammar(r):
""" Convert a result parsed by the Lark grammar to a ParsedResult object """
val = r.get('val')
if not val:
return None
# Create an object with the result
val, pow = get_prefix(float(val), PREFIXES[int(log10(r['mult']))])
parsed = ParsedValue(val, pow, get_unit(GRAM_TYPES[r['type']], ''), r)
return parsed
def comp_match(component, ref_prefix, ref=None):
"""
Return a normalized value and units for a given component value string
e.g. comp_match('10R2') returns (10, R)
e.g. comp_match('3.3mOhm') returns (0.0033, R)
Also tries to separate extra data, i.e. tolerance, using a complex parser
"""
global last_warning
original = component
global parser_cache
parsed = parser_cache.get(original+ref_prefix)
if parsed:
return parsed
# Remove useless spaces
component = component.strip()
# ~ is the same as empty for KiCad
@ -128,6 +185,7 @@ def comp_match(component, ref_prefix, ref=None):
if decimal_point:
component = component.replace(decimal_point, ".")
with_commas = component
# Remove any commas
component = component.strip().replace(",", "")
@ -140,13 +198,22 @@ def comp_match(component, ref_prefix, ref=None):
where = ' in {}'.format(ref) if ref is not None else ''
result = match.match(component)
if not result:
last_warning = W_BADVAL1
logger.warning(W_BADVAL1 + "Malformed value: `{}` (no match{})".format(original, where))
return None
# Failed with the regex, try with the parser
result = parse(ref_prefix[0]+' '+with_commas, with_extra=True)
if result:
result = value_from_grammar(result)
if result and result.get_extra('discarded'):
discarded = " ".join(list(map(lambda x: '`'+x+'`', result.get_extra('discarded'))))
logger.warning(W_BADVAL4 + "Malformed value: `{}` (discarded: {}{})".format(original, discarded, where))
if not result:
logger.warning(W_BADVAL1 + "Malformed value: `{}` (no match{})".format(original, where))
return None
# Cache the result
parser_cache[original+ref_prefix] = result
return result
value, prefix, units, post = result.groups()
if value == '.':
last_warning = W_BADVAL2
logger.warning(W_BADVAL2 + "Malformed value: `{}` (reduced to decimal point{})".format(original, where))
return None
if value == '':
@ -158,7 +225,6 @@ def comp_match(component, ref_prefix, ref=None):
# We will also have a trailing number
if post:
if "." in value:
last_warning = W_BADVAL3
logger.warning(W_BADVAL3 + "Malformed value: `{}` (unit split, but contains decimal point{})".
format(original, where))
return None
@ -168,35 +234,21 @@ def comp_match(component, ref_prefix, ref=None):
else:
val = float(value)
# Return all the data, let the caller join it
return (val, get_prefix(prefix), get_unit(units, ref_prefix))
# Create an object with the result
val, pow = get_prefix(val, prefix)
parsed = ParsedValue(val, pow, get_unit(units, ref_prefix))
# Cache the result
parser_cache[original+ref_prefix] = parsed
return parsed
def compare_values(c1, c2):
""" Compare two values """
# These are the results from comp_match()
r1 = c1.value_sort
r2 = c2.value_sort
# If they can't be parsed use the value
if not r1 or not r2:
return False
# Join the data to compare
(v1, (p1, ps1), u1) = r1
(v2, (p2, ps2), u2) = r2
v1 = "{0:.15f}".format(v1 * 1.0 * p1)
v2 = "{0:.15f}".format(v2 * 1.0 * p2)
if v1 == v2:
# Values match
if u1 == u2:
return True # Units match
# No longer possible because now we use the prefix to determine absent units
# if not u1:
# return True # No units for component 1
# if not u2:
# return True # No units for component 2
return False
return c1.value.strip() == c2.value.strip()
# Compare the normalized representation, i.e. 3300 == 3k3 == 3.3 k
return str(r1) == str(r2)

View File

@ -272,6 +272,7 @@ W_BADRES = '(W123) '
W_RESVALISSUE = '(W124) '
W_RES3DNAME = '(W125) '
W_ESCINV = '(W126) '
W_BADVAL4 = '(W127) '
# Somehow arbitrary, the colors are real, but can be different
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",

View File

@ -3,6 +3,7 @@
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from decimal import Decimal
from fnmatch import fnmatch
import os
import re
@ -341,11 +342,23 @@ class Base3DOptions(VariantOptions):
return name
r_len = float(m.group(1))
# THT Resistor that we want to add colors
# Check the tolerance
# Check the value
res = comp_match(c.value, c.ref_prefix, c.ref)
if res is None:
return name
val = res.get_decimal()
if val < Decimal('0.01'):
logger.warning(W_BADRES+'Resistor {} out of range, minimum value is 10 mOhms'.format(c.ref))
return name
val_str = "{0:.0f}".format(val*100)
# Check the tolerance (from the schematic fields)
tol = next(filter(lambda x: x, map(c.get_field_value, GS.global_field_tolerance)), None)
if not tol:
tol = GS.global_default_resistor_tolerance
logger.warning(W_BADTOL+'Missing tolerance for {}, using {}%'.format(c.ref, tol))
# Try using the parsed value (i.e. Value="12k 1%")
tol = res.get_extra('tolerance')
if not tol:
tol = GS.global_default_resistor_tolerance
logger.warning(W_BADTOL+'Missing tolerance for {}, using {}%'.format(c.ref, tol))
else:
tol = tol.strip()
if tol[-1] == '%':
@ -359,15 +372,6 @@ class Base3DOptions(VariantOptions):
logger.warning(W_BADTOL+'Unknown tolerance for {}: `{}`'.format(c.ref, tol))
return name
tol_color = TOL_COLORS[tol]
# Check the value
res = comp_match(c.value, c.ref_prefix, c.ref)
if res is None:
return name
val = res[0]*res[1][0]
if val < 0.01:
logger.warning(W_BADRES+'Resistor {} out of range, minimum value is 10 mOhms'.format(c.ref))
return name
val_str = "{0:.0f}".format(val*100)
# Find how many bars we'll use
if tol < 5:
# Use 5 bars for 2 % tol or better

View File

@ -0,0 +1,214 @@
//*****************************************************************************
//
// Copyright (c) 2023 Salvador E. Tropea
// Copyright (c) 2023 Instituto Nacional de Tecnologia Industrial
// Copyright (c) 2017-2018 Kaspar Emanuel
//
// LICENSE: MIT
//
// Grammar to parse electronic components.
// Can currently parse resistors, capacitors, inductors and LEDs.
// Is strongly based on "electro-grammar" created by Kaspar Emanuel.
// This description was coded to be used with Lark, a Python tool.
// Is an ambiguous description, so it needs the Early algorithm.
// Unlike the original version by Kaspar, implemented using Nearly, this
// grammar handles the stuff to ignore in the grammar description, not retrying
// in the parser. I think Lark can't retry. This makes things a little bit more
// complicated, and harder to debug.
//
//*****************************************************************************
%import common.INT
%import common.NUMBER
%import common.WS
%import common.CNAME
?main: capacitor | inductor | resistor | led
// All we ignore
crap: CNAME | NUMBER
// Whitespace and separators
_WS: WS
_SEP: _WS | /[,;]/
//************************************
//************ Capacitors ************
//************************************
// When we know this is a capacitor we don't need the units
capacitor: _c_specs capacitance _c_specs
| _CAP _c_specs (capacitance_no_farad | capacitance)? (_c_specs | c_spec)
_c_specs: ((c_spec _SEP)+ | (_SEP c_spec)+ | _SEP)*
?c_spec: tolerance | temp_coef | voltage_rating | package_size | crap
// Give priority to things like "25 V" to avoid separating the "V" as crap
voltage_rating.6: NUMBER _WS? _VOLT INT?
// See https://en.wikipedia.org/wiki/Ceramic_capacitor#Class_1_ceramic_capacitor
// https://en.wikipedia.org/wiki/Ceramic_capacitor#Class_2_ceramic_capacitor
temp_coef: _class1 | _class2
_class1: P100 | C0G | N33 | N75 | N150 | N220 | N330 | N470 | N750 | N1000 | N1500
_class2: /[XYZ]/i "4".."9" /[PRSTUV]/i
tolerance: (_PLUSMINUS _WS?)? NUMBER _WS? "%"
capacitance.10: (_capacitance_no_farad | NUMBER) _WS? _FARAD
capacitance_no_farad.10: _capacitance_no_farad
_capacitance_no_farad: INT _c_metric_prefix INT?
| NUMBER _WS? _c_metric_prefix
_c_metric_prefix: milli | micro | nano | pico
//***********************************
//************ Inductors ************
//***********************************
// When we know this is an inductor we don't need the units
inductor: _l_specs inductance _l_specs
| _IND _l_specs (inductance_no_henry | inductance)? (_l_specs | l_spec)
_l_specs: ((l_spec _SEP)+ | (_SEP l_spec)+ | _SEP)*
?l_spec: tolerance | voltage_rating | package_size | crap
inductance: _inductance_no_henry _WS? _HENRY
inductance_no_henry: _inductance_no_henry
_inductance_no_henry: NUMBER _WS? _l_metric_prefix? INT?
_l_metric_prefix: milli | micro | nano | pico
//***********************************
//************ Resistors ************
//***********************************
// When we know this is a resistor we don't need the units
resistor: _r_specs resistance _r_specs
| _RES _r_specs (resistance_no_r | resistance)? (_r_specs | r_spec)
_r_specs: ((r_spec _SEP)+ | (_SEP r_spec)+ | _SEP)*
?r_spec: tolerance | power_rating | package_size | crap
power_rating: _power_rating_decimal | _power_rating_fraction
_power_rating_fraction: INT "/" INT _WS? _WATTS
_power_rating_decimal: NUMBER _WS? _power_metric_prefix? _WS? _WATTS
_power_metric_prefix: giga | mega | kilo | milli | micro | nano | pico | femto
resistance.9: NUMBER _WS? (_r_metric_prefix INT? (_WS? _OHM)? | _OHM)
// Just a number, no R, K, ohm etc.
resistance_no_r.9: NUMBER
_r_metric_prefix: giga | mega | kilo | unit | milli | micro
//******************************
//************ LEDs ************
//******************************
led: _led_specs _LED (_SEP _led_specs _led_spec?)?
_led_specs: ((_led_spec _SEP)+ | (_SEP _led_spec)+ | _SEP)*
_led_spec: package_size | color | crap
!color: "red"i
| "green"i
| "blue"i
| "yellow"i
| "orange"i
| "white"i
| "amber"i
| "cyan"i
| "purple"i
| "yellow" WS "green"
//******************************
//************ Size ************
//******************************
// Sizes looks like numbers and resistors doesn't need units, things like 2512 are hard to differentiate from a
// resistor value. So we use a high priority here
?package_size.11: imperial_size | metric_size
!imperial_size.11: IS01005 | IS0201 | IS0402 | IS0603 | IS0805 | IS1008 | IS1206 | IS1210 | IS1806 | IS2010 | IS2512
?metric_size.11: metric_size_base _WS _METRIC
| _METRIC _WS metric_size_base
| unambigious_metric_size
// Metric sizes, with names to avoid anonymous
!unambigious_metric_size.11: MS1005 | MS1608 | MS2012 | MS2520 | MS3216 | MS3225 | MS4516 | MS5025 | MS6332
!metric_size_base.11: unambigious_metric_size | MS0402 | MS0603
//******************************
//****** Metric prefixes *******
//******************************
// !exa: "E" | "exa"i
// !peta: "P" | "peta"i
// !tera: "T" | "tera"i
!giga: "G" | "gig"i "a"i?
!mega: "M" | "meg"i "a"i?
!kilo: "K"i "ilo"i?
!unit: "R"i
// !hecto: "h" | "hecto"i
// !deci: "d" | "deci"i
// !centi: "c" | "centi"i
!milli: "m" | "milli"i
!micro: "U"i
| "\u03BC"
| "\u00B5"
| "𝛍"
| "𝜇"
| "𝝁"
| "𝝻"
| "𝞵"
| /micro/i
!nano: "N"i | "nan"i "o"i?
!pico: "P"i "ico"i?
!femto: "f" | "femto"i
// !atto: "a" | "atto"i
//******************************
//****** Named terminals *******
//******************************
// Components
_CAP: ("CAPACITOR"i | "CAPA"i | "C"i "AP"i?) _SEP
_RES: ("RESISTOR"i | "RES"i | "R"i) _SEP
_IND: ("IND"i "UCTOR"i? | "L"i) _SEP
_LED: "LED"i
// Units
_FARAD: "F"i "arad"i?
_OHM: "ohm"i "s"i? | "Ω" | "Ω"
_HENRY: "h"i "enry"i?
_VOLT: "Volt"i "s"i? | "V"i
_WATTS: "W"i "atts"i?
// Used for percent
_PLUSMINUS: "+/-" | "±" | "+-"
// Size
IS01005: "01005"
IS0201: "0201"
IS0402: "0402"
IS0603: "0603"
IS0805: "0805"
IS1008: "1008"
IS1206: "1206"
IS1210: "1210"
IS1806: "1806"
IS2010: "2010"
IS2512: "2512"
_METRIC: "METRIC"i
MS1005: "1005"
MS1608: "1608"
MS2012: "2012"
MS2520: "2520"
MS3216: "3216"
MS3225: "3225"
MS4516: "4516"
MS5025: "5025"
MS6332: "6332"
MS0402: "0402"
MS0603: "0603"
// Capacitor temp. coef. classes
P100: "P100"i "/" "M7G"i | "M7G"i "/" "P100"i | "P100"i | "M7G"i
N33: "N33"i "/" "H2G"i | "H2G"i "/" "N33"i | "N33"i | "H2G"i
N75: "N75"i "/" "L2G"i | "L2G"i "/" "N75"i | "N75"i | "L2G"i
N150: "N150"i "/" "P2H"i | "P2H"i "/" "N150"i | "N150"i | "P2H"i
N220: "N220"i "/" "R2H"i | "R2H"i "/" "N220"i | "N220"i | "R2H"i
N330: "N330"i "/" "S2H"i | "S2H"i "/" "N330"i | "N330"i | "S2H"i
N470: "N470"i "/" "T2H"i | "T2H"i "/" "N470"i | "N470"i | "T2H"i
N750: "N750"i "/" "U2J"i | "U2J"i "/" "N750"i | "N750"i | "U2J"i
N1000: "N1000"i "/" "Q3K"i | "Q3K"i "/" "N1000"i | "N1000"i | "Q3K"i
N1500: "N1500"i "/" "P3K"i | "P3K"i "/" "N1500"i | "N1500"i | "P3K"i
C0G: /C[O0]G/i "/" /NP[O0]/i | /NP[O0]/i "/" /C[O0]G/i | /C[O0]G/i | /NP[O0]/i

View File

@ -39,6 +39,7 @@ exclude = experiments/kicad/v6/
experiments/JLC/
experiments/resistor_colors/
experiments/EasyEDA/
experiments/grammar
kibot/mcpyrate/
kibot/PcbDraw/
kibot/PyPDF2/

View File

@ -326,7 +326,7 @@
(effects (font (size 1 1) (thickness 0.15)))
(tstamp 8c217b04-361d-48af-b961-1d540df74a9f)
)
(fp_text value "0.01" (at 12.7 3.37) (layer "F.Fab")
(fp_text value "0.01 5%" (at 12.7 3.37) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15)))
(tstamp d228c21f-e790-45ba-aad5-43b7d6020d9a)
)

View File

@ -684,7 +684,7 @@
(in_bom yes) (on_board yes) (fields_autoplaced)
(uuid bb38c897-d2f8-4276-a663-bb1f35f5db41)
(property "Reference" "R26" (id 0) (at 92.71 101.9642 90))
(property "Value" "0.01" (id 1) (at 92.71 104.5011 90))
(property "Value" "0.01 5%" (id 1) (at 92.71 104.5011 90))
(property "Footprint" "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P25.40mm_Horizontal" (id 2) (at 92.71 108.458 90)
(effects (font (size 1.27 1.27)) hide)
)
@ -1001,7 +1001,7 @@
(reference "R25") (unit 1) (value "33k") (footprint "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P20.32mm_Horizontal")
)
(path "/bb38c897-d2f8-4276-a663-bb1f35f5db41"
(reference "R26") (unit 1) (value "0.01") (footprint "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P25.40mm_Horizontal")
(reference "R26") (unit 1) (value "0.01 5%") (footprint "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P25.40mm_Horizontal")
)
(path "/9cfcad2d-3334-4d64-b2a9-fb405ef03985"
(reference "R27") (unit 1) (value "0.12") (footprint "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P5.08mm_Vertical")

View File

@ -367,7 +367,7 @@
(effects (font (size 1 1) (thickness 0.15)))
(tstamp 8c217b04-361d-48af-b961-1d540df74a9f)
)
(fp_text value "0.01" (at 12.7 3.37) (layer "F.Fab")
(fp_text value "0.01 5%" (at 12.7 3.37) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15)))
(tstamp d228c21f-e790-45ba-aad5-43b7d6020d9a)
)

View File

@ -1082,7 +1082,7 @@
(property "Reference" "R26" (at 92.71 101.9642 90)
(effects (font (size 1.27 1.27)))
)
(property "Value" "0.01" (at 92.71 104.5011 90)
(property "Value" "0.01 5%" (at 92.71 104.5011 90)
(effects (font (size 1.27 1.27)))
)
(property "Footprint" "Resistor_THT:R_Axial_DIN0414_L11.9mm_D4.5mm_P25.40mm_Horizontal" (at 92.71 108.458 90)

View File

@ -486,13 +486,13 @@ def int_bom_sort(test_dir, locale, dp):
ref_column = header.index(REF_COLUMN_NAME)
exp = ['C5', 'C6', 'C7', 'C8', 'C9', 'C10', 'C1', 'C2', 'C3', 'C4', 'C11', 'C12',
'L2', 'L1', 'L3',
'R5', 'R16', 'R12', 'R4', 'R9', 'R10', 'R3']
'R5', 'R16', 'R12', 'R4', 'R13', 'R9', 'R10', 'R3']
if dp == ',':
exp += ['R2', 'R1', 'R8']
else:
# 8,2 k is interpreted as 82 k
exp += ['R1', 'R2', 'R8']
exp += ['R7', 'R11', 'R14', 'R13', 'R15']
exp += ['R7', 'R11', 'R14', 'R15']
check_kibom_test_netlist(rows, ref_column, 23, None, exp)
# Check the sorting
assert get_column(rows, ref_column) == exp

View File

@ -16,7 +16,8 @@ from kibot.dep_downloader import search_as_plugin
from kibot.registrable import RegOutput, RegFilter
from kibot.misc import (WRONG_INSTALL, BOM_ERROR, DRC_ERROR, ERC_ERROR, PDF_PCB_PRINT, KICAD2STEP_ERR)
from kibot.bom.columnlist import ColumnList
from kibot.bom.units import get_prefix
from kibot.bom.units import get_prefix, comp_match
from kibot.bom.electro_grammar import parse
from kibot.__main__ import detect_kicad
from kibot.kicad.config import KiConf
from kibot.globals import Globals
@ -291,7 +292,7 @@ def test_step_fail(test_dir, caplog, monkeypatch):
def test_unknown_prefix(caplog):
with context.cover_it(cov):
get_prefix('y')
get_prefix(1, 'y')
assert 'Unknown prefix, please report' in caplog.text
@ -346,21 +347,166 @@ def test_makefile_kibot_sys(test_dir):
ctx.clean_up()
def test_units_1():
with context.cover_it(cov):
assert str(comp_match("1", 'R')) == "1 Ω"
assert str(comp_match("1000", 'R')) == "1 kΩ"
assert str(comp_match("1000000", 'R')) == "1 MΩ"
assert str(comp_match("1000000000", 'R')) == "1 GΩ"
assert str(comp_match("3.3 pF", 'C')) == "3.3 pF"
assert str(comp_match("0.0033 nF", 'C')) == "3.3 pF"
assert str(comp_match("3p3", 'C')) == "3.3 pF"
a = comp_match("3k3 1% 0805", 'R')
assert str(a) == "3.3 kΩ"
assert a.extra['tolerance'] == 1
assert a.extra['size'] == '0805'
a = comp_match("0.01 1%", 'R')
assert str(a) == "10 mΩ"
assert a.extra['tolerance'] == 1
def test_read_resistance():
assert read_resistance("4k7") == D("4700")
assert read_resistance("4k7") == D("4700")
assert read_resistance("4.7R") == D("4.7")
assert read_resistance("4R7") == D("4.7")
assert read_resistance("0R47") == D("0.47")
assert read_resistance("4700k") == D("4700000")
assert read_resistance("470m") == D("0.47")
assert read_resistance("470M") == D("470000000")
assert read_resistance("4M7") == D("4700000")
assert read_resistance("470") == D("470")
assert read_resistance("470Ω") == D("470")
assert read_resistance("470 Ω") == D("470")
assert read_resistance("470Ohm") == D("470")
assert read_resistance("470 Ohms") == D("470")
assert read_resistance("R47") == D("0.47")
assert read_resistance("1G") == D("1000000000")
assert read_resistance("4k7000") == D("4700")
with context.cover_it(cov):
assert read_resistance("4k7")[0] == D("4700")
assert read_resistance("4k7")[0] == D("4700")
assert read_resistance("4.7R")[0] == D("4.7")
assert read_resistance("4R7")[0] == D("4.7")
assert read_resistance("0R47")[0] == D("0.47")
assert read_resistance("4700k")[0] == D("4700000")
assert read_resistance("470m")[0] == D("0.47")
assert read_resistance("470M")[0] == D("470000000")
assert read_resistance("4M7")[0] == D("4700000")
assert read_resistance("470")[0] == D("470")
assert read_resistance("470Ω")[0] == D("470")
assert read_resistance("470 Ω")[0] == D("470")
assert read_resistance("470Ohm")[0] == D("470")
assert read_resistance("470 Ohms")[0] == D("470")
assert read_resistance("R47")[0] == D("0.47")
assert read_resistance("1G")[0] == D("1000000000")
assert read_resistance("4k7000")[0] == D("4700")
def test_electro_grammar_1():
with context.cover_it(cov):
C2UF_0603_30P = {'type': 'capacitor', 'capacitance': 2e-6, 'size': '0603', 'tolerance': 30}
C2UF_0603 = {'type': 'capacitor', 'capacitance': 2e-6, 'size': '0603'}
C10UF_0402 = {'type': 'capacitor', 'capacitance': 10e-6, 'size': '0402'}
C100NF_0603 = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603'}
C100NF_0603_X7R = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603', 'characteristic': 'X7R'}
C100NF_0603_Z5U = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603', 'characteristic': 'Z5U'}
C100NF_0603_Y5V = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603', 'characteristic': 'Y5V'}
C100NF_0603_C0G = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603', 'characteristic': 'C0G'}
C100NF_0603_25V = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603', 'voltage_rating': 25}
C100NF_0603_6V3 = {'type': 'capacitor', 'capacitance': 100e-9, 'size': '0603', 'voltage_rating': 6.3}
C100UF_0603 = {'type': 'capacitor', 'capacitance': 100e-6, 'size': '0603'}
C100UF_0603_X7R = {'type': 'capacitor', 'capacitance': 100e-6, 'size': '0603', 'characteristic': 'X7R'}
C1N5_0603_X7R = {'type': 'capacitor', 'capacitance': 1.5e-9, 'size': '0603', 'characteristic': 'X7R'}
C1F_0603_25V = {'type': 'capacitor', 'capacitance': 1, 'size': '0603', 'voltage_rating': 25}
C_01005 = {'type': 'capacitor', 'size': '01005'}
C_0201 = {'type': 'capacitor', 'size': '0201'}
C_0402 = {'type': 'capacitor', 'size': '0402'}
C_0603 = {'type': 'capacitor', 'size': '0603'}
C_0805 = {'type': 'capacitor', 'size': '0805'}
C_1206 = {'type': 'capacitor', 'size': '1206'}
C_TESTS = ((('this is total rubbish', ''), {}),
(('2uF 0603',), C2UF_0603),
(('2uF 0603 30%', '2uF 0603 +/-30%', '2uF 0603 ±30%', '2uF 0603 +-30%'), C2UF_0603_30P),
(('10uF 0402',
'10 micro Farad 0402',
'10 \u03BC''F 0402',
'10 \u00B5''F 0402',
'10𝛍F 0402',
'10𝜇F 0402',
'10𝝁 F 0402',
'10 𝝻F 0402',
'10𝞵F 0402'), C10UF_0402),
(('100nF 0603 kajdlkja alkdjlkajd',
'adjalkjd 100nF akjdlkjda 0603 kajdlkja alkdjlkajd',
'capacitor 100nF 0603, warehouse 5',
'adjalkjd 0603 akjdlkjda 100nF kajdlkja alkdjlkajd',
'C 100n 0603',
'Capacitor 100n 0603',
'cap 100n 0603'), C100NF_0603),
(('1n5F 0603 X7R',), C1N5_0603_X7R),
(('100NF 0603 X7R', '100nF 0603 X7R', '100nF 0603 x7r'), C100NF_0603_X7R),
(('100UF 0603 X7R',), C100UF_0603_X7R),
(('100nF 0603 Z5U',), C100NF_0603_Z5U),
(('100nF 0603 Y5V',), C100NF_0603_Y5V),
(('100nF 0603 C0G',
'100nF 0603 NP0',
'100nF 0603 np0',
'100nF 0603 c0g',
'100nF 0603 cog',
'100nF 0603 npO',
'100nF 0603 COG',
'100nF 0603 C0G/NP0'), C100NF_0603_C0G),
(('1F 0603 25V', '1f 0603 25V', '1 Farad 0603 25V'), C1F_0603_25V),
(('100nF 0603 25V', '100nF 0603 25 v'), C100NF_0603_25V),
(('100nF 0603 6v3', '100nF 0603 6V3', '100nF 0603 6.3V', '100nF 0603 6.3v'), C100NF_0603_6V3),
(('0603 0.0001F', '0603 0.0001 F', '0603 0.1mF'), C100UF_0603),
(('capacitor 01005',), C_01005),
(('capacitor 0201',), C_0201),
(('capacitor 0402',), C_0402),
(('capacitor 0603',), C_0603),
(('capacitor 0805',), C_0805),
(('capacitor 1206',), C_1206))
R1K_0603 = {'type': 'resistor', 'size': '0603', 'resistance': 1000}
R1K_0805_5P = {'type': 'resistor', 'size': '0805', 'resistance': 1000, 'tolerance': 5}
R1K_0805_5P_100MW = {'type': 'resistor', 'size': '0805', 'resistance': 1000, 'tolerance': 5, 'power_rating': 0.1}
R1K_0201_500MW = {'type': 'resistor', 'size': '0201', 'resistance': 1000, 'power_rating': 0.5}
R0_0201_125MW = {'type': 'resistor', 'size': '0201', 'resistance': 0, 'power_rating': 0.125}
R1M_0603 = {'type': 'resistor', 'size': '0603', 'resistance': 1e6}
R1M = {'type': 'resistor', 'resistance': 1e6}
R1M1_0603 = {'type': 'resistor', 'size': '0603', 'resistance': 1.1e6}
R100 = {'type': 'resistor', 'resistance': 100}
R10K_0805 = {'type': 'resistor', 'size': '0805', 'resistance': 10000}
R1 = {'type': 'resistor', 'resistance': 1}
R1_0402 = {'type': 'resistor', 'resistance': 1, 'size': '0402'}
R1_0805 = {'type': 'resistor', 'resistance': 1, 'size': '0805'}
R1K5_0402 = {'type': 'resistor', 'resistance': 1500, 'size': '0402'}
R2_7_0402 = {'type': 'resistor', 'resistance': 2.7, 'size': '0402'}
R1MILI = {'type': 'resistor', 'resistance': 0.001}
R100U = {'type': 'resistor', 'resistance': 0.0001}
R_01005 = {'type': 'resistor', 'size': '01005'}
R_0201 = {'type': 'resistor', 'size': '0201'}
R_0402 = {'type': 'resistor', 'size': '0402'}
R_0603 = {'type': 'resistor', 'size': '0603'}
R_0805 = {'type': 'resistor', 'size': '0805'}
R_1206 = {'type': 'resistor', 'size': '1206'}
R_TESTS = ((('R 0.01 1%',), {'type': 'resistor', 'resistance': 0.01, 'tolerance': 1}),
(('1k 0603', '1k ohm 0603', '1K ohms 0603'), R1K_0603),
(('resistor 100', '100R', '100 R'), R100),
(('r 10000 0805',), R10K_0805),
(('res or whatever 1',), R1),
(('1 ohm 0402',), R1_0402),
(('1Ω 0805', '1Ω 0805'), R1_0805),
(('1MEG 0603', '1M 0603'), R1M_0603),
(('1M1 ohms 0603',), R1M1_0603),
(('1k5 0402', '1.5k 0402'), R1K5_0402),
(('2r7 0402', '2R7 0402'), R2_7_0402),
(('1 mOhm',), R1MILI),
(('1 MOhm',), R1M),
(('100 uΩ',), R100U),
(('1k 0805 5%',), R1K_0805_5P),
(('1k 0805 5% 100mW',), R1K_0805_5P_100MW),
(('0 ohm 0201 0.125W', '0 ohm 0201 1/8W'), R0_0201_125MW),
(('resistor 1k 0201 1/2 watts',), R1K_0201_500MW),
(('resistor 01005',), R_01005),
(('resistor 0201',), R_0201),
(('resistor 0402',), R_0402),
(('resistor 0603',), R_0603),
(('resistor 0805',), R_0805),
(('resistor 1206',), R_1206))
LED_TEST = ((('led red 0603',), {'type': 'led', 'size': '0603', 'color': 'red'}),
(('SMD LED GREEN 0805', 'GREEN 0805 LED'), {'type': 'led', 'size': '0805', 'color': 'green'}))
L_TEST = ((('L 100 0805', 'IND 100 0805', 'Inductor 100 0805'), {'type': 'inductor', 'inductance': 100,
'size': '0805'}),
(('3n3 H', '3n3H', '3.3 nH', '3300pH', '3.3 nano Henry',
'This is a 3.3 nH inductor'), {'type': 'inductor', 'inductance': 3.3e-9}))
TESTS = C_TESTS+R_TESTS+L_TEST+LED_TEST
for test in TESTS:
ref = test[1]
for c in test[0]:
res = parse(c)
assert res == ref, "For `{}` got:\n{}\nExpected:\n{}".format(c, res, ref)
logging.debug(c+" Ok")