File size: 18,498 Bytes
985c397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# SPDX-License-Identifier: LGPL-2.1-or-later

#***************************************************************************
#*   Copyright (c) 2012 Milos Koutny <milos.koutny@gmail.com>              *
#*                                                                         *
#*   This file is part of the FreeCAD CAx development system.              *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Library General Public License (LGPL)   *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   FreeCAD is distributed in the hope that it will be useful,            *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with FreeCAD; if not, write to the Free Software        *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************/

import FreeCAD, Part, os, FreeCADGui
from FreeCAD import Base
from math import *
import ImportGui
from builtins import open as pyopen

##########################################################
# Script version dated 19-Jan-2012                       #
##########################################################
#Configuration parameters below - use standard slashes / #
##########################################################

## path to table file (simple comma separated values)

model_tab_filename = FreeCAD.getHomePath()+ "Mod/Idf/Idflibs/footprints_models.csv"

## path to directory containing step models

step_path=FreeCAD.getHomePath()+ "Mod/Idf/Idflibs/"

ignore_hole_size=0.5 # size in MM to prevent huge number of drilled holes
EmpDisplayMode=2 # 0='Flat Lines', 1='Shaded', 2='Wireframe', 3='Points'; recommended 2 or 0

IDF_sort=0 # 0-sort per refdes [1 - part number (not preferred)/refdes] 2-sort per footprint/refdes

IDF_diag=0 # 0/1=disabled/enabled output (footprint.lst/missing_models.lst)
IDF_diag_path="/tmp" # path for output of footprint.lst and missing_models.lst


########################################################################################
#              End config section do not touch code below                              #
########################################################################################

def open(filename):
    """called when freecad opens an Emn file"""
    docname = os.path.splitext(os.path.basename(filename))[0]
    doc = FreeCAD.newDocument(docname)
    message='Started with opening of "'+filename+'" file\n'
    FreeCAD.Console.PrintMessage(message)
    process_emn(doc,filename)

def insert(filename,docname):
    """called when freecad imports an Emn file"""
    FreeCAD.setActiveDocument(docname)
    doc=FreeCAD.getDocument(docname)
    FreeCAD.Console.PrintMessage('Started import of "'+filename+'" file')
    process_emn(doc,filename)

def process_emn(doc,filename):
   """process_emn(document, filename)-> adds emn geometry from emn file"""
   emnfile=pyopen(filename, "r")
   emn_unit=1.0 #presume millimeter like emn unit
   emn_version=2 #presume emn_version 2
   board_thickness=0 #presume 0 board height
   board_outline=[] #no outline
   drills=[] #no drills
   placement=[] #no placement
   place_item=[] #empty place item
   emnlines=emnfile.readlines()
   emnfile.close()
   passed_sections=[]
   current_section=""
   section_counter=0
   for emnline in emnlines:
       emnrecords=split_records(emnline)
       if len( emnrecords )==0 : continue
       if len( emnrecords[0] )>4 and emnrecords[0][0:4]==".END":
          passed_sections.append(current_section)
          current_section=""
       elif emnrecords[0][0]==".":
          current_section=emnrecords[0]
          section_counter=0
       section_counter+=1
       if current_section==".HEADER"  and section_counter==2:
          emn_version=int(float(emnrecords[1]))
          FreeCAD.Console.PrintMessage("Emn version: "+emnrecords[1]+"\n")
       if current_section==".HEADER"  and section_counter==3 and emnrecords[1]=="THOU":
          emn_unit=0.0254
          FreeCAD.Console.PrintMessage("UNIT THOU\n" )
       if current_section==".HEADER"  and section_counter==3 and emnrecords[1]=="TNM":
          emn_unit=0.000010
          FreeCAD.Console.PrintMessage("TNM\n" )
       if current_section==".BOARD_OUTLINE"  and section_counter==2:
          board_thickness=emn_unit*float(emnrecords[0])
          FreeCAD.Console.PrintMessage("Found board thickness "+emnrecords[0]+"\n")
       if current_section==".BOARD_OUTLINE"  and section_counter>2:
          board_outline.append([int(emnrecords[0]),float(emnrecords[1])*emn_unit,float(emnrecords[2])*emn_unit,float(emnrecords[3])])
       if current_section==".DRILLED_HOLES"  and section_counter>1 and float(emnrecords[0])*emn_unit>ignore_hole_size:
          drills.append([float(emnrecords[0])*emn_unit,float(emnrecords[1])*emn_unit,float(emnrecords[2])*emn_unit])
       if current_section==".PLACEMENT"  and section_counter>1 and fmod(section_counter,2)==0:
          place_item=[]
          place_item.append(emnrecords[2]) #Reference designator
          place_item.append(emnrecords[1]) #Component part number
          place_item.append(emnrecords[0]) #Package name
       if current_section==".PLACEMENT"  and section_counter>1 and fmod(section_counter,2)==1:
          place_item.append(float(emnrecords[0])*emn_unit) #X
          place_item.append(float(emnrecords[1])*emn_unit) #Y
          place_item.append(float(emnrecords[emn_version])) #Rotation
          place_item.append(emnrecords[emn_version+1]) #Side
          place_item.append(emnrecords[emn_version+2]) #Place Status
          FreeCAD.Console.PrintMessage(str(place_item)+"\n")
          placement.append(place_item)
   FreeCAD.Console.PrintMessage("\n".join(passed_sections)+"\n")
   FreeCAD.Console.PrintMessage("Proceed "+str(Process_board_outline(doc,board_outline,drills,board_thickness))+" outlines\n")
   placement.sort(key=lambda param: (param[IDF_sort],param[0]))
   process_emp(doc,filename,placement,board_thickness)
   place_steps(doc,placement,board_thickness)

def Process_board_outline(doc,board_outline,drills,board_thickness):
    """Process_board_outline(doc,board_outline,drills,board_thickness)-> number processed loops

        adds emn geometry from emn file"""
    vertex_index=-1; #presume no vertex
    lines=-1 #presume no lines
    out_shape=[]
    out_face=[]
    for point in board_outline:
       vertex=Base.Vector(point[1],point[2],0)
       vertex_index+=1
       if vertex_index==0:
          lines=point[0]
       elif lines==point[0]:
           if point[3]!=0 and point[3]!=360:
              out_shape.append(Part.Arc(prev_vertex,mid_point(prev_vertex,vertex,point[3]),vertex))
              FreeCAD.Console.PrintMessage("mid point "+str(mid_point)+"\n")
           elif point[3]==360:
              per_point=Per_point(prev_vertex,vertex)
              out_shape.append(Part.Arc(per_point,mid_point(per_point,vertex,point[3]/2),vertex))
              out_shape.append(Part.Arc(per_point,mid_point(per_point,vertex,-point[3]/2),vertex))
           else:
              out_shape.append(Part.LineSegment(prev_vertex,vertex))
       else:
          out_shape=Part.Shape(out_shape)
          out_shape=Part.Wire(out_shape.Edges)
          out_face.append(Part.Face(out_shape))
          out_shape=[]
          vertex_index=0
          lines=point[0]
       prev_vertex=vertex
    if lines!=-1:
      out_shape=Part.Shape(out_shape)
      out_shape=Part.Wire(out_shape.Edges)
      out_face.append(Part.Face(out_shape))
      outline=out_face[0]
      FreeCAD.Console.PrintMessage("Added outline\n")
      if len(out_face)>1:
        for otl_cut in out_face[1: ]:
          outline=outline.cut(otl_cut)
          FreeCAD.Console.PrintMessage("Cutting shape inside outline\n")
      for drill in drills:
        FreeCAD.Console.PrintMessage("Cutting hole inside outline\n")
        out_shape=Part.makeCircle(drill[0]/2, Base.Vector(drill[1],drill[2],0))
        out_shape=Part.Wire(out_shape.Edges)
        outline=outline.cut(Part.Face(out_shape))
      doc_outline=doc.addObject("Part::Feature","Board_outline")
      doc_outline.Shape=outline.extrude(Base.Vector(0,0,-board_thickness))
      grp=doc.addObject("App::DocumentObjectGroup", "Board_Geoms")
      grp.addObject(doc_outline)
      doc.Board_outline.ViewObject.ShapeColor=(0.0, 0.5, 0.0, 0.0)
    return lines+1

def mid_point(prev_vertex,vertex,angle):
    """mid_point(prev_vertex,vertex,angle)-> mid_vertex

       returns mid point on arc of angle between prev_vertex and vertex"""
    angle=radians(angle/2)
    basic_angle=atan2(vertex.y-prev_vertex.y,vertex.x-prev_vertex.x)-pi/2
    shift=(1-cos(angle))*hypot(vertex.y-prev_vertex.y,vertex.x-prev_vertex.x)/2/sin(angle)
    midpoint=Base.Vector((vertex.x+prev_vertex.x)/2+shift*cos(basic_angle),(vertex.y+prev_vertex.y)/2+shift*sin(basic_angle),0)
    return midpoint

def split_records(line_record):
    """split_records(line_record)-> list of strings(records)

       standard separator list separator is space, records containing encapsulated by " """
    split_result=[]
    quote_pos=line_record.find('"')
    while quote_pos!=-1:
       if quote_pos>0:
          split_result.extend(line_record[ :quote_pos].split())
          line_record=line_record[quote_pos: ]
          quote_pos=line_record.find('"',1)
       else:
          quote_pos=line_record.find('"',1)
       if quote_pos!=-1:
          split_result.append(line_record[ :quote_pos+1])
          line_record=line_record[quote_pos+1: ]
       else:
          split_result.append(line_record)
          line_record=""
       quote_pos=line_record.find('"')
    split_result.extend(line_record.split())
    return split_result

def process_emp(doc,filename,placement,board_thickness):
   """process_emp(doc,filename,placement,board_thickness) -> place components from emn file to board"""
   filename=filename.partition(".emn")[0]+".emp"
   empfile=pyopen(filename, "r")
   emp_unit=1.0 #presume millimeter like emn unit
   emp_version=2 #presume emn_version 2
   comp_height=0 #presume 0 part height
   comp_outline=[] #no part outline
   comp_GeometryName="" # no geometry name
   comp_PartNumber="" # no Part Number
   comp_height=0 # no Comp Height
   emplines=empfile.readlines()
   empfile.close()
   passed_sections=[]
   current_section=""
   section_counter=0
   comps=[]
   for empline in emplines:
     emprecords=split_records(empline)
     if len( emprecords )==0 : continue
     if len( emprecords[0] )>4 and emprecords[0][0:4]==".END":
        passed_sections.append(current_section)
        current_section=""
        if comp_PartNumber!="":
          if comp_height==0:
            comp_height=0.1
          comps.append((comp_PartNumber,[Process_comp_outline(doc,comp_outline,comp_height),comp_GeometryName]))
          comp_PartNumber=""
          comp_outline=[]
     elif emprecords[0][0]==".":
        current_section=emprecords[0]
        section_counter=0
     section_counter+=1
     if current_section==".HEADER"  and section_counter==2:
        emp_version=int(float(emprecords[1]))
        FreeCAD.Console.PrintMessage("Emp version: "+emprecords[1]+"\n")
     if (current_section==".ELECTRICAL" or current_section==".MECHANICAL") and section_counter==2 and emprecords[2]=="THOU":
        emp_unit=0.0254
     if (current_section==".ELECTRICAL" or current_section==".MECHANICAL") and section_counter==2 and emprecords[2]=="MM":
        emp_unit=1
     if (current_section==".ELECTRICAL" or current_section==".MECHANICAL") and section_counter==2:
        comp_outline=[] #no part outline
        comp_GeometryName=emprecords[0] # geometry name
        comp_PartNumber=emprecords[1] # Part Number
        comp_height=emp_unit*float(emprecords[3]) # Comp Height
     if (current_section==".ELECTRICAL" or current_section==".MECHANICAL") and section_counter>2:
        comp_outline.append([float(emprecords[1])*emp_unit,float(emprecords[2])*emp_unit,float(emprecords[3])]) #add point of outline
   FreeCAD.Console.PrintMessage("\n".join(passed_sections)+"\n")
   #Write file with list of footprint
   if IDF_diag==1:
     empfile=pyopen(IDF_diag_path+"/footprint.lst", "w")
     for compx in comps:
       empfile.writelines(str(compx[1][1])+"\n")
     empfile.close()
   #End section of list footprint
   comps=dict(comps)
   grp=doc.addObject("App::DocumentObjectGroup", "EMP Models")
   for place_item in placement:
     if place_item[1] in comps:
       doc_comp=doc.addObject("Part::Feature",place_item[0])
       FreeCAD.Console.PrintMessage("Adding EMP model "+str(place_item[0])+"\n")
       doc_comp.Shape=comps[place_item[1]][0]
       doc_comp.ViewObject.DisplayMode=EmpDisplayMode
       z_pos=0
       rotateY=0
       if place_item[6]=='BOTTOM':
          rotateY=pi
          z_pos=-board_thickness
       placmnt=Base.Placement(Base.Vector(place_item[3],place_item[4],z_pos),toQuaternion(rotateY,place_item[5]*pi/180,0))
       doc_comp.Placement=placmnt
       grp.addObject(doc_comp)
   return 1

def Process_comp_outline(doc,comp_outline,comp_height):
    """Process_comp_outline(doc,comp_outline,comp_height)->part shape
       Create solid component shape base on its outline"""
    vertex_index=-1; #presume no vertex
    out_shape=[]
    if comp_outline==[]:  #force 0.2mm circle shape for components without place outline definition
       comp_outline.append([0.0,0.0,0.0])
       comp_outline.append([0.1,0.0,360.0])
    for point in comp_outline:
       vertex=Base.Vector(point[0],point[1],0)
       vertex_index+=1
       if vertex_index>0:
         if point[2]!=0 and point[2]!=360:
            out_shape.append(Part.Arc(prev_vertex,mid_point(prev_vertex,vertex,point[2]),vertex))
            FreeCAD.Console.PrintMessage("mid point "+str(mid_point)+"\n")
         elif point[2]==360:
            per_point=Per_point(prev_vertex,vertex)
            out_shape.append(Part.Arc(per_point,mid_point(per_point,vertex,point[2]/2),vertex))
            out_shape.append(Part.Arc(per_point,mid_point(per_point,vertex,-point[2]/2),vertex))
         else:
            out_shape.append(Part.LineSegment(prev_vertex,vertex))
       prev_vertex=vertex
    out_shape=Part.Shape(out_shape)
    out_shape=Part.Wire(out_shape.Edges)
    out_shape=Part.Face(out_shape)
    out_shape=out_shape.extrude(Base.Vector(0,0,comp_height))
    return out_shape

def place_steps(doc,placement,board_thickness):
    """ place_steps(doc,placement,board_thickness)->place step models on board

        list of models and path to step files is set at start of this script
                 model_tab_filename= "" &   step_path="" """
    model_file=pyopen(model_tab_filename, "r")
    model_lines=model_file.readlines()
    model_file.close()
    model_dict=[]
    if IDF_diag==1:
        model_file=pyopen(IDF_diag_path+"/missing_models.lst", "w")
    keys=[]
    step_dict=[]
    for model_line in model_lines:
        model_records=split_records(model_line)
        if len(model_records)>1 and model_records[0] and not model_records[0] in keys:
           keys.append(model_records[0])
           model_dict.append((str(model_records[0]).replace('"',''),str(model_records[1]).replace('"','')))
    model_dict=dict(model_dict)
    validkeys=filter(lambda x:x in  [place_item[2] for place_item in placement], model_dict.keys())
    FreeCAD.Console.PrintMessage("Step models to be loaded for footprints: "+str(validkeys)+"\n")
    grp=doc.addObject("App::DocumentObjectGroup", "Step Lib")
    for validkey in validkeys:
         ImportGui.insert(step_path+model_dict[validkey],FreeCAD.ActiveDocument.Name)
         impPart=FreeCAD.ActiveDocument.ActiveObject
         impPart.ViewObject.Visibility=0
         impPart.Label=validkey
         grp.addObject(impPart)
         step_dict.append((validkey,impPart))
         FreeCAD.Console.PrintMessage("Reading step file "+str(model_dict[validkey])+" for footprint "+str(validkey)+"\n")
    step_dict=dict(step_dict)
    grp=doc.addObject("App::DocumentObjectGroup", "Step Models")
    for place_item in placement:
      if place_item[2] in step_dict:
        step_model=doc.addObject("Part::Feature",place_item[0]+"_s")
        FreeCAD.Console.PrintMessage("Adding STEP model "+str(place_item[0])+"\n")
        step_model.Shape=step_dict[place_item[2]].Shape
        step_model.ViewObject.DiffuseColor=step_dict[place_item[2]].ViewObject.DiffuseColor
        z_pos=0
        rotateY=0
        if place_item[6]=='BOTTOM':
           rotateY=pi
           z_pos=-board_thickness
        placmnt=Base.Placement(Base.Vector(place_item[3],place_item[4],z_pos),toQuaternion(rotateY,place_item[5]*pi/180,0))
        step_model.Placement=placmnt
        grp.addObject(step_model)
      else:
        if IDF_diag==1:
            model_file.writelines(str(place_item[0])+" "+str(place_item[2])+"\n")
            model_file.close()

def toQuaternion(heading, attitude,bank): # rotation heading=around Y, attitude =around Z,  bank attitude =around X
    """toQuaternion(heading, attitude,bank)->FreeCAD.Base.Rotation(Quternion)"""
    c1 = cos(heading/2)
    s1 = sin(heading/2)
    c2 = cos(attitude/2)
    s2 = sin(attitude/2)
    c3 = cos(bank/2)
    s3 = sin(bank/2)
    c1c2 = c1*c2
    s1s2 = s1*s2
    w = c1c2*c3 - s1s2*s3
    x = c1c2*s3 + s1s2*c3
    y = s1*c2*c3 + c1*s2*s3
    z = c1*s2*c3 - s1*c2*s3
    return  FreeCAD.Base.Rotation(x,y,z,w)

def Per_point(prev_vertex,vertex):
    """Per_point(center,vertex)->per point

       returns opposite perimeter point of circle"""
    perpoint=Base.Vector(2*prev_vertex.x-vertex.x,2*prev_vertex.y-vertex.y,0)
    return perpoint