diff --git a/.github/workflows/check-code-style.yml b/.github/workflows/check-code-style.yml index e4cd494..3d37b9f 100644 --- a/.github/workflows/check-code-style.yml +++ b/.github/workflows/check-code-style.yml @@ -14,6 +14,6 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.x" + python-version: "3.8" cache: "pip" - uses: pre-commit/action@v3.0.0 diff --git a/README.md b/README.md index 72300a2..be19699 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# easyeda2kicad v0.6.3 +# easyeda2kicad v0.6.5 _________________ [![PyPI version](https://badge.fury.io/py/easyeda2kicad.svg)](https://badge.fury.io/py/easyeda2kicad) diff --git a/easyeda2kicad/__init__.py b/easyeda2kicad/__init__.py index 62cc8fb..34fc0ee 100644 --- a/easyeda2kicad/__init__.py +++ b/easyeda2kicad/__init__.py @@ -1,3 +1,3 @@ -__version__ = "0.6.3" +__version__ = "0.6.5" __author__ = "uPesy" __email__ = "contact@upesy.com" diff --git a/easyeda2kicad/__main__.py b/easyeda2kicad/__main__.py index 7cca178..a4bec62 100644 --- a/easyeda2kicad/__main__.py +++ b/easyeda2kicad/__main__.py @@ -158,7 +158,7 @@ def valid_arguments(arguments: dict) -> bool: "easyeda2kicad", ) if not os.path.isdir(default_folder): - os.mkdir(default_folder) + os.makedirs(default_folder, exist_ok=True) base_folder = default_folder lib_name = "easyeda2kicad" diff --git a/easyeda2kicad/easyeda/easyeda_importer.py b/easyeda2kicad/easyeda/easyeda_importer.py index f289299..3af8930 100644 --- a/easyeda2kicad/easyeda/easyeda_importer.py +++ b/easyeda2kicad/easyeda/easyeda_importer.py @@ -149,7 +149,8 @@ def __init__(self, easyeda_cp_cad_data: dict): self.output = self.extract_easyeda_data( ee_data_str=self.input["packageDetail"]["dataStr"], ee_data_info=self.input["packageDetail"]["dataStr"]["head"]["c_para"], - is_smd=self.input.get("SMT"), + is_smd=self.input.get("SMT") + and "-TH_" not in self.input["packageDetail"]["title"], ) def get_footprint(self): @@ -172,7 +173,6 @@ def extract_easyeda_data( ) for line in ee_data_str["shape"]: - ee_designator = line.split("~")[0] ee_fields = line.split("~")[1:] diff --git a/easyeda2kicad/easyeda/parameters_easyeda.py b/easyeda2kicad/easyeda/parameters_easyeda.py index 9acbf0c..54767ec 100644 --- a/easyeda2kicad/easyeda/parameters_easyeda.py +++ b/easyeda2kicad/easyeda/parameters_easyeda.py @@ -3,7 +3,7 @@ from enum import Enum from typing import List, Union -from pydantic import BaseModel, validator +from pydantic import BaseModel, field_validator from easyeda2kicad.easyeda.svg_path_parser import parse_svg_path @@ -33,19 +33,23 @@ class EeSymbolPinSettings(BaseModel): id: str is_locked: bool - @validator("is_displayed", pre=True) + @field_validator("is_displayed", mode="before") + @classmethod def parse_display_field(cls, field: str) -> bool: return True if field == "show" else field - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, is_locked: str) -> str: return is_locked or False - @validator("rotation", pre=True) + @field_validator("rotation", mode="before") + @classmethod def empty_str_rotation(cls, rotation: str) -> str: return rotation or 0.0 - @validator("type", pre=True) + @field_validator("type", mode="before") + @classmethod def convert_pin_type(cls, field: str) -> str: return ( EasyedaPinType(int(field or 0)) @@ -63,7 +67,8 @@ class EeSymbolPinPath(BaseModel): path: str color: str - @validator("path", pre=True) + @field_validator("path", mode="before") + @classmethod def tune_path(cls, field: str) -> str: return field.replace("v", "h") @@ -78,17 +83,20 @@ class EeSymbolPinName(BaseModel): font: str font_size: float - @validator("font_size", pre=True) + @field_validator("font_size", mode="before") + @classmethod def empty_str_font(cls, font_size: str) -> float: if isinstance(font_size, str) and "pt" in font_size: return float(font_size.replace("pt", "")) return font_size or 7.0 - @validator("is_displayed", pre=True) + @field_validator("is_displayed", mode="before") + @classmethod def parse_display_field(cls, field: str) -> str: return True if field == "show" else field - @validator("rotation", pre=True) + @field_validator("rotation", mode="before") + @classmethod def empty_str_rotation(cls, rotation: str) -> str: return rotation or 0.0 @@ -98,7 +106,8 @@ class EeSymbolPinDotBis(BaseModel): circle_x: float circle_y: float - @validator("is_displayed", pre=True) + @field_validator("is_displayed", mode="before") + @classmethod def parse_display_field(cls, field: str) -> str: return True if field == "show" else field @@ -107,7 +116,8 @@ class EeSymbolPinClock(BaseModel): is_displayed: bool path: str - @validator("is_displayed", pre=True) + @field_validator("is_displayed", mode="before") + @classmethod def parse_display_field(cls, field: str) -> str: return True if field == "show" else field @@ -126,8 +136,8 @@ class EeSymbolPin: class EeSymbolRectangle(BaseModel): pos_x: float pos_y: float - rx: Union[float, None] - ry: Union[float, None] + rx: Union[float, None] = None + ry: Union[float, None] = None width: float height: float stroke_color: str @@ -137,7 +147,8 @@ class EeSymbolRectangle(BaseModel): id: str is_locked: bool - @validator("*", pre=True) + @field_validator("*", mode="before") + @classmethod def empty_str_to_none(cls, field: str) -> str: return field or None @@ -154,11 +165,13 @@ class EeSymbolCircle(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False - @validator("fill_color", pre=True) + @field_validator("fill_color", mode="before") + @classmethod def parse_background_filling(cls, fill_color: str) -> str: return bool(fill_color and fill_color.lower() != "none") @@ -174,15 +187,18 @@ class EeSymbolArc(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False - @validator("fill_color", pre=True) + @field_validator("fill_color", mode="before") + @classmethod def parse_background_filling(cls, fill_color: str) -> str: return bool(fill_color and fill_color.lower() != "none") - @validator("path", pre=True) + @field_validator("path", mode="before") + @classmethod def convert_svg_path(cls, path: str) -> list: return parse_svg_path(svg_path=path) @@ -199,11 +215,13 @@ class EeSymbolEllipse(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False - @validator("fill_color", pre=True) + @field_validator("fill_color", mode="before") + @classmethod def parse_background_filling(cls, fill_color: str) -> str: return bool(fill_color and fill_color.lower() != "none") @@ -218,11 +236,13 @@ class EeSymbolPolyline(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False - @validator("fill_color", pre=True) + @field_validator("fill_color", mode="before") + @classmethod def parse_background_filling(cls, fill_color: str) -> str: return bool(fill_color and fill_color.lower() != "none") @@ -245,11 +265,13 @@ class EeSymbolPath(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False - @validator("fill_color", pre=True) + @field_validator("fill_color", mode="before") + @classmethod def parse_background_filling(cls, fill_color: str) -> str: return bool(fill_color and fill_color.lower() != "none") @@ -328,11 +350,13 @@ def convert_to_mm(self) -> None: self.hole_radius = convert_to_mm(self.hole_radius) self.hole_length = convert_to_mm(self.hole_length) - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False - @validator("rotation", pre=True) + @field_validator("rotation", mode="before") + @classmethod def empty_str_rotation(cls, field: str) -> str: return field or 0.0 @@ -345,7 +369,8 @@ class EeFootprintTrack(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False @@ -360,7 +385,8 @@ class EeFootprintHole(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False @@ -379,7 +405,8 @@ class EeFootprintCircle(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field: str) -> str: return field or False @@ -400,9 +427,10 @@ class EeFootprintRectangle(BaseModel): layer_id: int is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field): - return False if field == "" else field + return False if field == "" else bool(float(field)) def convert_to_mm(self): self.x = convert_to_mm(self.x) @@ -420,7 +448,8 @@ class EeFootprintArc(BaseModel): id: str is_locked: bool - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field): return False if field == "" else field @@ -441,15 +470,18 @@ class EeFootprintText(BaseModel): id: str is_locked: bool - @validator("is_displayed", pre=True) + @field_validator("is_displayed", mode="before") + @classmethod def empty_str_display(cls, field): return True if field == "" else field - @validator("is_locked", pre=True) + @field_validator("is_locked", mode="before") + @classmethod def empty_str_lock(cls, field): return False if field == "" else field - @validator("rotation", pre=True) + @field_validator("rotation", mode="before") + @classmethod def empty_str_rotation(cls, field): return 0.0 if field == "" else field diff --git a/easyeda2kicad/kicad/export_kicad_footprint.py b/easyeda2kicad/kicad/export_kicad_footprint.py index f8b9a0d..7758ff0 100644 --- a/easyeda2kicad/kicad/export_kicad_footprint.py +++ b/easyeda2kicad/kicad/export_kicad_footprint.py @@ -32,7 +32,6 @@ def compute_arc( end_x: float, end_y: float, ) -> Tuple[float, float, float]: - # Compute the half distance between the current and the final point dx2 = (start_x - end_x) / 2.0 dy2 = (start_y - end_y) / 2.0 @@ -130,7 +129,6 @@ def drill_to_ki( and hole_length is not None and hole_length != 0 ): - max_distance_hole = max(hole_radius * 2, hole_length) pos_0 = pad_height - max_distance_hole pos_90 = pad_width - max_distance_hole @@ -176,7 +174,6 @@ def __init__(self, footprint: ee_footprint): self.generate_kicad_footprint() def generate_kicad_footprint(self) -> None: - # Convert dimension from easyeda to kicad self.input.bbox.convert_to_mm() @@ -207,7 +204,9 @@ def generate_kicad_footprint(self) -> None: y=-round( (self.input.model_3d.translation.y - self.input.bbox.y), 2 ), - z=-round(self.input.model_3d.translation.z, 2), + z=-round(self.input.model_3d.translation.z, 2) + if self.input.info.fp_type == "smd" + else 0, ), rotation=Ki3dModelBase( x=(360 - self.input.model_3d.rotation.x) % 360, @@ -233,14 +232,15 @@ def generate_kicad_footprint(self) -> None: pos_y=ee_pad.center_y - self.input.bbox.y, width=max(ee_pad.width, 0.01), height=max(ee_pad.height, 0.01), - layers=KI_PAD_LAYER[ee_pad.layer_id] - if ee_pad.layer_id in KI_PAD_LAYER - else "", + layers=( + KI_PAD_LAYER if ee_pad.hole_radius <= 0 else KI_PAD_LAYER_THT + ).get(ee_pad.layer_id, ""), number=ee_pad.number, drill=0.0, orientation=angle_to_ki(ee_pad.rotation), polygon="", ) + ki_pad.drill = drill_to_ki( ee_pad.hole_radius, ee_pad.hole_length, ki_pad.height, ki_pad.width ) @@ -253,9 +253,7 @@ def generate_kicad_footprint(self) -> None: if is_custom_shape: if len(point_list) <= 0: logging.warning( - "PAD ${id} is a polygon, but has no points defined".format( - id=ee_pad.id - ) + f"PAD ${ee_pad.id} is a polygon, but has no points defined" ) else: # Replace pad width & height since kicad doesn't care @@ -447,7 +445,6 @@ def get_ki_footprint(self) -> KiFootprint: return self.output def export(self, footprint_full_path: str, model_3d_path: str) -> None: - ki = self.output ki_lib = "" @@ -455,8 +452,10 @@ def export(self, footprint_full_path: str, model_3d_path: str) -> None: package_lib="easyeda2kicad", package_name=ki.info.name, edit="5DC5F6A4" ) - if ki.info.fp_type and ki.info.fp_type == "smd": - ki_lib += KI_FP_TYPE.format(component_type=ki.info.fp_type) + if ki.info.fp_type: + ki_lib += KI_FP_TYPE.format( + component_type=("smd" if ki.info.fp_type == "smd" else "through_hole") + ) # Get y_min and y_max to put component info y_low = min(pad.pos_y for pad in ki.pads) diff --git a/easyeda2kicad/kicad/parameters_kicad_footprint.py b/easyeda2kicad/kicad/parameters_kicad_footprint.py index d754825..788da32 100644 --- a/easyeda2kicad/kicad/parameters_kicad_footprint.py +++ b/easyeda2kicad/kicad/parameters_kicad_footprint.py @@ -77,6 +77,15 @@ 15: "Dwgs.User", } +KI_PAD_LAYER_THT = { + 1: "F.Cu F.Mask", + 2: "B.Cu B.Mask", + 3: "F.SilkS", + 11: "*.Cu *.Mask", + 13: "F.Fab", + 15: "Dwgs.User", +} + KI_LAYERS = { 1: "F.Cu", 2: "B.Cu", diff --git a/requirements.txt b/requirements.txt index b890cd7..20893eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pre-commit>=2.17.0 -pydantic>=1.5 +pydantic>=2.0.0 requests>2.0.0 diff --git a/setup.cfg b/setup.cfg index 759f3bf..b7a1507 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.6.3 +current_version = 0.6.5 commit = True tag = True diff --git a/setup.py b/setup.py index af1543f..ef48fbf 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ with open("README.md") as fh: long_description = fh.read() -production_dependencies = ["pydantic>=1.5", "requests>2.0.0"] +production_dependencies = ["pydantic>=2.0.0", "requests>2.0.0"] development_dependencies = [ "pre-commit>=2.17.0", @@ -20,7 +20,7 @@ ), long_description=long_description, long_description_content_type="text/markdown", - version="0.6.3", + version="0.6.5", author="uPesy", author_email="contact@upesy.com", url="https://github.com/uPesy/easyeda2kicad.py",