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

73 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-04 10:00 +0100

1import json 

2import logging 

3import os 

4from dataclasses import dataclass 

5 

6import requests 

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

8 

9from .footprint_handlers import handlers, mil2mm 

10 

11 

12@dataclass 

13class 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 

27def 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 

140def 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))