1752 lines
50 KiB
C++
1752 lines
50 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>
|
|
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* 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/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 <TDataStd_TreeNode.hxx>
|
|
#include <TDF_LabelSequence.hxx>
|
|
#include <TDF_ChildIterator.hxx>
|
|
#include <TopExp_Explorer.hxx>
|
|
#include <XCAFDoc.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>
|
|
|
|
#include <macros.h>
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// 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( wxT( " * fileType(): no such file: %s\n" ),
|
|
wxString::FromUTF8Unchecked( aFileName ) );
|
|
|
|
ReportMessage( msg );
|
|
return FMT_NONE;
|
|
}
|
|
|
|
wxString ext = lfile.GetExt().Lower();
|
|
|
|
if( ext == wxT( "wrl" ) )
|
|
return FMT_WRL;
|
|
|
|
if( ext == wxT( "wrz" ) )
|
|
return FMT_WRZ;
|
|
|
|
if( ext == wxT( "idf" ) )
|
|
return FMT_IDF; // component outline
|
|
|
|
if( ext == wxT( "emn" ) )
|
|
return FMT_EMN; // PCB assembly
|
|
|
|
if( ext == wxT( "stpz" ) || ext == wxT( "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( const wxString& aPcbName )
|
|
{
|
|
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();
|
|
m_pcbName = aPcbName;
|
|
BRepBuilderAPI::Precision( MIN_DISTANCE );
|
|
}
|
|
|
|
|
|
PCBMODEL::~PCBMODEL()
|
|
{
|
|
m_doc->Close();
|
|
}
|
|
|
|
|
|
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( wxT( " * 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( wxT( " * 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 )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( " * AddOutlineSegment() rejected an arc "
|
|
"with equivalent end points, %s\n" ),
|
|
aCurve->Describe() ) );
|
|
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:
|
|
{
|
|
double dx = aCurve->m_start.x - aCurve->m_radius;
|
|
|
|
if( dx < m_minx )
|
|
{
|
|
m_minx = dx;
|
|
m_mincurve = --( m_curves.end() );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case CURVE_ARC:
|
|
{
|
|
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() );
|
|
}
|
|
|
|
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
|
|
{
|
|
wxString msg;
|
|
msg.Printf( wxT( " * AddOutlineSegment() unsupported curve type: %d\n" ),
|
|
aCurve->m_form );
|
|
ReportMessage( msg );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
bool PCBMODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
|
|
bool aBottom, DOUBLET aPosition, double aRotation,
|
|
TRIPLET aOffset, TRIPLET aOrientation, TRIPLET aScale,
|
|
bool aSubstituteModels )
|
|
{
|
|
if( aFileNameUTF8.empty() )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "No model defined for component %s.\n" ), aRefDes ) );
|
|
return false;
|
|
}
|
|
|
|
wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
|
|
ReportMessage( wxString::Format( wxT( "Add component %s.\n" ), aRefDes ) );
|
|
|
|
// first retrieve a label
|
|
TDF_Label lmodel;
|
|
wxString errorMessage;
|
|
|
|
if( !getModelLabel( aFileNameUTF8, aScale, lmodel, aSubstituteModels, &errorMessage ) )
|
|
{
|
|
if( errorMessage.IsEmpty() )
|
|
ReportMessage( wxString::Format( wxT( "No model for filename '%s'.\n" ), fileName ) );
|
|
else
|
|
ReportMessage( errorMessage );
|
|
|
|
return false;
|
|
}
|
|
|
|
// calculate the Location transform
|
|
TopLoc_Location toploc;
|
|
|
|
if( !getModelLocation( aBottom, aPosition, aRotation, aOffset, aOrientation, toploc ) )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "No location data for filename '%s'.\n" ),
|
|
fileName ) );
|
|
return false;
|
|
}
|
|
|
|
// add the located sub-assembly
|
|
TDF_Label llabel = m_assy->AddComponent( m_assy_label, lmodel, toploc );
|
|
|
|
if( llabel.IsNull() )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "Could not add component with filename '%s'.\n" ),
|
|
fileName ) );
|
|
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;
|
|
}
|
|
|
|
|
|
void PCBMODEL::SetBoardColor( double r, double g, double b )
|
|
{
|
|
m_boardColor[0] = r;
|
|
m_boardColor[1] = g;
|
|
m_boardColor[2] = b;
|
|
}
|
|
|
|
|
|
void PCBMODEL::SetMinDistance( double aDistance )
|
|
{
|
|
// m_minDistance2 keeps a squared distance value
|
|
m_minDistance2 = aDistance * aDistance;
|
|
BRepBuilderAPI::Precision( aDistance );
|
|
}
|
|
|
|
|
|
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( wxT( "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( wxT( "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( wxT( "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( wxT( "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( wxT( "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( wxT( "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( wxT( "Could not create board cutout.\n" ) );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReportMessage( wxT( "Could not create closed board outlines.\n" ) );
|
|
return false;
|
|
}
|
|
|
|
// subtract cutouts (if any)
|
|
if( m_cutouts.size() )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "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( wxT( "." ) );
|
|
}
|
|
else
|
|
{
|
|
char_count = 0;
|
|
ReportMessage( wxString::Format( wxT( ". %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( wxT( "\nGenerate board full shape.\n" ) );
|
|
|
|
// Dont expand the component or else coloring it gets hard
|
|
m_pcb_label = m_assy->AddComponent( m_assy_label, board, false );
|
|
|
|
if( m_pcb_label.IsNull() )
|
|
return false;
|
|
|
|
// AddComponent adds a label that has a reference (not a parent/child relation) to the real
|
|
// label. We need to extract that real label to name it for the STEP output cleanly
|
|
// Why are we trying to name the bare board? Because CAD tools like SolidWorks do fun things
|
|
// like "deduplicate" imported STEPs by swapping STEP assembly components with already
|
|
// identically named assemblies. So we want to avoid having the PCB be generally defaulted
|
|
// to "Component" or "Assembly".
|
|
Handle( TDataStd_TreeNode ) node;
|
|
|
|
if( m_pcb_label.FindAttribute( XCAFDoc::ShapeRefGUID(), node ) )
|
|
{
|
|
TDF_Label label = node->Father()->Label();
|
|
|
|
if( !label.IsNull() )
|
|
{
|
|
wxString pcbName = wxString::Format( wxT( "%s PCB" ), m_pcbName );
|
|
std::string pcbNameStdString( pcbName.ToUTF8() );
|
|
TCollection_ExtendedString partname( pcbNameStdString.c_str() );
|
|
TDataStd_Name::Set( label, partname );
|
|
}
|
|
}
|
|
|
|
// color the PCB
|
|
Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main () );
|
|
Quantity_Color color( m_boardColor[0], m_boardColor[1], m_boardColor[2], Quantity_TOC_RGB );
|
|
|
|
colorTool->SetColor( m_pcb_label, color, XCAFDoc_ColorSurf );
|
|
|
|
TopExp_Explorer topex;
|
|
topex.Init( m_assy->GetShape( m_pcb_label ), TopAbs_SOLID );
|
|
|
|
while( topex.More() )
|
|
{
|
|
colorTool->SetColor( topex.Current(), color, 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( wxT( "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
|
|
|
|
|
|
bool PCBMODEL::WriteSTEP( const wxString& aFileName )
|
|
{
|
|
if( m_pcb_label.IsNull() )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "No valid PCB assembly; cannot create output file "
|
|
"'%s'.\n" ),
|
|
aFileName ) );
|
|
return false;
|
|
}
|
|
|
|
wxFileName fn( aFileName );
|
|
|
|
STEPCAFControl_Writer writer;
|
|
writer.SetColorMode( Standard_True );
|
|
writer.SetNameMode( Standard_True );
|
|
|
|
// This must be set before we "transfer" the document.
|
|
// Should default to kicad_pcb.general.title_block.title,
|
|
// but in the meantime, defaulting to the basename of the output
|
|
// target is still better than "open cascade step translter v..."
|
|
// UTF8 should be ok from ISO 10303-21:2016, but... older stuff? use boring ascii
|
|
if( !Interface_Static::SetCVal( "write.step.product.name", fn.GetName().ToAscii() ) )
|
|
ReportMessage( wxT( "Failed to set step product name, but will attempt to continue." ) );
|
|
|
|
if( Standard_False == writer.Transfer( m_doc, STEPControl_AsIs ) )
|
|
return false;
|
|
|
|
APIHeaderSection_MakeHeader hdr( writer.ChangeWriter().Model() );
|
|
|
|
// 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( wxT( "Cannot rename temporary file '%s' to '%s'.\n" ),
|
|
tmpfname,
|
|
fn.GetFullName() ) );
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
wxSetWorkingDirectory( currCWD );
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool PCBMODEL::getModelLabel( const std::string& aFileNameUTF8, TRIPLET aScale, TDF_Label& aLabel,
|
|
bool aSubstituteModels, wxString* aErrorMessage )
|
|
{
|
|
std::string model_key = aFileNameUTF8 + "_" + 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 );
|
|
|
|
wxString fileName( wxString::FromUTF8( aFileNameUTF8.c_str() ) );
|
|
FormatType modelFmt = fileType( aFileNameUTF8.c_str() );
|
|
|
|
switch( modelFmt )
|
|
{
|
|
case FMT_IGES:
|
|
if( !readIGES( doc, aFileNameUTF8.c_str() ) )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "readIGES() failed on filename '%s'.\n" ),
|
|
fileName ) );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case FMT_STEP:
|
|
if( !readSTEP( doc, aFileNameUTF8.c_str() ) )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "readSTEP() failed on filename '%s'.\n" ),
|
|
fileName ) );
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case FMT_STEPZ:
|
|
{
|
|
// To export a compressed step file (.stpz or .stp.gz file), the best way is to
|
|
// decaompress it in a temporaty file and load this temporary file
|
|
wxFFileInputStream ifile( fileName );
|
|
wxFileName outFile( fileName );
|
|
|
|
outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
|
|
outFile.SetExt( wxT( "step" ) );
|
|
wxFileOffset size = ifile.GetLength();
|
|
|
|
if( size == wxInvalidOffset )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "getModelLabel() failed on filename '%s'.\n" ),
|
|
fileName ) );
|
|
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( ... )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "failed to decompress '%s'.\n" ),
|
|
fileName ) );
|
|
}
|
|
|
|
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();
|
|
|
|
if( success )
|
|
{
|
|
std::string altFileNameUTF8 = TO_UTF8( outFile.GetFullPath() );
|
|
success = getModelLabel( altFileNameUTF8, TRIPLET( 1.0, 1.0, 1.0 ), aLabel, false );
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
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
|
|
*/
|
|
if( aSubstituteModels )
|
|
{
|
|
wxFileName wrlName( fileName );
|
|
|
|
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( wxT( "stp" ) );
|
|
alts.Add( wxT( "step" ) );
|
|
alts.Add( wxT( "STP" ) );
|
|
alts.Add( wxT( "STEP" ) );
|
|
alts.Add( wxT( "Stp" ) );
|
|
alts.Add( wxT( "Step" ) );
|
|
alts.Add( wxT( "stpz" ) );
|
|
alts.Add( wxT( "stpZ" ) );
|
|
alts.Add( wxT( "STPZ" ) );
|
|
alts.Add( wxT( "step.gz" ) );
|
|
alts.Add( wxT( "stp.gz" ) );
|
|
|
|
// IGES files
|
|
alts.Add( wxT( "iges" ) );
|
|
alts.Add( wxT( "IGES" ) );
|
|
alts.Add( wxT( "igs" ) );
|
|
alts.Add( wxT( "IGS" ) );
|
|
|
|
//TODO - Other alternative formats?
|
|
|
|
for( const auto& alt : alts )
|
|
{
|
|
wxFileName altFile( basePath, baseName + wxT( "." ) + alt );
|
|
|
|
if( altFile.IsOk() && altFile.FileExists() )
|
|
{
|
|
std::string altFileNameUTF8 = TO_UTF8( altFile.GetFullPath() );
|
|
|
|
// When substituting a STEP/IGS file for VRML, do not apply the VRML scaling
|
|
// to the new STEP model. This process of auto-substitution is janky as all
|
|
// heck so let's not mix up un-displayed scale factors with potentially
|
|
// mis-matched files. And hope that the user doesn't have multiples files
|
|
// named "model.wrl" and "model.stp" referring to different parts.
|
|
// TODO: Fix model handling in v7. Default models should only be STP.
|
|
// Have option to override this in DISPLAY.
|
|
if( getModelLabel( altFileNameUTF8, TRIPLET( 1.0, 1.0, 1.0 ), aLabel, false ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // No replacement model found
|
|
}
|
|
else // Substitution is not allowed
|
|
{
|
|
if( aErrorMessage )
|
|
aErrorMessage->Printf( wxT( "Cannot add a VRML model to a STEP file.\n" ) );
|
|
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
|
|
// TODO: implement IDF and EMN converters
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
aLabel = transferModel( doc, m_doc, aScale );
|
|
|
|
if( aLabel.IsNull() )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "Could not transfer model data from file '%s'.\n" ),
|
|
fileName ) );
|
|
return false;
|
|
}
|
|
|
|
// attach the PART NAME ( base filename: note that in principle
|
|
// different models may have the same base filename )
|
|
wxFileName afile( fileName );
|
|
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() )
|
|
{
|
|
TopoDS_Shape scaled_shape( shape );
|
|
|
|
if( aScale.x != 1.0 || aScale.y != 1.0 || aScale.z != 1.0 )
|
|
{
|
|
brep.Perform( shape, Standard_False );
|
|
|
|
if( brep.IsDone() )
|
|
{
|
|
scaled_shape = brep.Shape();
|
|
}
|
|
else
|
|
{
|
|
ReportMessage( wxT( " * 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;
|
|
}
|
|
|
|
|
|
OUTLINE::~OUTLINE()
|
|
{
|
|
}
|
|
|
|
|
|
void OUTLINE::Clear()
|
|
{
|
|
m_closed = false;
|
|
m_curves.clear();
|
|
}
|
|
|
|
|
|
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( KICADCURVE& i : m_curves )
|
|
{
|
|
bool success = false;
|
|
|
|
try
|
|
{
|
|
success = addEdge( &wire, i, lastPoint );
|
|
}
|
|
catch( const Standard_Failure& e )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "Exception caught: %s\n" ),
|
|
e.GetMessageString() ) );
|
|
success = false;
|
|
}
|
|
|
|
if( !success )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "failed to add edge: %s\n"
|
|
"last 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( wxT( "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( wxT( "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( wxT( "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;
|
|
}
|