FreeCAD/src/Mod/Idf/Idf.py

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