Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
# One manf# number (and one cat# for each distributor):
# All parts have the same manf#. Don't split this group.
# Two manf# numbers (or cat# distributor code), but one is `None`:
# Some of the parts have no manf# or distributor# but are otherwise
# identical to the other parts in the group. Don't split this group.
# Instead, propagate the non-None manf# to all the parts.
# Two manf# (or two cat# distributor code), neither is `None`:
# All parts have non-`None` manf# and distributor# numbers. Split
# the group into two smaller groups of parts all having the same
# manf# and distributor#.
# Three or more manf# (or distributor#):
# Split this group into smaller groups, each one with parts having
# the same manf# and distributor#, even if it's `None`. It's
# impossible to determine which manf# the `None` parts should be
# assigned to, so leave their manf# as `None`.
logger.log(DEBUG_OVERVIEW, 'Checking the seemingly identical parts group...')
new_component_groups = [] # Copy new component groups into this.
for g, grp in list(component_groups.items()):
num_manfcat_codes = {f:len(grp.manfcat_codes[f]) for f in FIELDS_MANFCAT}
if all([num_manfcat_codes[f]==1 or (num_manfcat_codes[f]==2 and None in grp.manfcat_codes[f]) for f in FIELDS_MANFCAT]):
new_component_groups.append(grp)
continue # CASE ONE and TWO:
# Single manf# and distributor catalogue. Or a seemingly
# identical group with just one valid manf# or cat# code,
# the other one is `None`.Don't split this group. `None`
# will be replaced with the propagated manufacture /
# distributor catalogue code.
elif all([(num_manfcat_codes[f]==1 and grp.manfcat_codes[f]==None) for f in FIELDS_MANFCAT]):
new_component_groups.append(grp)
continue # CASE THREE:
# One manf# or cat# that is `None`. Don't split this
# group. These parts are not intended to be purchased.
# Do not create empty fields. This is useful
# when used more than one `manf#` alias in one designator.
if v and v!=ALTIUM_NONE:
fields[i][field_name_translations.get(hdr.lower(),hdr.lower())] = v.strip()
return refs, fields
# Read-in the schematic XML file to get a tree and get its root.
logger.log(DEBUG_OVERVIEW, '# Getting from XML \'{}\' Altium BoM...'.format(
os.path.basename(in_file)) )
file_h = open(in_file)
root = BeautifulSoup(file_h, 'lxml')
file_h.close()
# Get the header of the XML file of Altium, so KiCost is able to to
# to get all the informations in the file.
logger.log(DEBUG_OVERVIEW, 'Getting the XML table header...')
header = [ extract_field(entry, 'name') for entry in root.find('columns').find_all('column') ]
logger.log(DEBUG_OVERVIEW, 'Getting components...')
accepted_components = {}
for row in root.find('rows').find_all('row'):
# Get the values for the fields in each library part (if any).
refs, fields = extract_fields_row(row, variant, header)
for i in range(len(refs)):
ref = refs[i]
ref = re.sub('\+$', 'p', ref) # Finishing "+".
ref = re.sub(PART_REF_REGEX_NOT_ALLOWED, '', ref) # Generic special characters not allowed. To work around #ISSUE #89.
ref = re.sub('\-+', '-', ref) # Double "-".
ref = re.sub('^\-', '', ref) # Starting "-".
ref = re.sub('\-$', 'n', ref) # Finishing "-".
if not re.search('\d$', ref):
logger.log(DEBUG_OVERVIEW, 'Getting authorship data...')
title = root.find('title_block')
def title_find_all(data, field):
'''Helper function for finding title info, especially if it is absent.'''
try:
return data.find_all(field)[0].string
except (AttributeError, IndexError):
return None
prj_info = dict()
prj_info['title'] = title_find_all(title, 'title') or os.path.basename( in_file )
prj_info['company'] = title_find_all(title, 'company')
prj_info['date'] = title_find_all(root, 'date') or (datetime.strptime(time.ctime(os.path.getmtime(in_file)), '%a %b %d %H:%M:%S %Y').strftime("%Y-%m-%d %H:%M:%S") + ' (file)')
# Make a dictionary from the fields in the parts library so these field
# values can be instantiated into the individual components in the schematic.
logger.log(DEBUG_OVERVIEW, 'Getting parts library...')
libparts = {}
if root.find('libparts'):
for p in root.find('libparts').find_all('libpart'):
# Get the values for the fields in each library part (if any).
fields = extract_fields(p, variant)
# Store the field dict under the key made from the
# concatenation of the library and part names.
libparts[str(p['lib']) + SEPRTR + str(p['part'])] = fields
# Also have to store the fields under any part aliases.
try:
for alias in p.find('aliases').find_all('alias'):
libparts[str(p['lib']) + SEPRTR + str(alias.string)] = fields
except AttributeError:
# recognized by the installed modules and, quantity and sub quantity of the part.
FIELDS_NOT_HASH = (['manf#_qty', 'manf'] + FIELDS_MANFCAT + [d + '#_qty' for d in distributor_dict])
# Check if was asked to merge some not allowed fields (as `manf`, `manf# ...
# other ones as `desc` and even `value` and `footprint` may be merged due
# the different typed (1uF and 1u) or footprint library names to the same one.
fields_merge = list( [field_name_translations.get(f.lower(),f.lower()) for f in fields_merge] )
for c in FIELDS_NOT_HASH:
if c in fields_merge:
raise ValueError('Manufacturer/distributor codes and manufacture company "{}" can\'t be ignored to create the components groups.'.format(c))
FIELDS_NOT_HASH = FIELDS_NOT_HASH + fields_merge # Not use the fields do merge to create the hash.
# Now partition the parts into groups of like components.
# First, get groups of identical components but ignore any manufacturer's
# part numbers that may be assigned. Just collect those in a list for each group.
logger.log(DEBUG_OVERVIEW, 'Getting groups of identical components...')
component_groups = {}
for ref, fields in list(components.items()): # part references and field values.
# Take the field keys and values of each part and create a hash.
# Use the hash as the key to a dictionary that stores lists of
# part references that have identical field values. The important fields
# are the reference prefix ('R', 'C', etc.), value, and footprint.
# Don't use the manufacturer's part number when calculating the hash!
# Also, don't use any fields with SEPRTR in the label because that indicates
# a field used by a specific tool (including KiCost).
hash_fields = {k: fields[k] for k in fields if k not in FIELDS_NOT_HASH and SEPRTR not in k}
h = hash(tuple(sorted(hash_fields.items())))
# Now add the hashed component to the group with the matching hash
# or create a new group if the hash hasn't been seen before.
try:
continue # If not manf/distributor code pass to next.
# Divide the `manf` manufacture name.
try:
subparts_manf = subpart_list(part['manf'])
if len(subparts_manf)!=subparts_qty:
if len(subparts_manf)==1:
# If just one `manf`assumes that is for all.
subparts_manf = [subparts_manf[0]]*subparts_qty
else:
# Exception `manf` and `manf#` length doesn't match, fill with '' at the end.
subparts_manf.extend(['']*(subparts_qty-len(subparts_manf)))
except KeyError:
subparts_manf = ['']*subparts_qty
pass
logger.log(DEBUG_DETAILED, '{} >> {}'.format(part_ref, founded_fields) )
# Second, if more than one subpart, split the sub parts as
# new components with the same description, footprint, and
# so on... Get the subpart.
if subparts_qty>1:
# Remove the actual part from the list.
part_actual = part
part_actual_value = part_actual['value']
subpart_part = ''
subpart_qty = ''
# Add the split subparts.
for subparts_index in range(subparts_qty):
# Create a sub component based on the main component with
# the subparts. Modify the designator and the part. Create
# a sub quantity field.
subpart_actual = part_actual.copy()
# Empty value also propagated to force deleting default value
fields[name] = value
logger.log(DEBUG_OBSESSIVE, 'Field {}={}'.format(name,value))
except AttributeError:
pass # No fields found for this part.
return fields
# Read-in the schematic XML file to get a tree and get its root.
logger.log(DEBUG_OVERVIEW, '# Getting from XML \'{}\' KiCad BoM...'.format(
os.path.basename(in_file)) )
file_h = open(in_file)
root = BeautifulSoup(file_h, 'lxml')
file_h.close()
# Get the general information of the project BoM XML file.
logger.log(DEBUG_OVERVIEW, 'Getting authorship data...')
title = root.find('title_block')
def title_find_all(data, field):
'''Helper function for finding title info, especially if it is absent.'''
try:
return data.find_all(field)[0].string
except (AttributeError, IndexError):
return None
prj_info = dict()
prj_info['title'] = title_find_all(title, 'title') or os.path.basename( in_file )
prj_info['company'] = title_find_all(title, 'company')
prj_info['date'] = title_find_all(root, 'date') or (datetime.strptime(time.ctime(os.path.getmtime(in_file)), '%a %b %d %H:%M:%S %Y').strftime("%Y-%m-%d %H:%M:%S") + ' (file)')
# Make a dictionary from the fields in the parts library so these field
# values can be instantiated into the individual components in the schematic.
logger.log(DEBUG_OVERVIEW, 'Getting parts library...')
libparts = {}
logger.log(DEBUG_OBSESSIVE, 'Identifier: {} in {}.'.format(ref_identifier, component_groups_ref_match) )
if len(component_groups_ref_match)>0:
# If found more than one group with the reference, use the 'manf#'
# as second order criteria.
if len(component_groups_ref_match)>1:
try:
for item in component_groups_ref_match:
component_groups_order_old.remove(item)
except ValueError:
pass
# Examine 'manf#' and refs to get the order.
# Order by refs that have 'manf#' codes, that ones that don't have stay at the end of the group.
group_manf_list = [new_component_groups[h].fields.get('manf#') for h in component_groups_ref_match]
group_refs_list = [new_component_groups[h].refs for h in component_groups_ref_match]
sorted_groups = sorted(range(len(group_refs_list)), key=lambda k:(group_manf_list[k] is None, group_refs_list[k]))
logger.log(DEBUG_OBSESSIVE, '{} > order: {}'.format( group_manf_list, sorted_groups) )
component_groups_ref_match = [component_groups_ref_match[i] for i in sorted_groups]
component_groups_order_new += component_groups_ref_match
else:
try:
component_groups_order_old.remove(component_groups_ref_match[0])
except ValueError:
pass
component_groups_order_new += component_groups_ref_match
# The new order is the found refs first and at the last the not referenced in BOM_ORDER.
component_groups_order_new += component_groups_order_old # Add the missing references groups.
new_component_groups = [new_component_groups[i] for i in component_groups_order_new]
return new_component_groups
# number or a distributors catalog number, then add
# it to 'local' if it doesn't start with a distributor
# name and colon.
#if name not in ('manf#', 'manf', 'desc', 'value', 'comment', 'S1PN', 'S1MN', 'S1PL', 'S2PN', 'S2MN', 'S2PL') and name[:-1] not in distributor_dict:
dist_mtch = re.match('([^:]+):',name)
if dist_mtch and dist_mtch.group(1) not in distributor_dict:
# 'name' is a distibutore (preceded & followed with ':'
logger.log(DEBUG_OBSESSIVE, 'Assigning local: for name "{}" dist "{}" ... '.format(name,dist_mtch.group(1)) )
# Original code supposes that name is a distributor
if SEPRTR not in name: # This field has no distributor.
name = 'local:' + name # Assign it to a local distributor.
value = str(f.string)
if value or v is not None:
# Empty value also propagated to force deleting default value
fields[name] = value
logger.log(DEBUG_OBSESSIVE, 'Field {}={}'.format(name,value))
except AttributeError:
pass # No fields found for this part.
return fields