my-kicad-user-folder/3rdparty/plugins/org_openscopeproject_InteractiveHtmlBom/ecad/genericjson.py
2025-03-21 13:28:36 +08:00

168 lines
5.7 KiB
Python

import io
import json
import os.path
from jsonschema import validate, ValidationError
from .common import EcadParser, Component, BoundingBox, ExtraFieldData
from ..core.fontparser import FontParser
from ..errors import ParsingException
class GenericJsonParser(EcadParser):
COMPATIBLE_SPEC_VERSIONS = [1]
def extra_data_file_filter(self):
return "Json file ({f})|{f}".format(f=os.path.basename(self.file_name))
def latest_extra_data(self, extra_dirs=None):
return self.file_name
def get_extra_field_data(self, file_name):
if os.path.abspath(file_name) != os.path.abspath(self.file_name):
return None
_, components = self._parse()
field_set = set()
comp_dict = {}
for c in components:
ref_fields = comp_dict.setdefault(c.ref, {})
for k, v in c.extra_fields.items():
field_set.add(k)
ref_fields[k] = v
by_index = {
i: components[i].extra_fields for i in range(len(components))
}
return ExtraFieldData(list(field_set), comp_dict, by_index)
def get_generic_json_pcb(self):
with io.open(self.file_name, 'r', encoding='utf-8') as f:
pcb = json.load(f)
if 'spec_version' not in pcb:
raise ValidationError("'spec_version' is a required property")
if pcb['spec_version'] not in self.COMPATIBLE_SPEC_VERSIONS:
raise ValidationError("Unsupported spec_version ({})"
.format(pcb['spec_version']))
schema_dir = os.path.join(os.path.dirname(__file__), 'schema')
schema_file_name = os.path.join(schema_dir,
'genericjsonpcbdata_v{}.schema'.format(
pcb['spec_version']))
with io.open(schema_file_name, 'r', encoding='utf-8') as f:
schema = json.load(f)
validate(instance=pcb, schema=schema)
return pcb
def _verify(self, pcb):
"""Spot check the pcb object."""
if len(pcb['pcbdata']['footprints']) != len(pcb['components']):
self.logger.error("Length of components list doesn't match"
" length of footprints list.")
return False
return True
@staticmethod
def _texts(pcbdata):
for layer in pcbdata['drawings'].values():
for side in layer.values():
for dwg in side:
if 'text' in dwg:
yield dwg
@staticmethod
def _remove_control_codes(s):
import unicodedata
return ''.join(c for c in s if unicodedata.category(c)[0] != "C")
def _parse_font_data(self, pcbdata):
font_parser = FontParser()
for dwg in self._texts(pcbdata):
if 'svgpath' not in dwg:
dwg['text'] = self._remove_control_codes(dwg['text'])
font_parser.parse_font_for_string(dwg['text'])
if font_parser.get_parsed_font():
pcbdata['font_data'] = font_parser.get_parsed_font()
def _check_font_data(self, pcbdata):
mc = set()
for dwg in self._texts(pcbdata):
dwg['text'] = self._remove_control_codes(dwg['text'])
mc.update({c for c in dwg['text'] if 'svgpath' not in dwg and
c not in pcbdata['font_data']})
if mc:
s = ''.join(mc)
self.logger.error('Provided font_data is missing character(s)'
f' "{s}" that are present in text drawing'
' objects')
return False
else:
return True
def _parse(self):
try:
pcb = self.get_generic_json_pcb()
except ValidationError as e:
self.logger.error('File {f} does not comply with json schema. {m}'
.format(f=self.file_name, m=e.message))
return None, None
if not self._verify(pcb):
self.logger.error('File {} does not appear to be valid generic'
' InteractiveHtmlBom json file.'
.format(self.file_name))
return None, None
pcbdata = pcb['pcbdata']
components = [Component(**c) for c in pcb['components']]
if 'font_data' in pcbdata:
if not self._check_font_data(pcbdata):
raise ParsingException(f'Failed parsing {self.file_name}')
else:
self._parse_font_data(pcbdata)
if 'font_data' in pcbdata:
self.logger.info('No font_data provided in JSON, using '
'newstroke font')
self.logger.info('Successfully parsed {}'.format(self.file_name))
return pcbdata, components
def parse(self):
pcbdata, components = self._parse()
# override board bounding box based on edges
board_outline_bbox = BoundingBox()
for drawing in pcbdata['edges']:
self.add_drawing_bounding_box(drawing, board_outline_bbox)
if board_outline_bbox.initialized():
pcbdata['edges_bbox'] = board_outline_bbox.to_dict()
extra_fields = set(self.config.show_fields)
extra_fields.discard("Footprint")
extra_fields.discard("Value")
if self.config.dnp_field:
extra_fields.add(self.config.dnp_field)
if self.config.board_variant_field:
extra_fields.add(self.config.board_variant_field)
if extra_fields:
for c in components:
c.extra_fields = {
f: c.extra_fields.get(f, "") for f in extra_fields}
self.config.kicad_text_formatting = False
return pcbdata, components