Seth Hillbrand 1d3159c1cb Add support for PKZIP-based stpZ files
FreeCAD uses gzip-based stpZ files but many programs will compress using
the archive format of PKZIP (e.g. WinZIP).  This handles the archive
format, taking the first file from the archive, which by the standard
should be the STEP file

Fixes https://gitlab.com/kicad/code/kicad/issues/5376
2021-02-27 16:44:54 -08:00

1664 lines
46 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/wx.h>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/stdpaths.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <decompress.hpp>
#include "oce_utils.h"
#include "kicadpad.h"
#include "streamwrapper.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 <Standard_Version.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_GTransform.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 <Standard_Failure.hxx>
#include <gp_Ax2.hxx>
#include <gp_Circ.hxx>
#include <gp_Dir.hxx>
#include <gp_Pnt.hxx>
#include <Geom_BezierCurve.hxx>
static constexpr double USER_PREC = 1e-4;
static constexpr double USER_ANGLE_PREC = 1e-6;
// minimum PCB thickness in mm (2 microns assumes a very thin polyimide film)
static constexpr double THICKNESS_MIN = 0.002;
// default PCB thickness in mm
static constexpr double THICKNESS_DEFAULT = 1.6;
// nominal offset from the board
static constexpr double BOARD_OFFSET = 0.05;
// min. length**2 below which 2 points are considered coincident
static constexpr double MIN_LENGTH2 = MIN_DISTANCE * MIN_DISTANCE;
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;
}
if( CURVE_BEZIER == aCurve.m_form )
{
std::swap( aCurve.m_start, aCurve.m_end );
std::swap( aCurve.m_bezierctrl1, aCurve.m_bezierctrl2 );
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,
FMT_STEP,
FMT_STEPZ,
FMT_IGES,
FMT_EMN,
FMT_IDF,
FMT_WRL,
FMT_WRZ
};
FormatType fileType( const char* aFileName )
{
wxFileName lfile( wxString::FromUTF8Unchecked( aFileName ) );
if( !lfile.FileExists() )
{
wxString msg;
msg.Printf( " * fileType(): no such file: %s\n",
wxString::FromUTF8Unchecked( aFileName ) );
ReportMessage( msg );
return FMT_NONE;
}
wxString ext = lfile.GetExt().Lower();
if( ext == "wrl" )
return FMT_WRL;
if( ext == "wrz" )
return FMT_WRZ;
if( ext == "idf" )
return FMT_IDF; // component outline
if( ext == "emn" )
return FMT_EMN; // PCB assembly
if( ext == "stpz" || ext == "gz" )
return FMT_STEPZ;
OPEN_ISTREAM( ifile, aFileName );
if( ifile.fail() )
return FMT_NONE;
char iline[82];
memset( iline, 0, 82 );
ifile.getline( iline, 82 );
CLOSE_STREAM( ifile );
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_minDistance2 = MIN_LENGTH2;
m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
m_mincurve = m_curves.end();
BRepBuilderAPI::Precision( MIN_DISTANCE );
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 || CURVE_BEZIER == 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 < m_minDistance2 )
{
wxString msg;
msg.Printf( " * AddOutlineSegment() rejected a zero-length %s\n", aCurve->Describe() );
ReportMessage( msg );
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 < m_minDistance2 )
{
wxString msg;
msg.Printf( " * AddOutlineSegment() rejected a zero-radius %s\n", aCurve->Describe() );
ReportMessage( msg );
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 < m_minDistance2 )
{
wxString msg;
msg.Printf( " * AddOutlineSegment() rejected an arc with equivalent end points, %s\n",
aCurve->Describe() );
ReportMessage( msg );
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;
case CURVE_BEZIER:
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;
default:
// unexpected curve type
do
{
wxString msg;
msg.Printf( " * AddOutlineSegment() unsupported curve type: %d\n",
aCurve->m_form );
ReportMessage( msg );
} while( 0 );
return false;
}
return true;
}
// add a pad hole or slot
bool PCBMODEL::AddPadHole( const 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;
oln.SetMinSqDistance( m_minDistance2 );
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, TRIPLET aScale )
{
if( aFileName.empty() )
{
ReportMessage( wxString::Format( "no model defined for component %s\n", aRefDes ) );
return false;
}
ReportMessage( wxString::Format( "add component %s\n", aRefDes ) );
// first retrieve a label
TDF_Label lmodel;
if( !getModelLabel( aFileName, aScale, lmodel ) )
{
ReportMessage( wxString::Format( "no model for filename %s\n", aFileName ) );
return false;
}
// calculate the Location transform
TopLoc_Location toploc;
if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
{
ReportMessage( wxString::Format( "no location data for filename %s\n", aFileName ) );
return false;
}
// add the located sub-assembly
TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
if( llabel.IsNull() )
{
ReportMessage( wxString::Format( "could not add component with filename %s\n", aFileName ) );
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;
}
void PCBMODEL::SetMinDistance( double aDistance )
{
// m_minDistance2 keeps a squared distance value
m_minDistance2 = aDistance * aDistance;
BRepBuilderAPI::Precision( aDistance );
}
// 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;
ReportMessage( "no valid board outline\n" );
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.SetMinSqDistance( m_minDistance2 );
oln.AddSegment( *m_mincurve );
m_curves.erase( m_mincurve );
ReportMessage( wxString::Format( "Build board outline (%d items)\n",
(int)m_curves.size() ) );
while( !m_curves.empty() )
{
if( oln.IsClosed() )
{
if( board.IsNull() )
{
if( !oln.MakeShape( board, m_thickness ) )
{
ReportMessage( "could not create board extrusion\n" );
return false;
}
}
else
{
TopoDS_Shape hole;
if( oln.MakeShape( hole, m_thickness ) )
{
m_cutouts.push_back( hole );
}
else
{
ReportMessage( "could not create board cutout\n" );
}
}
oln.Clear();
if( !m_curves.empty() )
{
oln.AddSegment( m_curves.front() );
m_curves.pop_front();
}
continue;
}
std::list< KICADCURVE >::iterator sC = m_curves.begin();
bool added = false;
while( sC != m_curves.end() )
{
if( oln.AddSegment( *sC ) )
{
added = true;
m_curves.erase( sC );
break;
}
++sC;
}
if( !added && !oln.m_curves.empty() )
{
wxString msg;
msg.Printf( "could not close outline (dropping outline data with %d segments)\n",
static_cast<int>( oln.m_curves.size() ) );
for( const auto& c : oln.m_curves )
msg << " + " << c.Describe() << "\n";
ReportMessage( msg );
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 ) )
{
ReportMessage( "could not create board extrusion\n" );
return false;
}
}
else
{
TopoDS_Shape hole;
if( oln.MakeShape( hole, m_thickness ) )
{
m_cutouts.push_back( hole );
}
else
{
ReportMessage( "could not create board cutout\n" );
}
}
}
else
{
ReportMessage( "* could not create closed board outlines *\n" );
return false;
}
// subtract cutouts (if any)
if( m_cutouts.size() )
ReportMessage( wxString::Format( "Build board cutouts and holes (%d holes)\n",
(int)m_cutouts.size() ) );
#if 0 // First version for holes removing: very slow when having many (> 300) holes
// Substract holes (cutouts) can be time consuming, so display activity
// state to be sure there is no hang:
int char_count = 0;
int cur_count = 0;
int cntmax = m_cutouts.size();
for( auto hole : m_cutouts )
{
board = BRepAlgoAPI_Cut( board, hole );
cur_count++;
char_count++;
if( char_count < 80 )
ReportMessage( "." );
else
{
char_count = 0;
ReportMessage( wxString::Format( ". %d/%d\n", cur_count, cntmax ) );
}
}
#else // Much faster than first version: group all holes and cut only once
if( m_cutouts.size() )
{
BRepAlgoAPI_Cut Cut;
TopTools_ListOfShape mainbrd;
mainbrd.Append( board );
Cut.SetArguments( mainbrd );
TopTools_ListOfShape holelist;
for( auto hole : m_cutouts )
holelist.Append( hole );
Cut.SetTools( holelist );
Cut.Build();
board = Cut.Shape();
}
#endif
// push the board to the data structure
ReportMessage( "\nGenerate board full shape\n" );
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();
}
#if ( defined OCC_VERSION_HEX ) && ( OCC_VERSION_HEX > 0x070101 )
m_assy->UpdateAssemblies();
#endif
return true;
}
#ifdef SUPPORTS_IGES
// write the assembly model in IGES format
bool PCBMODEL::WriteIGES( const wxString& aFileName )
{
if( m_pcb_label.IsNull() )
{
ReportMessage( wxString::Format(
"No valid PCB assembly; cannot create output file %s\n", aFileName ) );
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().ToAscii() ) );
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 wxString& aFileName )
{
if( m_pcb_label.IsNull() )
{
ReportMessage( wxString::Format( "No valid PCB assembly; cannot create output file %s\n", aFileName ) );
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 );
// Note: use only Ascii7 chars, non Ascii7 chars (therefore UFT8 chars)
// are creating issues in the step file
hdr.SetName( new TCollection_HAsciiString( fn.GetFullName().ToAscii() ) );
// TODO: how to control and ensure consistency with IGES?
hdr.SetAuthorValue( 1, new TCollection_HAsciiString( "Pcbnew" ) );
hdr.SetOrganizationValue( 1, new TCollection_HAsciiString( "Kicad" ) );
hdr.SetOriginatingSystem( new TCollection_HAsciiString( "KiCad to STEP converter" ) );
hdr.SetDescriptionValue( 1, new TCollection_HAsciiString( "KiCad electronic assembly" ) );
bool success = true;
// Creates a temporary file with a ascii7 name, because writer does not know
// unicode filenames
wxString currCWD = wxGetCwd();
wxString workCWD =fn.GetPath();
if( !workCWD.IsEmpty() )
wxSetWorkingDirectory( workCWD );
char tmpfname[] = "$tempfile$.step";
if( Standard_False == writer.Write( tmpfname ) )
success = false;
if( success )
{
if( !wxRenameFile( tmpfname, fn.GetFullName(), true ) )
{
ReportMessage( wxString::Format( "Cannot rename temporary file '%s' to '%s'\n",
tmpfname, fn.GetFullName() ) );
success = false;
}
}
wxSetWorkingDirectory( currCWD );
return success;
}
bool PCBMODEL::getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_Label& aLabel )
{
std::string model_key = aFileName + "_" + std::to_string( aScale.x )
+ "_" + std::to_string( aScale.y ) + "_" + std::to_string( aScale.z );
MODEL_MAP::const_iterator mm = m_models.find( model_key );
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() ) )
{
ReportMessage( wxString::Format( "readIGES() failed on filename %s\n",
aFileName ) );
return false;
}
break;
case FMT_STEP:
if( !readSTEP( doc, aFileName.c_str() ) )
{
ReportMessage( wxString::Format( "readSTEP() failed on filename %s\n",
aFileName ) );
return false;
}
break;
case FMT_STEPZ:
{
wxFFileInputStream ifile( aFileName );
wxFileName outFile( aFileName );
outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
outFile.SetExt( "STEP" );
wxFileOffset size = ifile.GetLength();
if( size == wxInvalidOffset )
{
ReportMessage( wxString::Format( "readSTEP() failed on filename %s\n",
aFileName ) );
return false;
}
{
bool success = false;
wxFFileOutputStream ofile( outFile.GetFullPath() );
if( !ofile.IsOk() )
return false;
char *buffer = new char[size];
ifile.Read( buffer, size);
std::string expanded;
try
{
expanded = gzip::decompress( buffer, size );
success = true;
}
catch(...)
{}
if( expanded.empty() )
{
ifile.Reset();
ifile.SeekI( 0 );
wxZipInputStream izipfile( ifile );
std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
{
izipfile.Read( ofile );
success = true;
}
}
else
{
ofile.Write( expanded.data(), expanded.size() );
}
delete[] buffer;
ofile.Close();
return success;
}
return getModelLabel( outFile.GetFullPath().ToStdString(), aScale, aLabel );
break;
}
case FMT_WRL:
case FMT_WRZ:
/* WRL files are preferred for internal rendering,
* due to superior material properties, etc.
* However they are not suitable for MCAD export.
*
* If a .wrl file is specified, attempt to locate
* a replacement file for it.
*
* If a valid replacement file is found, the label
* for THAT file will be associated with the .wrl file
*
*/
{
wxFileName wrlName( aFileName );
wxString basePath = wrlName.GetPath();
wxString baseName = wrlName.GetName();
// List of alternate files to look for
// Given in order of preference
// (Break if match is found)
wxArrayString alts;
// Step files
alts.Add( "stp" );
alts.Add( "step" );
alts.Add( "STP" );
alts.Add( "STEP" );
alts.Add( "Stp" );
alts.Add( "Step" );
alts.Add( "stpz" );
alts.Add( "stpZ" );
alts.Add( "STPZ" );
alts.Add( "step.gz" );
// IGES files
alts.Add( "iges" );
alts.Add( "IGES" );
alts.Add( "igs" );
alts.Add( "IGS" );
//TODO - Other alternative formats?
for( const auto& alt : alts )
{
wxFileName altFile( basePath, baseName + "." + alt );
if( altFile.IsOk() && altFile.FileExists() )
{
std::string altFileName = altFile.GetFullPath().ToStdString();
if( getModelLabel( altFileName, aScale, aLabel ) )
{
return true;
}
}
}
}
break;
// TODO: implement IDF and EMN converters
default:
return false;
}
aLabel = transferModel( doc, m_doc, aScale );
if( aLabel.IsNull() )
{
ReportMessage( wxString::Format( "could not transfer model data from file %s\n", aFileName ) );
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( model_key, 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 board thickness
aOffset.z += 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, TRIPLET aScale )
{
// transfer data from Source into a top level component of Dest
gp_GTrsf scale_transform;
scale_transform.SetVectorialPart( gp_Mat( aScale.x, 0, 0,
0, aScale.y, 0,
0, 0, aScale.z ) );
BRepBuilderAPI_GTransform brep( scale_transform );
// 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() )
{
brep.Perform( shape, Standard_False );
TopoDS_Shape scaled_shape;
if ( brep.IsDone() )
{
scaled_shape = brep.Shape();
}
else
{
ReportMessage( " * transfertModel(): failed to scale model\n" );
scaled_shape = shape;
}
TDF_Label niulab = d_assy->AddComponent( component, scaled_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;
m_minDistance2 = MIN_LENGTH2;
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 < m_minDistance2 )
{
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 < m_minDistance2 )
{
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 < m_minDistance2 )
{
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 < m_minDistance2 )
{
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; // succeeded 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 )
{
bool success = false;
try
{
success = addEdge( &wire, i, lastPoint );
}
catch( const Standard_Failure& e )
{
ReportMessage( wxString::Format( "Exception caught: %s\n", e.GetMessageString() ) );
success = false;
}
if( !success )
{
ReportMessage( wxString::Format(
"failed to add an edge:\n%s\nlast valid outline point: %f %f\n",
i.Describe().c_str(), lastPoint.x, lastPoint.y ) );
return false;
}
}
TopoDS_Face face = BRepBuilderAPI_MakeFace( wire );
aShape = BRepPrimAPI_MakePrism( face, gp_Vec( 0, 0, aThickness ) );
if( aShape.IsNull() )
{
ReportMessage( "failed to create a prismatic shape\n" );
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:
{
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 );
}
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;
case CURVE_BEZIER:
{
TColgp_Array1OfPnt poles(0, 3);
gp_Pnt pt = gp_Pnt( aCurve.m_start.x, aCurve.m_start.y, 0.0 );
poles(0) = pt;
pt = gp_Pnt( aCurve.m_bezierctrl1.x, aCurve.m_bezierctrl1.y, 0.0 );
poles(1) = pt;
pt = gp_Pnt( aCurve.m_bezierctrl2.x, aCurve.m_bezierctrl2.y, 0.0 );
poles(2) = pt;
pt = gp_Pnt( endPoint.x, endPoint.y, 0.0 );
poles(3) = pt;
Geom_BezierCurve* bezier_curve = new Geom_BezierCurve( poles );
edge = BRepBuilderAPI_MakeEdge( bezier_curve );
}
break;
default:
ReportMessage( wxString::Format( "unsupported curve type: %d\n", aCurve.m_form ) );
return false;
}
if( edge.IsNull() )
return false;
aLastPoint = endPoint;
aWire->Add( edge );
if( BRepBuilderAPI_DisconnectedWire == aWire->Error() )
{
ReportMessage( "failed to add curve\n" );
return false;
}
return true;
}
bool OUTLINE::testClosed( const KICADCURVE& aFrontCurve, const 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 < m_minDistance2 )
return true;
return false;
}