413 lines
19 KiB
Python
413 lines
19 KiB
Python
#***************************************************************************
|
|
#* (c) Milos Koutny (milos.koutny@gmail.com) 2012 *
|
|
#* *
|
|
#* 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 *
|
|
#* *
|
|
#* Milos Koutny 2010 *
|
|
#***************************************************************************/
|
|
|
|
import FreeCAD, Part, os, FreeCADGui, __builtin__
|
|
from FreeCAD import Base
|
|
from math import *
|
|
import ImportGui
|
|
|
|
##########################################################
|
|
# 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 preffered)/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 #
|
|
########################################################################################
|
|
|
|
pythonopen = __builtin__.open # to distinguish python built-in open function from the one declared here
|
|
|
|
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=pythonopen(filename, "r")
|
|
emn_unit=1.0 #presume milimeter 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 proccesed 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.Line(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
|
|
#FreeCADGui.Selection.addSelection(doc_outline)
|
|
#FreeCADGui.runCommand("Draft_Upgrade")
|
|
#outline=FreeCAD.ActiveDocument.getObject("Union").Shape
|
|
#FreeCAD.ActiveDocument.removeObject("Union")
|
|
#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 containting 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=pythonopen(filename, "r")
|
|
emp_unit=1.0 #presume milimeter 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=pythonopen(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 comps.has_key(place_item[1]):
|
|
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.Line(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))
|
|
#Part.show(out_shape)
|
|
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=pythonopen(model_tab_filename, "r")
|
|
model_lines=model_file.readlines()
|
|
model_file.close()
|
|
model_dict=[]
|
|
if IDF_diag==1:
|
|
model_file=pythonopen(IDF_diag_path+"/missing_models.lst", "w")
|
|
keys=[]
|
|
#prev_step="*?.*?" #hope nobody will insert this step filename
|
|
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)
|
|
#partName=FreeCAD.ActiveDocument.ActiveObject.Name
|
|
impPart=FreeCAD.ActiveDocument.ActiveObject
|
|
#impPart.Shape=FreeCAD.ActiveDocument.ActiveObject.Shape
|
|
#impPart.ViewObject.DiffuseColor=FreeCAD.ActiveDocument.ActiveObject.ViewObject.DiffuseColor
|
|
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 step_dict.has_key(place_item[2]):
|
|
step_model=doc.addObject("Part::Feature",place_item[0]+"_s")
|
|
FreeCAD.Console.PrintMessage("Adding STEP model "+str(place_item[0])+"\n")
|
|
#if prev_step!=place_item[2]:
|
|
# model0=Part.read(step_path+"/"+model_dict[place_item[2]])
|
|
# prev_step=place_item[2]
|
|
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=arround Y, attitude =arround Z, bank attitude =arround 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"""
|
|
#basic_angle=atan2(prev_vertex.y-vertex.y,prev_vertex.x-vertex.x)
|
|
#shift=hypot(prev_vertex.y-vertex.y,prev_vertex.x-vertex.x)
|
|
#perpoint=Base.Vector(prev_vertex.x+shift*cos(basic_angle),prev_vertex.y+shift*sin(basic_angle),0)
|
|
perpoint=Base.Vector(2*prev_vertex.x-vertex.x,2*prev_vertex.y-vertex.y,0)
|
|
return perpoint
|
|
|
|
|