mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-15 02:33:15 +02:00
Software like Solidworks and other CAD treat STEP labels as unique entries. When you import multi STEP files into the same project, it'll start deduplicating your design by those name. So two completely unrelated PCBs with the same "PCB" name will result in one being replaced by another.
1272 lines
41 KiB
C++
1272 lines
41 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
|
|
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
|
|
* Copyright (C) 2016-2023 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 <footprint.h>
|
|
#include <pad.h>
|
|
|
|
#include "step_pcb_model.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;
|
|
|
|
// nominal offset from the board
|
|
static constexpr double BOARD_OFFSET = 0.05;
|
|
|
|
// supported file types for 3D models
|
|
enum MODEL3D_FORMAT_TYPE
|
|
{
|
|
FMT_NONE,
|
|
FMT_STEP,
|
|
FMT_STEPZ,
|
|
FMT_IGES,
|
|
FMT_EMN,
|
|
FMT_IDF,
|
|
FMT_WRL,
|
|
FMT_WRZ
|
|
};
|
|
|
|
|
|
MODEL3D_FORMAT_TYPE 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;
|
|
}
|
|
|
|
|
|
STEP_PCB_MODEL::STEP_PCB_MODEL( 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_boardThickness = BOARD_THICKNESS_DEFAULT_MM;
|
|
m_copperThickness = COPPER_THICKNESS_DEFAULT_MM;
|
|
m_mergeOCCMaxDist = OCC_MAX_DISTANCE_TO_MERGE_POINTS;
|
|
m_minx = 1.0e10; // absurdly large number; any valid PCB X value will be smaller
|
|
m_pcbName = aPcbName;
|
|
BRepBuilderAPI::Precision( m_mergeOCCMaxDist );
|
|
m_maxError = pcbIUScale.mmToIU( ARC_TO_SEGMENT_MAX_ERROR_MM );
|
|
}
|
|
|
|
|
|
STEP_PCB_MODEL::~STEP_PCB_MODEL()
|
|
{
|
|
m_doc->Close();
|
|
}
|
|
|
|
bool STEP_PCB_MODEL::AddPadShape( const PAD* aPad, const VECTOR2D& aOrigin )
|
|
{
|
|
const std::shared_ptr<SHAPE_POLY_SET>& pad_shape = aPad->GetEffectivePolygon();
|
|
bool success = true;
|
|
VECTOR2I pos = aPad->GetPosition();
|
|
|
|
for( PCB_LAYER_ID pcb_layer = F_Cu; ; pcb_layer = B_Cu )
|
|
{
|
|
TopoDS_Shape curr_shape;
|
|
double Zpos = pcb_layer == F_Cu ? m_boardThickness : -m_copperThickness;
|
|
|
|
if( aPad->IsOnLayer( pcb_layer ) )
|
|
{
|
|
// Make a shape on top/bottom copper layer: a cylinder for rond shapes (pad or via)
|
|
// and a polygon for other shapes:
|
|
if( aPad->GetShape() == PAD_SHAPE::CIRCLE )
|
|
{
|
|
curr_shape = BRepPrimAPI_MakeCylinder(
|
|
pcbIUScale.IUTomm( aPad->GetSizeX() ) * 0.5, m_copperThickness ).Shape();
|
|
gp_Trsf shift;
|
|
shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( pos.x - aOrigin.x ),
|
|
-pcbIUScale.IUTomm( pos.y - aOrigin.y ),
|
|
Zpos ) );
|
|
BRepBuilderAPI_Transform round_shape( curr_shape, shift );
|
|
m_board_outlines.push_back( round_shape.Shape() );
|
|
}
|
|
else
|
|
{
|
|
success = MakeShape( curr_shape, pad_shape.get()->COutline(0), m_copperThickness, Zpos, aOrigin );
|
|
|
|
if( success )
|
|
m_board_outlines.push_back( curr_shape );
|
|
}
|
|
}
|
|
|
|
if( pcb_layer == B_Cu )
|
|
break;
|
|
}
|
|
|
|
if( !success ) // Error
|
|
ReportMessage( wxT( "OCC error adding pad/via polygon.\n" ) );
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool STEP_PCB_MODEL::AddCopperPolygonShapes( const SHAPE_POLY_SET* aPolyShapes, bool aOnTop, const VECTOR2D& aOrigin )
|
|
{
|
|
bool success = true;
|
|
|
|
for( int ii = 0; ii < aPolyShapes->OutlineCount(); ii++ )
|
|
{
|
|
TopoDS_Shape copper_shape;
|
|
double z_pos = aOnTop ? m_boardThickness : -m_copperThickness;
|
|
|
|
if( MakeShape( copper_shape, aPolyShapes->COutline( ii ), m_copperThickness, z_pos, aOrigin ) )
|
|
{
|
|
m_board_outlines.push_back( copper_shape );
|
|
}
|
|
else
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "Could not add shape (%d points) to copper layer on %s.\n" ),
|
|
aPolyShapes->COutline( ii ).PointCount(),
|
|
aOnTop ? wxT( "top" ) : wxT( "bottom" ) ) );
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool STEP_PCB_MODEL::AddPadHole( const PAD* aPad, const VECTOR2D& aOrigin )
|
|
{
|
|
if( aPad == nullptr || !aPad->GetDrillSize().x )
|
|
return false;
|
|
|
|
VECTOR2I pos = aPad->GetPosition();
|
|
const double margin = 0.01; // a small margin on the Z axix to be sure the hole
|
|
// is bigget than the board with copper
|
|
// must be > OCC_MAX_DISTANCE_TO_MERGE_POINTS
|
|
double holeZsize = m_boardThickness + ( m_copperThickness * 2 ) + ( margin * 2 );
|
|
|
|
if( aPad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
|
|
{
|
|
TopoDS_Shape s =
|
|
BRepPrimAPI_MakeCylinder( pcbIUScale.IUTomm( aPad->GetDrillSize().x ) * 0.5, holeZsize ).Shape();
|
|
gp_Trsf shift;
|
|
shift.SetTranslation( gp_Vec( pcbIUScale.IUTomm( pos.x - aOrigin.x ),
|
|
-pcbIUScale.IUTomm( pos.y - aOrigin.y ),
|
|
-m_copperThickness - margin ) );
|
|
BRepBuilderAPI_Transform hole( s, shift );
|
|
m_cutouts.push_back( hole.Shape() );
|
|
return true;
|
|
}
|
|
|
|
// slotted hole
|
|
SHAPE_POLY_SET holeOutlines;
|
|
|
|
if( !aPad->TransformHoleToPolygon( holeOutlines, 0, m_maxError, ERROR_INSIDE ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TopoDS_Shape hole;
|
|
|
|
if( holeOutlines.OutlineCount() > 0 )
|
|
{
|
|
if( MakeShape( hole, holeOutlines.COutline( 0 ), holeZsize, -m_copperThickness - margin, aOrigin ) )
|
|
{
|
|
m_cutouts.push_back( hole );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool STEP_PCB_MODEL::AddComponent( const std::string& aFileNameUTF8, const std::string& aRefDes,
|
|
bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset,
|
|
VECTOR3D aOrientation, VECTOR3D 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 STEP_PCB_MODEL::SetPCBThickness( double aThickness )
|
|
{
|
|
if( aThickness < 0.0 )
|
|
m_boardThickness = BOARD_THICKNESS_DEFAULT_MM;
|
|
else if( aThickness < BOARD_THICKNESS_MIN_MM )
|
|
m_boardThickness = BOARD_THICKNESS_MIN_MM;
|
|
else
|
|
m_boardThickness = aThickness;
|
|
}
|
|
|
|
|
|
void STEP_PCB_MODEL::SetBoardColor( double r, double g, double b )
|
|
{
|
|
m_boardColor[0] = r;
|
|
m_boardColor[1] = g;
|
|
m_boardColor[2] = b;
|
|
}
|
|
|
|
|
|
void STEP_PCB_MODEL::SetCopperColor( double r, double g, double b )
|
|
{
|
|
m_copperColor[0] = r;
|
|
m_copperColor[1] = g;
|
|
m_copperColor[2] = b;
|
|
}
|
|
|
|
|
|
void STEP_PCB_MODEL::OCCSetMergeMaxDistance( double aDistance )
|
|
{
|
|
// Ensure a minimal value (in mm)
|
|
m_mergeOCCMaxDist = aDistance;
|
|
BRepBuilderAPI::Precision( m_mergeOCCMaxDist );
|
|
}
|
|
|
|
bool STEP_PCB_MODEL::isBoardOutlineValid()
|
|
{
|
|
return m_pcb_labels.size() > 0;
|
|
}
|
|
|
|
|
|
bool STEP_PCB_MODEL::MakeShape( TopoDS_Shape& aShape, const SHAPE_LINE_CHAIN& aChain,
|
|
double aThickness, double aZposition, const VECTOR2D& aOrigin )
|
|
{
|
|
if( !aShape.IsNull() )
|
|
return false; // there is already data in the shape object
|
|
|
|
if( !aChain.IsClosed() )
|
|
return false; // the loop is not closed
|
|
|
|
BRepBuilderAPI_MakeWire wire;
|
|
bool success = true;
|
|
|
|
gp_Pnt start = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( 0 ).x - aOrigin.x ),
|
|
-pcbIUScale.IUTomm( aChain.CPoint( 0 ).y - aOrigin.y ), aZposition );
|
|
|
|
int items_count = 0;
|
|
|
|
for( int j = 0; j < aChain.PointCount(); j++ )
|
|
{
|
|
int next = j+1;
|
|
|
|
gp_Pnt end;
|
|
|
|
if( next >= aChain.PointCount() )
|
|
next = 0;
|
|
|
|
end = gp_Pnt( pcbIUScale.IUTomm( aChain.CPoint( next ).x - aOrigin.x ),
|
|
-pcbIUScale.IUTomm( aChain.CPoint( next ).y - aOrigin.y ), aZposition );
|
|
|
|
// Do not export too short segments: they create broken shape because OCC thinks
|
|
// start point and end point are at the same place
|
|
double seg_len = std::hypot( end.X() - start.X(), end.Y() - start.Y() );
|
|
|
|
if( seg_len <= m_mergeOCCMaxDist )
|
|
continue;
|
|
try
|
|
{
|
|
TopoDS_Edge edge;
|
|
edge = BRepBuilderAPI_MakeEdge( start, end );
|
|
wire.Add( edge );
|
|
|
|
if( wire.Error() != BRepBuilderAPI_WireDone )
|
|
{
|
|
ReportMessage( wxT( "failed to add curve\n" ) );
|
|
return false;
|
|
}
|
|
|
|
items_count++;
|
|
}
|
|
catch( const Standard_Failure& e )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "MakeShape1: OCC exception: %s\n" ),
|
|
e.GetMessageString() ) );
|
|
success = false;
|
|
}
|
|
|
|
if( !success )
|
|
{
|
|
ReportMessage( wxS( "failed to add edge\n" ) );
|
|
return false;
|
|
}
|
|
|
|
start = end;
|
|
}
|
|
|
|
BRepBuilderAPI_MakeFace face;
|
|
|
|
try
|
|
{
|
|
face = BRepBuilderAPI_MakeFace( wire );
|
|
}
|
|
catch( const Standard_Failure& e )
|
|
{
|
|
ReportMessage(
|
|
wxString::Format( wxT( "MakeShape2 (items_count %d): OCC exception: %s\n" ),
|
|
items_count, e.GetMessageString() ) );
|
|
return false;
|
|
}
|
|
|
|
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 STEP_PCB_MODEL::CreatePCB( SHAPE_POLY_SET& aOutline, VECTOR2D aOrigin )
|
|
{
|
|
if( m_hasPCB )
|
|
{
|
|
if( !isBoardOutlineValid() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
m_hasPCB = true; // whether or not operations fail we note that CreatePCB has been invoked
|
|
|
|
// Number of items having the copper color
|
|
int copper_item_count = m_board_outlines.size();
|
|
|
|
// Support for more than one main outline (more than one board)
|
|
for( int cnt = 0; cnt < aOutline.OutlineCount(); cnt++ )
|
|
{
|
|
const SHAPE_LINE_CHAIN& outline = aOutline.COutline( cnt );
|
|
|
|
ReportMessage( wxString::Format( wxT( "Build board main outline %d with %d points.\n" ),
|
|
cnt+1, outline.PointCount() ) );
|
|
|
|
TopoDS_Shape curr_brd;
|
|
|
|
if( !MakeShape( curr_brd, outline, m_boardThickness, 0.0, aOrigin ) )
|
|
{
|
|
// Error
|
|
ReportMessage( wxString::Format(
|
|
wxT( "OCC error adding main outline polygon %d with %d points.\n" ),
|
|
cnt+1, outline.PointCount() ) );
|
|
}
|
|
else
|
|
m_board_outlines.push_back( curr_brd );
|
|
|
|
// Generate board cutouts in current main outline:
|
|
if( aOutline.HoleCount( cnt ) > 0 )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "Add cutouts in outline %d (%d cutout(s)).\n" ),
|
|
cnt+1, aOutline.HoleCount( cnt ) ) );
|
|
}
|
|
|
|
for( int ii = 0; ii < aOutline.HoleCount( cnt ); ii++ )
|
|
{
|
|
const SHAPE_LINE_CHAIN& holeOutline = aOutline.Hole( cnt, ii );
|
|
TopoDS_Shape hole;
|
|
|
|
if( MakeShape( hole, holeOutline, m_boardThickness + (m_copperThickness*4),
|
|
-m_copperThickness*2, aOrigin ) )
|
|
{
|
|
m_cutouts.push_back( hole );
|
|
}
|
|
}
|
|
}
|
|
|
|
// subtract cutouts (if any)
|
|
if( m_cutouts.size() )
|
|
{
|
|
ReportMessage( wxString::Format( wxT( "Build board cutouts and holes (%d hole(s)).\n" ),
|
|
(int) m_cutouts.size() ) );
|
|
|
|
TopTools_ListOfShape holelist;
|
|
|
|
for( TopoDS_Shape& hole : m_cutouts )
|
|
holelist.Append( hole );
|
|
|
|
// Remove holes for each item (board body or bodies, one can have more than one board)
|
|
// and copper items (copper_item_count items)
|
|
int cnt = 0;
|
|
for( TopoDS_Shape& board: m_board_outlines )
|
|
{
|
|
cnt++;
|
|
|
|
if( cnt % 10 == 0 )
|
|
ReportMessage( wxString::Format( wxT( "added %d/%d shapes\n" ),
|
|
cnt, (int)m_board_outlines.size() ) );
|
|
|
|
BRepAlgoAPI_Cut Cut;
|
|
TopTools_ListOfShape mainbrd;
|
|
|
|
mainbrd.Append( board );
|
|
|
|
Cut.SetArguments( mainbrd );
|
|
|
|
Cut.SetTools( holelist );
|
|
Cut.Build();
|
|
|
|
board = Cut.Shape();
|
|
}
|
|
}
|
|
|
|
// push the board to the data structure
|
|
ReportMessage( wxT( "\nGenerate board full shape.\n" ) );
|
|
|
|
// Dont expand the component or else coloring it gets hard
|
|
for( TopoDS_Shape& board: m_board_outlines )
|
|
{
|
|
m_pcb_labels.push_back( m_assy->AddComponent( m_assy_label, board, false ) );
|
|
|
|
if( m_pcb_labels.back().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".
|
|
|
|
// Init colors for the board body and the copper items (if any)
|
|
Handle( XCAFDoc_ColorTool ) colorTool = XCAFDoc_DocumentTool::ColorTool( m_doc->Main() );
|
|
Quantity_Color board_color( m_boardColor[0], m_boardColor[1], m_boardColor[2],
|
|
Quantity_TOC_RGB );
|
|
Quantity_Color copper_color( m_copperColor[0], m_copperColor[1], m_copperColor[2],
|
|
Quantity_TOC_RGB );
|
|
|
|
int pcbIdx = 1;
|
|
int copper_objects_cnt = 0;
|
|
|
|
for( TDF_Label& pcb_label : m_pcb_labels )
|
|
{
|
|
Handle( TDataStd_TreeNode ) node;
|
|
|
|
if( pcb_label.FindAttribute( XCAFDoc::ShapeRefGUID(), node ) )
|
|
{
|
|
// Gives a name to each board object
|
|
TDF_Label label = node->Father()->Label();
|
|
|
|
if( !label.IsNull() )
|
|
{
|
|
wxString pcbName;
|
|
|
|
if( copper_objects_cnt < copper_item_count )
|
|
{
|
|
pcbName = wxString::Format( wxT( "Copper_Item%d" ), copper_objects_cnt+1 );
|
|
}
|
|
else
|
|
{
|
|
// Note, we include the pcb/project name as a prefix
|
|
// because several STEP importing CAD software like SolidWorks
|
|
// will deduplicate anything imported by it's STEP name
|
|
if( m_pcb_labels.size() == 1 )
|
|
pcbName = wxString::Format( wxT( "%s PCB" ), m_pcbName );
|
|
else
|
|
pcbName = wxString::Format( wxT( "%s PCB%d" ), m_pcbName, pcbIdx++ );
|
|
}
|
|
|
|
std::string pcbNameStdString( pcbName.ToUTF8() );
|
|
TCollection_ExtendedString partname( pcbNameStdString.c_str() );
|
|
TDataStd_Name::Set( label, partname );
|
|
}
|
|
}
|
|
|
|
// color the PCB
|
|
TopExp_Explorer topex;
|
|
topex.Init( m_assy->GetShape( pcb_label ), TopAbs_SOLID );
|
|
|
|
while( topex.More() )
|
|
{
|
|
// First objects are copper objects, last(s) is the board body
|
|
if( copper_objects_cnt < copper_item_count )
|
|
colorTool->SetColor( topex.Current(), copper_color, XCAFDoc_ColorSurf );
|
|
else
|
|
colorTool->SetColor( topex.Current(), board_color, XCAFDoc_ColorSurf );
|
|
|
|
topex.Next();
|
|
}
|
|
|
|
copper_objects_cnt++;
|
|
}
|
|
|
|
#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 STEP_PCB_MODEL::WriteIGES( const wxString& aFileName )
|
|
{
|
|
if( !isBoardOutlineValid() )
|
|
{
|
|
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 STEP_PCB_MODEL::WriteSTEP( const wxString& aFileName )
|
|
{
|
|
if( !isBoardOutlineValid() )
|
|
{
|
|
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 STEP_PCB_MODEL::getModelLabel( const std::string& aFileNameUTF8, VECTOR3D 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() ) );
|
|
MODEL3D_FORMAT_TYPE 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, VECTOR3D( 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, VECTOR3D( 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 STEP_PCB_MODEL::getModelLocation( bool aBottom, VECTOR2D aPosition, double aRotation, VECTOR3D aOffset, VECTOR3D 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_boardThickness;
|
|
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 STEP_PCB_MODEL::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 STEP_PCB_MODEL::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 STEP_PCB_MODEL::transferModel( Handle( TDocStd_Document )& source,
|
|
Handle( TDocStd_Document )& dest, VECTOR3D 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;
|
|
} |