Coverage for JLC2KiCadLib/footprint/footprint.py: 89%

1 import json

2 import logging

3 import os

4 from dataclasses import dataclass

5

6 import requests

7 from KicadModTree import Footprint, KicadFileHandler, Pad, Text, Translation

8

9 from .footprint_handlers import handlers, mil2mm

10

11

12@dataclass

13 class FootprintInfo:

14 max_X: float = -10000

15 max_Y: float = -10000

16 min_X: float = 10000

17 min_Y: float = 10000

18 footprint_name: str = ""

19 output_dir: str = ""

20 footprint_lib: str = ""

21 model_base_variable: str = ""

22 model_dir: str = ""

23 origin: tuple = (0, 0)

24 models: str = ""

25

26

27 def create_footprint(

28 footprint_component_uuid,

29 component_id,

30 footprint_lib,

31 output_dir,

32 model_base_variable,

33 model_dir,

34 skip_existing,

35 models,

36):

37 logging.info("Creating footprint ...")

38

39 (

40 footprint_name,

41 datasheet_link,

42 footprint_shape,

43 translation,

44 ) = get_footprint_info(footprint_component_uuid)

45

46 if skip_existing and os.path.isfile(

47 os.path.join(output_dir, footprint_lib, footprint_name + ".kicad_mod")

48 ):

49 logging.info(f"Footprint {footprint_name} already exists, skipping.")

50 return f"{footprint_lib}:{footprint_name}", datasheet_link

51

52 # init kicad footprint

53 kicad_mod = Footprint(f'"{footprint_name}"')

54 kicad_mod.setDescription(f"{footprint_name} footprint") # TODO Set real description

55 kicad_mod.setTags(f"{footprint_name} footprint {component_id}")

56

57 footprint_info = FootprintInfo(

58 footprint_name=footprint_name,

59 output_dir=output_dir,

60 footprint_lib=footprint_lib,

61 model_base_variable=model_base_variable,

62 model_dir=model_dir,

63 origin=translation,

64 models=models,

65 )

66

67 # for each line in data : use the appropriate handler

68 for line in footprint_shape:

69 args = [i for i in line.split("~")] # split and remove empty string in list

70 model = args[0]

71 logging.debug(args)

72 if model not in handlers:

73 logging.warning(f"footprint : model not in handler : {model}")

74 else:

75 handlers.get(model)(args[1:], kicad_mod, footprint_info)

76

77 if any(

78 isinstance(child, Pad) and child.type == Pad.TYPE_THT

79 for child in kicad_mod.getAllChilds()

80 ):

81 kicad_mod.setAttribute("through_hole")

82 else:

83 kicad_mod.setAttribute("smd")

84

85 kicad_mod.insert(Translation(-mil2mm(translation[0]), -mil2mm(translation[1])))

86

87 # Translate the footprint max and min values to the origin

88 footprint_info.max_X -= mil2mm(translation[0])

89 footprint_info.max_Y -= mil2mm(translation[1])

90 footprint_info.min_X -= mil2mm(translation[0])

91 footprint_info.min_Y -= mil2mm(translation[1])

92

93 # set general values

94 kicad_mod.append(

95 Text(

96 type="reference",

97 text="REF**",

98 at=[

99 (footprint_info.min_X + footprint_info.max_X) / 2,

100 footprint_info.min_Y - 2,

101 ],

102 layer="F.SilkS",

103 )

104 )

105 kicad_mod.append(

106 Text(

107 type="user",

108 text="${REFERENCE}",

109 at=[

110 (footprint_info.min_X + footprint_info.max_X) / 2,

111 (footprint_info.min_Y + footprint_info.max_Y) / 2,

112 ],

113 layer="F.Fab",

114 )

115 )

116 kicad_mod.append(

117 Text(

118 type="value",

119 text=footprint_name,

120 at=[

121 (footprint_info.min_X + footprint_info.max_X) / 2,

122 footprint_info.max_Y + 2,

123 ],

124 layer="F.Fab",

125 )

126 )

127

128 if not os.path.exists(f"{output_dir}/{footprint_lib}"):

129 os.makedirs(f"{output_dir}/{footprint_lib}")

130

131 # output kicad model

132 file_handler = KicadFileHandler(kicad_mod)

133 file_handler.writeFile(f"{output_dir}/{footprint_lib}/{footprint_name}.kicad_mod")

134 logging.info(f"Created '{output_dir}/{footprint_lib}/{footprint_name}.kicad_mod'")

135

136 # return the datasheet link and footprint name to be linked with the symbol

137 return (f"{footprint_lib}:{footprint_name}", datasheet_link)

138

139

140 def get_footprint_info(footprint_component_uuid):

141 # fetch the component data from easyeda library

142 response = requests.get(

143 f"https://easyeda.com/api/components/{footprint_component_uuid}"

144 )

145

146 if response.status_code == requests.codes.ok:

147 data = json.loads(response.content.decode())

148 else:

149 logging.error(

150 "create_footprint error. Requests returned with error code "

151 f"{response.status_code}"

152 )

153 return ("", None, "", (0, 0))

154

155 footprint_shape = data["result"]["dataStr"]["shape"]

156 x = data["result"]["dataStr"]["head"]["x"]

157 y = data["result"]["dataStr"]["head"]["y"]

158 try:

159 datasheet_link = data["result"]["dataStr"]["head"]["c_para"]["link"]

160 except KeyError:

161 datasheet_link = ""

162 logging.warning("Could not retrieve datasheet link from EASYEDA")

163

164 footprint_name = (

165 data["result"]["title"]

166 .replace(" ", "_")

167 .replace("/", "_")

168 .replace("(", "_")

169 .replace(")", "_")

170 )

171

172 if not footprint_name:

173 footprint_name = "NoName"

174 logging.warning(

175 "Could not retrieve components information from EASYEDA, default name "

176 "'NoName'."

177 )

178

179 return (footprint_name, datasheet_link, footprint_shape, (x, y))