Morgan Quigley 1b9c8676df Fix bug in STEP import with curve intersections.
Set BRL precision to 10 nanometers to prevent floating point round errors
from creating arcs that leave gaps in the board outlines.  Note: it may
be prudent to make this setting 100 nanometers if there are gerber export
issues.

Fixes lp:1652406

https://bugs.launchpad.net/kicad/+bug/1652406
2017-02-01 19:32:27 -05:00

1392 lines
40 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <algorithm>
#include <cmath>
#include <sstream>
#include <string>
#include <utility>
#include <wx/filename.h>
#include <wx/log.h>
#include "oce_utils.h"
#include "kicadpad.h"
#include <IGESCAFControl_Reader.hxx>
#include <IGESCAFControl_Writer.hxx>
#include <IGESControl_Controller.hxx>
#include <IGESData_GlobalSection.hxx>
#include <IGESData_IGESModel.hxx>
#include <Interface_Static.hxx>
#include <Quantity_Color.hxx>
#include <STEPCAFControl_Reader.hxx>
#include <STEPCAFControl_Writer.hxx>
#include <APIHeaderSection_MakeHeader.hxx>
#include <TCollection_ExtendedString.hxx>
#include <TDataStd_Name.hxx>
#include <TDF_LabelSequence.hxx>
#include <TDF_ChildIterator.hxx>
#include <TopExp_Explorer.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <BRep_Tool.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRepBuilderAPI.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepAlgoAPI_Cut.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Wire.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Builder.hxx>
#include <gp_Ax2.hxx>
#include <gp_Circ.hxx>
#include <gp_Dir.hxx>
#include <gp_Pnt.hxx>
#define USER_PREC (1e-4)
#define USER_ANGLE_PREC (1e-6)
// minimum PCB thickness in mm (2 microns assumes a very thin polyimide film)
#define THICKNESS_MIN (0.002)
// default PCB thickness in mm
#define THICKNESS_DEFAULT (1.6)
// nominal offset from the board
#define BOARD_OFFSET (0.05 )
// min. length**2 below which 2 points are considered coincident
#define MIN_LENGTH2 (0.0001)
static void getEndPoints( const KICADCURVE& aCurve, double& spx0, double& spy0,
double& epx0, double& epy0 )
{
if( CURVE_ARC == aCurve.m_form )
{
spx0 = aCurve.m_end.x;
spy0 = aCurve.m_end.y;
epx0 = aCurve.m_ep.x;
epy0 = aCurve.m_ep.y;
return;
}
// assume a line
spx0 = aCurve.m_start.x;
spy0 = aCurve.m_start.y;
epx0 = aCurve.m_end.x;
epy0 = aCurve.m_end.y;
return;
}
static void getCurveEndPoint( const KICADCURVE& aCurve, DOUBLET& aEndPoint )
{
if( CURVE_CIRCLE == aCurve.m_form )
return; // circles are closed loops and have no end point
if( CURVE_ARC == aCurve.m_form )
{
aEndPoint.x = aCurve.m_ep.x;
aEndPoint.y = aCurve.m_ep.y;
return;
}
// assume a line
aEndPoint.x = aCurve.m_end.x;
aEndPoint.y = aCurve.m_end.y;
return;
}
static void reverseCurve( KICADCURVE& aCurve )
{
if( CURVE_NONE == aCurve.m_form || CURVE_CIRCLE == aCurve.m_form )
return;
if( CURVE_LINE == aCurve.m_form )
{
std::swap( aCurve.m_start, aCurve.m_end );
return;
}
std::swap( aCurve.m_end, aCurve.m_ep );
std::swap( aCurve.m_endangle, aCurve.m_startangle );
aCurve.m_angle = -aCurve.m_angle;
return;
}
// supported file types
enum FormatType
{
FMT_NONE = 0,
FMT_STEP = 1,
FMT_IGES = 2,
FMT_EMN = 3,
FMT_IDF = 4
};
FormatType fileType( const char* aFileName )
{
wxFileName lfile( aFileName );
if( !lfile.FileExists() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * no such file: '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return FMT_NONE;
}
wxString ext = lfile.GetExt();
if( ext == "idf" || ext == "IDF" )
return FMT_IDF; // component outline
else if( ext == "emn" || ext == "EMN" )
return FMT_EMN; // PCB assembly
std::ifstream ifile;
ifile.open( aFileName );
if( !ifile.is_open() )
return FMT_NONE;
char iline[82];
memset( iline, 0, 82 );
ifile.getline( iline, 82 );
ifile.close();
iline[81] = 0; // ensure NULL termination when string is too long
// check for STEP in Part 21 format
// (this can give false positives since Part 21 is not exclusively STEP)
if( !strncmp( iline, "ISO-10303-21;", 13 ) )
return FMT_STEP;
std::string fstr = iline;
// check for STEP in XML format
// (this can give both false positive and false negatives)
if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
return FMT_STEP;
// Note: this is a very simple test which can yield false positives; the only
// sure method for determining if a file *not* an IGES model is to attempt
// to load it.
if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
return FMT_IGES;
return FMT_NONE;
}
PCBMODEL::PCBMODEL()
{
m_app = XCAFApp_Application::GetApplication();
m_app->NewDocument( "MDTV-XCAF", m_doc );
m_assy = XCAFDoc_DocumentTool::ShapeTool ( m_doc->Main() );
m_assy_label = m_assy->NewShape();
m_hasPCB = false;
m_components = 0;
m_precision = USER_PREC;
m_angleprec = USER_ANGLE_PREC;
m_thickness = THICKNESS_DEFAULT;
m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
m_mincurve = m_curves.end();
BRepBuilderAPI::Precision( 1.0e-6 );
return;
}
PCBMODEL::~PCBMODEL()
{
m_doc->Close();
return;
}
// add an outline segment
bool PCBMODEL::AddOutlineSegment( KICADCURVE* aCurve )
{
if( NULL == aCurve || LAYER_EDGE != aCurve->m_layer || CURVE_NONE == aCurve->m_form )
return false;
if( CURVE_LINE == aCurve->m_form )
{
// reject zero - length lines
double dx = aCurve->m_end.x - aCurve->m_start.x;
double dy = aCurve->m_end.y - aCurve->m_start.y;
double distance = dx * dx + dy * dy;
if( distance < MIN_LENGTH2 )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * rejected a zero-length line\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
}
else
{
// ensure that the start (center) and end (start of arc) are not the same point
double dx = aCurve->m_end.x - aCurve->m_start.x;
double dy = aCurve->m_end.y - aCurve->m_start.y;
double rad = dx * dx + dy * dy;
if( rad < MIN_LENGTH2 )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * rejected a zero-radius arc or circle\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
// calculate the radius and, if applicable, end point
rad = sqrt( rad );
aCurve->m_radius = rad;
if( CURVE_ARC == aCurve->m_form )
{
aCurve->m_startangle = atan2( dy, dx );
if( aCurve->m_startangle < 0.0 )
aCurve->m_startangle += 2.0 * M_PI;
double eang = aCurve->m_startangle + aCurve->m_angle;
if( eang < 0.0 )
eang += 2.0 * M_PI;
if( aCurve->m_angle < 0.0 && eang > aCurve->m_startangle )
aCurve->m_startangle += 2.0 * M_PI;
else if( aCurve->m_angle >= 0.0 && eang < aCurve->m_startangle )
eang += 2.0 * M_PI;
aCurve->m_endangle = eang;
aCurve->m_ep.x = aCurve->m_start.x + rad * cos( eang );
aCurve->m_ep.y = aCurve->m_start.y + rad * sin( eang );
dx = aCurve->m_ep.x - aCurve->m_end.x;
dy = aCurve->m_ep.y - aCurve->m_end.y;
rad = dx * dx + dy * dy;
if( rad < MIN_LENGTH2 )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * rejected an arc with equivalent end points\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
}
}
m_curves.push_back( *aCurve );
// check if this curve has the current leftmost feature
switch( aCurve->m_form )
{
case CURVE_LINE:
if( aCurve->m_start.x < m_minx )
{
m_minx = aCurve->m_start.x;
m_mincurve = --(m_curves.end());
}
if( aCurve->m_end.x < m_minx )
{
m_minx = aCurve->m_end.x;
m_mincurve = --(m_curves.end());
}
break;
case CURVE_CIRCLE:
do
{
double dx = aCurve->m_start.x - aCurve->m_radius;
if( dx < m_minx )
{
m_minx = dx;
m_mincurve = --(m_curves.end());
}
} while( 0 );
break;
case CURVE_ARC:
do
{
double dx0 = aCurve->m_end.x - aCurve->m_start.x;
double dy0 = aCurve->m_end.y - aCurve->m_start.y;
int q0; // quadrant of start point
if( dx0 > 0.0 && dy0 >= 0.0 )
q0 = 1;
else if( dx0 <= 0.0 && dy0 > 0.0 )
q0 = 2;
else if( dx0 < 0.0 && dy0 <= 0.0 )
q0 = 3;
else
q0 = 4;
double dx1 = aCurve->m_ep.x - aCurve->m_start.x;
double dy1 = aCurve->m_ep.y - aCurve->m_start.y;
int q1; // quadrant of end point
if( dx1 > 0.0 && dy1 >= 0.0 )
q1 = 1;
else if( dx1 <= 0.0 && dy1 > 0.0 )
q1 = 2;
else if( dx1 < 0.0 && dy1 <= 0.0 )
q1 = 3;
else
q1 = 4;
// calculate x0, y0 for the start point on a CCW arc
double x0 = aCurve->m_end.x;
double x1 = aCurve->m_ep.x;
if( aCurve->m_angle < 0.0 )
{
std::swap( q0, q1 );
std::swap( x0, x1 );
}
double minx;
if( ( q0 <= 2 && q1 >= 3 ) || ( q0 >= 3 && x0 > x1 ) )
minx = aCurve->m_start.x - aCurve->m_radius;
else
minx = std::min( x0, x1 );
if( minx < m_minx )
{
m_minx = minx;
m_mincurve = --(m_curves.end());
}
} while( 0 );
break;
default:
// unexpected curve type
do
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * unsupported curve type: '" << aCurve->m_form << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
} while( 0 );
return false;
}
return true;
}
// add a pad hole or slot
bool PCBMODEL::AddPadHole( KICADPAD* aPad )
{
if( NULL == aPad || !aPad->IsThruHole() )
return false;
if( !aPad->m_drill.oval )
{
TopoDS_Shape s = BRepPrimAPI_MakeCylinder( aPad->m_drill.size.x * 0.5,
m_thickness * 2.0 ).Shape();
gp_Trsf shift;
shift.SetTranslation( gp_Vec( aPad->m_position.x, aPad->m_position.y, -m_thickness * 0.5 ) );
BRepBuilderAPI_Transform hole( s, shift );
m_cutouts.push_back( hole.Shape() );
return true;
}
// slotted hole
double angle_offset = 0.0;
double rad; // radius of the slot
double hlen; // half length of the slot
if( aPad->m_drill.size.x < aPad->m_drill.size.y )
{
angle_offset = M_PI_2;
rad = aPad->m_drill.size.x * 0.5;
hlen = aPad->m_drill.size.y * 0.5 - rad;
}
else
{
rad = aPad->m_drill.size.y * 0.5;
hlen = aPad->m_drill.size.x * 0.5 - rad;
}
DOUBLET c0( -hlen, 0.0 );
DOUBLET c1( hlen, 0.0 );
DOUBLET p0( -hlen, rad );
DOUBLET p1( -hlen, -rad );
DOUBLET p2( hlen, -rad );
DOUBLET p3( hlen, rad );
angle_offset += aPad->m_rotation;
double dlim = (double)std::numeric_limits< float >::epsilon();
if( angle_offset < -dlim || angle_offset > dlim )
{
double vsin = sin( angle_offset );
double vcos = cos( angle_offset );
double x = c0.x * vcos - c0.y * vsin;
double y = c0.x * vsin + c0.y * vcos;
c0.x = x;
c0.y = y;
x = c1.x * vcos - c1.y * vsin;
y = c1.x * vsin + c1.y * vcos;
c1.x = x;
c1.y = y;
x = p0.x * vcos - p0.y * vsin;
y = p0.x * vsin + p0.y * vcos;
p0.x = x;
p0.y = y;
x = p1.x * vcos - p1.y * vsin;
y = p1.x * vsin + p1.y * vcos;
p1.x = x;
p1.y = y;
x = p2.x * vcos - p2.y * vsin;
y = p2.x * vsin + p2.y * vcos;
p2.x = x;
p2.y = y;
x = p3.x * vcos - p3.y * vsin;
y = p3.x * vsin + p3.y * vcos;
p3.x = x;
p3.y = y;
}
c0.x += aPad->m_position.x;
c0.y += aPad->m_position.y;
c1.x += aPad->m_position.x;
c1.y += aPad->m_position.y;
p0.x += aPad->m_position.x;
p0.y += aPad->m_position.y;
p1.x += aPad->m_position.x;
p1.y += aPad->m_position.y;
p2.x += aPad->m_position.x;
p2.y += aPad->m_position.y;
p3.x += aPad->m_position.x;
p3.y += aPad->m_position.y;
OUTLINE oln;
KICADCURVE crv0, crv1, crv2, crv3;
// crv0 = arc
crv0.m_start = c0;
crv0.m_end = p0;
crv0.m_ep = p1;
crv0.m_angle = M_PI;
crv0.m_radius = rad;
crv0.m_form = CURVE_ARC;
// crv1 = line
crv1.m_start = p1;
crv1.m_end = p2;
crv1.m_form = CURVE_LINE;
// crv2 = arc
crv2.m_start = c1;
crv2.m_end = p2;
crv2.m_ep = p3;
crv2.m_angle = M_PI;
crv2.m_radius = rad;
crv2.m_form = CURVE_ARC;
// crv3 = line
crv3.m_start = p3;
crv3.m_end = p0;
crv3.m_form = CURVE_LINE;
oln.AddSegment( crv0 );
oln.AddSegment( crv1 );
oln.AddSegment( crv2 );
oln.AddSegment( crv3 );
TopoDS_Shape slot;
if( oln.MakeShape( slot, m_thickness ) )
{
if( !slot.IsNull() )
m_cutouts.push_back( slot );
return true;
}
return false;
}
// add a component at the given position and orientation
bool PCBMODEL::AddComponent( const std::string& aFileName, const std::string aRefDes,
bool aBottom, DOUBLET aPosition, double aRotation,
TRIPLET aOffset, TRIPLET aOrientation )
{
// first retrieve a label
TDF_Label lmodel;
if( !getModelLabel( aFileName, lmodel ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * no model for filename '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
// calculate the Location transform
TopLoc_Location toploc;
if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * no location data for filename '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
// add the located sub-assembly
TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
if( llabel.IsNull() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not add component with filename '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
// attach the RefDes name
TCollection_ExtendedString refdes( aRefDes.c_str() );
TDataStd_Name::Set( llabel, refdes );
return true;
}
void PCBMODEL::SetPCBThickness( double aThickness )
{
if( aThickness < 0.0 )
m_thickness = THICKNESS_DEFAULT;
else if( aThickness < THICKNESS_MIN )
m_thickness = THICKNESS_MIN;
else
m_thickness = aThickness;
return;
}
// create the PCB (board only) model using the current outlines and drill holes
bool PCBMODEL::CreatePCB()
{
if( m_hasPCB )
{
if( m_pcb_label.IsNull() )
return false;
return true;
}
if( m_curves.empty() || m_mincurve == m_curves.end() )
{
m_hasPCB = true;
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * no valid board outline\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
TopoDS_Shape board;
OUTLINE oln; // loop to assemble (represents PCB outline and cutouts)
oln.AddSegment( *m_mincurve );
m_curves.erase( m_mincurve );
while( !m_curves.empty() )
{
if( oln.IsClosed() )
{
if( board.IsNull() )
{
if( !oln.MakeShape( board, m_thickness ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not create board extrusion\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
}
else
{
TopoDS_Shape hole;
if( oln.MakeShape( hole, m_thickness ) )
{
m_cutouts.push_back( hole );
}
else
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not create board cutout\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
}
}
oln.Clear();
if( !m_curves.empty() )
{
oln.AddSegment( m_curves.front() );
m_curves.pop_front();
}
continue;
}
std::list< KICADCURVE >::iterator sC = m_curves.begin();
std::list< KICADCURVE >::iterator eC = m_curves.end();
while( sC != eC )
{
if( oln.AddSegment( *sC ) )
{
m_curves.erase( sC );
break;
}
++sC;
}
if( sC == eC && !oln.m_curves.empty() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not close outline (dropping outline data with " << oln.m_curves.size() << " segments)\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
oln.Clear();
if( !m_curves.empty() )
{
oln.AddSegment( m_curves.front() );
m_curves.pop_front();
}
}
}
if( oln.IsClosed() )
{
if( board.IsNull() )
{
if( !oln.MakeShape( board, m_thickness ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not create board extrusion\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
}
else
{
TopoDS_Shape hole;
if( oln.MakeShape( hole, m_thickness ) )
{
m_cutouts.push_back( hole );
}
else
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not create board cutout\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
}
}
}
// subtract cutouts (if any)
for( auto i : m_cutouts )
board = BRepAlgoAPI_Cut( board, i );
// push the board to the data structure
m_pcb_label = m_assy->AddComponent( m_assy_label, board );
if( m_pcb_label.IsNull() )
return false;
// color the PCB
Handle(XCAFDoc_ColorTool) color =
XCAFDoc_DocumentTool::ColorTool( m_doc->Main () );
Quantity_Color pcb_green( 0.06, 0.4, 0.06, Quantity_TOC_RGB );
color->SetColor( m_pcb_label, pcb_green, XCAFDoc_ColorSurf );
TopExp_Explorer topex;
topex.Init( m_assy->GetShape( m_pcb_label ), TopAbs_SOLID );
while( topex.More() )
{
color->SetColor( topex.Current(), pcb_green, XCAFDoc_ColorSurf );
topex.Next();
}
return true;
}
#ifdef SUPPORTS_IGES
// write the assembly model in IGES format
bool PCBMODEL::WriteIGES( const std::string& aFileName, bool aOverwrite )
{
if( m_pcb_label.IsNull() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * No valid PCB assembly; cannot create output file " << aFileName << "\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
wxFileName fn( aFileName );
IGESControl_Controller::Init();
IGESCAFControl_Writer writer;
writer.SetColorMode( Standard_True );
writer.SetNameMode( Standard_True );
IGESData_GlobalSection header = writer.Model()->GlobalSection();
header.SetFileName( new TCollection_HAsciiString( fn.GetFullName().ToUTF8() ) );
header.SetSendName( new TCollection_HAsciiString( "KiCad electronic assembly" ) );
header.SetAuthorName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.author" ) ) );
header.SetCompanyName( new TCollection_HAsciiString( Interface_Static::CVal( "write.iges.header.company" ) ) );
writer.Model()->SetGlobalSection( header );
if( Standard_False == writer.Perform( m_doc, aFileName.c_str() ) )
return false;
return true;
}
#endif
// write the assembly model in STEP format
bool PCBMODEL::WriteSTEP( const std::string& aFileName, bool aOverwrite )
{
if( m_pcb_label.IsNull() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * No valid PCB assembly; cannot create output file " << aFileName << "\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
STEPCAFControl_Writer writer;
writer.SetColorMode( Standard_True );
writer.SetNameMode( Standard_True );
if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
return false;
APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
wxFileName fn( aFileName );
hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToUTF8() ) );
// TODO: how to control and ensure consistency with IGES?
hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "An Author" ) );
hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "A Company" ) );
hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
if( Standard_False == writer.Write( aFileName.c_str() ) )
return false;
return true;
}
bool PCBMODEL::getModelLabel( const std::string aFileName, TDF_Label& aLabel )
{
MODEL_MAP::const_iterator mm = m_models.find( aFileName );
if( mm != m_models.end() )
{
aLabel = mm->second;
return true;
}
aLabel.Nullify();
Handle( TDocStd_Document ) doc;
m_app->NewDocument( "MDTV-XCAF", doc );
FormatType modelFmt = fileType( aFileName.c_str() );
switch( modelFmt )
{
case FMT_IGES:
if( !readIGES( doc, aFileName.c_str() ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * readIGES() failed on filename '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
break;
case FMT_STEP:
if( !readSTEP( doc, aFileName.c_str() ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * readSTEP() failed on filename '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
break;
// TODO: implement IDF and EMN converters
default:
return false;
}
aLabel = transferModel( doc, m_doc );
if( aLabel.IsNull() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * could not transfer model data from file '" << aFileName << "'\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
// attach the PART NAME ( base filename: note that in principle
// different models may have the same base filename )
wxFileName afile( aFileName.c_str() );
std::string pname( afile.GetName().ToUTF8() );
TCollection_ExtendedString partname( pname.c_str() );
TDataStd_Name::Set( aLabel, partname );
m_models.insert( MODEL_DATUM( aFileName, aLabel ) );
++m_components;
return true;
}
bool PCBMODEL::getModelLocation( bool aBottom, DOUBLET aPosition, double aRotation,
TRIPLET aOffset, TRIPLET aOrientation, TopLoc_Location& aLocation )
{
// Order of operations:
// a. aOrientation is applied -Z*-Y*-X
// b. aOffset is applied
// Top ? add thickness to the Z offset
// c. Bottom ? Rotate on X axis (in contrast to most ECAD which mirror on Y),
// then rotate on +Z
// Top ? rotate on -Z
// d. aPosition is applied
//
// Note: Y axis is inverted in KiCad
gp_Trsf lPos;
lPos.SetTranslation( gp_Vec( aPosition.x, -aPosition.y, 0.0 ) );
// offset (inches)
aOffset.x *= 25.4;
aOffset.y *= 25.4;
aOffset.z *= 25.4 + BOARD_OFFSET;
gp_Trsf lRot;
if( aBottom )
{
lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
lPos.Multiply( lRot );
lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 1.0, 0.0, 0.0 ) ), M_PI );
lPos.Multiply( lRot );
}
else
{
aOffset.z += m_thickness;
lRot.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ), gp_Dir( 0.0, 0.0, 1.0 ) ), aRotation );
lPos.Multiply( lRot );
}
gp_Trsf lOff;
lOff.SetTranslation( gp_Vec( aOffset.x, aOffset.y, aOffset.z ) );
lPos.Multiply( lOff );
gp_Trsf lOrient;
lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ),
gp_Dir( 0.0, 0.0, 1.0 ) ), -aOrientation.z );
lPos.Multiply( lOrient );
lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ),
gp_Dir( 0.0, 1.0, 0.0 ) ), -aOrientation.y );
lPos.Multiply( lOrient );
lOrient.SetRotation( gp_Ax1( gp_Pnt( 0.0, 0.0, 0.0 ),
gp_Dir( 1.0, 0.0, 0.0 ) ), -aOrientation.x );
lPos.Multiply( lOrient );
aLocation = TopLoc_Location( lPos );
return true;
}
bool PCBMODEL::readIGES( Handle( TDocStd_Document )& doc, const char* fname )
{
IGESControl_Controller::Init();
IGESCAFControl_Reader reader;
IFSelect_ReturnStatus stat = reader.ReadFile( fname );
if( stat != IFSelect_RetDone )
return false;
// Enable user-defined shape precision
if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
return false;
// Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
return false;
// set other translation options
reader.SetColorMode(true); // use model colors
reader.SetNameMode(false); // don't use IGES label names
reader.SetLayerMode(false); // ignore LAYER data
if ( !reader.Transfer( doc ) )
{
doc->Close();
return false;
}
// are there any shapes to translate?
if( reader.NbShapes() < 1 )
{
doc->Close();
return false;
}
return true;
}
bool PCBMODEL::readSTEP( Handle(TDocStd_Document)& doc, const char* fname )
{
STEPCAFControl_Reader reader;
IFSelect_ReturnStatus stat = reader.ReadFile( fname );
if( stat != IFSelect_RetDone )
return false;
// Enable user-defined shape precision
if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
return false;
// Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
return false;
// set other translation options
reader.SetColorMode(true); // use model colors
reader.SetNameMode(false); // don't use label names
reader.SetLayerMode(false); // ignore LAYER data
if ( !reader.Transfer( doc ) )
{
doc->Close();
return false;
}
// are there any shapes to translate?
if( reader.NbRootsForTransfer() < 1 )
{
doc->Close();
return false;
}
return true;
}
TDF_Label PCBMODEL::transferModel( Handle( TDocStd_Document )& source,
Handle( TDocStd_Document )& dest )
{
// transfer data from Source into a top level component of Dest
// s_assy = shape tool for the source
Handle(XCAFDoc_ShapeTool) s_assy = XCAFDoc_DocumentTool::ShapeTool ( source->Main() );
// retrieve all free shapes within the assembly
TDF_LabelSequence frshapes;
s_assy->GetFreeShapes( frshapes );
// d_assy = shape tool for the destination
Handle(XCAFDoc_ShapeTool) d_assy = XCAFDoc_DocumentTool::ShapeTool ( dest->Main() );
// create a new shape within the destination and set the assembly tool to point to it
TDF_Label component = d_assy->NewShape();
int nshapes = frshapes.Length();
int id = 1;
Handle( XCAFDoc_ColorTool ) scolor = XCAFDoc_DocumentTool::ColorTool( source->Main() );
Handle( XCAFDoc_ColorTool ) dcolor = XCAFDoc_DocumentTool::ColorTool( dest->Main() );
TopExp_Explorer dtop;
TopExp_Explorer stop;
while( id <= nshapes )
{
TopoDS_Shape shape = s_assy->GetShape( frshapes.Value(id) );
if ( !shape.IsNull() )
{
TDF_Label niulab = d_assy->AddComponent( component, shape, Standard_False );
// check for per-surface colors
stop.Init( shape, TopAbs_FACE );
dtop.Init( d_assy->GetShape( niulab ), TopAbs_FACE );
while( stop.More() && dtop.More() )
{
Quantity_Color face_color;
TDF_Label tl;
// give priority to the base shape's color
if( s_assy->FindShape( stop.Current(), tl ) )
{
if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color )
|| scolor->GetColor( tl, XCAFDoc_ColorGen, face_color )
|| scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) )
{
dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
}
}
else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color )
|| scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color )
|| scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) )
{
dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
}
stop.Next();
dtop.Next();
}
// check for per-solid colors
stop.Init( shape, TopAbs_SOLID );
dtop.Init( d_assy->GetShape( niulab ), TopAbs_SOLID, TopAbs_FACE );
while( stop.More() && dtop.More() )
{
Quantity_Color face_color;
TDF_Label tl;
// give priority to the base shape's color
if( s_assy->FindShape( stop.Current(), tl ) )
{
if( scolor->GetColor( tl, XCAFDoc_ColorSurf, face_color )
|| scolor->GetColor( tl, XCAFDoc_ColorGen, face_color )
|| scolor->GetColor( tl, XCAFDoc_ColorCurv, face_color ) )
{
dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorGen );
}
}
else if( scolor->GetColor( stop.Current(), XCAFDoc_ColorSurf, face_color )
|| scolor->GetColor( stop.Current(), XCAFDoc_ColorGen, face_color )
|| scolor->GetColor( stop.Current(), XCAFDoc_ColorCurv, face_color ) )
{
dcolor->SetColor( dtop.Current(), face_color, XCAFDoc_ColorSurf );
}
stop.Next();
dtop.Next();
}
}
++id;
};
return component;
}
OUTLINE::OUTLINE()
{
m_closed = false;
return;
}
OUTLINE::~OUTLINE()
{
return;
}
void OUTLINE::Clear()
{
m_closed = false;
m_curves.clear();
return;
}
bool OUTLINE::AddSegment( const KICADCURVE& aCurve )
{
if( m_closed )
return false;
if( m_curves.empty() )
{
m_curves.push_back( aCurve );
if( CURVE_CIRCLE == aCurve.m_form )
m_closed = true;
return true;
}
if( CURVE_CIRCLE == aCurve.m_form )
return false;
// get the end points of the first curve
double spx0, spy0;
double epx0, epy0;
getEndPoints( m_curves.front(), spx0, spy0, epx0, epy0 );
// get the end points of the free curve
double spx1, spy1;
double epx1, epy1;
getEndPoints( aCurve, spx1, spy1, epx1, epy1 );
// check if the curve attaches to the front
double dx, dy;
dx = epx1 - spx0;
dy = epy1 - spy0;
if( dx * dx + dy * dy < MIN_LENGTH2 )
{
m_curves.push_front( aCurve );
m_closed = testClosed( m_curves.front(), m_curves.back() );
return true;
}
else
{
dx = spx1 - spx0;
dy = spy1 - spy0;
if( dx * dx + dy * dy < MIN_LENGTH2 )
{
KICADCURVE curve = aCurve;
reverseCurve( curve );
m_curves.push_front( curve );
m_closed = testClosed( m_curves.front(), m_curves.back() );
return true;
}
}
// check if the curve attaches to the back
getEndPoints( m_curves.back(), spx0, spy0, epx0, epy0 );
dx = spx1 - epx0;
dy = spy1 - epy0;
if( dx * dx + dy * dy < MIN_LENGTH2 )
{
m_curves.push_back( aCurve );
m_closed = testClosed( m_curves.front(), m_curves.back() );
return true;
}
else
{
dx = epx1 - epx0;
dy = epy1 - epy0;
if( dx * dx + dy * dy < MIN_LENGTH2 )
{
KICADCURVE curve = aCurve;
reverseCurve( curve );
m_curves.push_back( curve );
m_closed = testClosed( m_curves.front(), m_curves.back() );
return true;
}
}
// this curve is not an end segment of the current loop
return false;
}
bool OUTLINE::MakeShape( TopoDS_Shape& aShape, double aThickness )
{
if( !aShape.IsNull() )
return false; // there is already data in the shape object
if( m_curves.empty() )
return true; // suceeded in doing nothing
if( !m_closed )
return false; // the loop is not closed
BRepBuilderAPI_MakeWire wire;
DOUBLET lastPoint;
getCurveEndPoint( m_curves.back(), lastPoint );
for( auto i : m_curves )
{
if( !addEdge( &wire, i, lastPoint ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * failed to add an edge\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
}
TopoDS_Face face = BRepBuilderAPI_MakeFace( wire );
aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
if( aShape.IsNull() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * failed to create a prismatic shape\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
return true;
}
bool OUTLINE::addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBLET& aLastPoint )
{
TopoDS_Edge edge;
DOUBLET endPoint;
getCurveEndPoint( aCurve, endPoint );
switch( aCurve.m_form )
{
case CURVE_LINE:
edge = BRepBuilderAPI_MakeEdge( gp_Pnt( aLastPoint.x, aLastPoint.y, 0.0 ),
gp_Pnt( endPoint.x, endPoint.y, 0.0 ) );
break;
case CURVE_ARC:
do
{
gp_Circ arc( gp_Ax2( gp_Pnt( aCurve.m_start.x, aCurve.m_start.y, 0.0 ),
gp_Dir( 0.0, 0.0, 1.0 ) ), aCurve.m_radius );
gp_Pnt sa( aLastPoint.x, aLastPoint.y, 0.0 );
gp_Pnt ea( endPoint.x, endPoint.y, 0.0 );
if( aCurve.m_angle < 0.0 )
edge = BRepBuilderAPI_MakeEdge( arc, ea, sa );
else
edge = BRepBuilderAPI_MakeEdge( arc, sa, ea );
} while( 0 );
break;
case CURVE_CIRCLE:
edge = BRepBuilderAPI_MakeEdge( gp_Circ( gp_Ax2( gp_Pnt( aCurve.m_start.x, aCurve.m_start.y, 0.0 ),
gp_Dir( 0.0, 0.0, 1.0 ) ), aCurve.m_radius ) );
break;
default:
do
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * unsupported curve type: " << aCurve.m_form << "\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
} while( 0 );
}
if( edge.IsNull() )
return false;
aLastPoint = endPoint;
aWire->Add( edge );
if( BRepBuilderAPI_DisconnectedWire == aWire->Error() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * failed to add curve\n";
wxLogMessage( "%s\n", ostr.str().c_str() );
return false;
}
return true;
}
bool OUTLINE::testClosed( KICADCURVE& aFrontCurve, KICADCURVE& aBackCurve )
{
double spx0, spy0, epx0, epy0;
getEndPoints( aFrontCurve, spx0, spy0, epx0, epy0 );
double spx1, spy1, epx1, epy1;
getEndPoints( aBackCurve, spx1, spy1, epx1, epy1 );
double dx = epx1 - spx0;
double dy = epy1 - spy0;
double r = dx * dx + dy * dy;
if( r < MIN_LENGTH2 )
return true;
return false;
}