KiCad2Step code cleaning.

This commit is contained in:
Wayne Stambaugh 2021-07-29 07:10:58 -04:00
parent 934b6e4cdc
commit d36fbb864f
9 changed files with 662 additions and 687 deletions

View File

@ -32,6 +32,11 @@
#include "pluginldr.h"
/**
* Flag to enable plugin loader trace output.
*
* @ingroup trace_env_vars
*/
const wxChar* const tracePluginLoader = wxT( "KICAD_PLUGIN_LOADER" );

View File

@ -44,8 +44,8 @@ class KICAD2MCAD_APP : public wxApp
public:
KICAD2MCAD_APP() :
wxApp(),
m_frame( nullptr ),
m_Panel( nullptr )
m_Panel( nullptr ),
m_frame( nullptr )
{}
virtual bool OnInit() override;
@ -53,24 +53,24 @@ public:
virtual void OnInitCmdLine(wxCmdLineParser& parser) override;
virtual bool OnCmdLineParsed(wxCmdLineParser& parser) override;
PANEL_KICAD2STEP* m_Panel;
private:
KICAD2STEP_FRAME* m_frame;
KICAD2MCAD_PRMS m_params;
public:
PANEL_KICAD2STEP* m_Panel;
};
wxIMPLEMENT_APP( KICAD2MCAD_APP );
class KICAD2STEP_FRAME : public KICAD2STEP_FRAME_BASE
{
public:
KICAD2STEP_FRAME( const wxString& title );
private:
};
KICAD2MCAD_PRMS::KICAD2MCAD_PRMS()
{
#ifdef SUPPORTS_IGES
@ -95,18 +95,19 @@ void ReportMessage( const wxString& aMessage )
}
static const wxCmdLineEntryDesc cmdLineDesc[] =
{
{ wxCMD_LINE_PARAM, NULL, NULL, _( "pcb_filename" ).mb_str(),
wxCMD_LINE_VAL_STRING, wxCMD_LINE_OPTION_MANDATORY },
static const wxCmdLineEntryDesc cmdLineDesc[] = {
{ wxCMD_LINE_PARAM, NULL, NULL, _( "pcb_filename" ).mb_str(), wxCMD_LINE_VAL_STRING,
wxCMD_LINE_OPTION_MANDATORY },
{ wxCMD_LINE_OPTION, "o", "output-filename", _( "output filename" ).mb_str(),
wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
#ifdef SUPPORTS_IGES
{ wxCMD_LINE_SWITCH, "fmt-iges", NULL, _( "IGES output (default STEP)" ).mb_str(),
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
#endif
{ wxCMD_LINE_SWITCH, "f", "force", _( "overwrite output file" ).mb_str(),
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_SWITCH, "f", "force", _( "overwrite output file" ).mb_str(), wxCMD_LINE_VAL_NONE,
wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_SWITCH, NULL, "drill-origin", _( "Use Drill Origin for output origin" ).mb_str(),
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_SWITCH, NULL, "grid-origin", _( "Use Grid Origin for output origin" ).mb_str(),
@ -121,10 +122,11 @@ static const wxCmdLineEntryDesc cmdLineDesc[] =
_( "Substitute STEP or IGS models with the same name in place of VRML models" ).mb_str(),
wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_OPTION, NULL, "min-distance",
_( "Minimum distance between points to treat them as separate ones (default 0.01 mm)" ).mb_str(),
_( "Minimum distance between points to treat them as separate ones (default 0.01 mm)" )
.mb_str(),
wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
{ wxCMD_LINE_SWITCH, "h", NULL, _( "display this message" ).mb_str(),
wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
{ wxCMD_LINE_SWITCH, "h", NULL, _( "display this message" ).mb_str(), wxCMD_LINE_VAL_NONE,
wxCMD_LINE_OPTION_HELP },
{ wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 }
};
@ -157,8 +159,8 @@ int KICAD2MCAD_APP::OnRun()
}
KICAD2STEP_FRAME::KICAD2STEP_FRAME(const wxString& title)
: KICAD2STEP_FRAME_BASE(NULL, wxID_ANY, title)
KICAD2STEP_FRAME::KICAD2STEP_FRAME( const wxString& title ) :
KICAD2STEP_FRAME_BASE( NULL, wxID_ANY, title )
{
}
@ -167,7 +169,6 @@ void KICAD2MCAD_APP::OnInitCmdLine( wxCmdLineParser& parser )
{
parser.SetDesc( cmdLineDesc );
parser.SetSwitchChars( "-" );
return;
}
@ -265,7 +266,6 @@ bool KICAD2MCAD_APP::OnCmdLineParsed( wxCmdLineParser& parser )
}
}
if( parser.Found( "min-distance", &tstr ) )
{
std::istringstream istr;
@ -319,8 +319,9 @@ bool KICAD2MCAD_APP::OnCmdLineParsed( wxCmdLineParser& parser )
class STREAMBUF_SWAPPER
{
public:
STREAMBUF_SWAPPER( std::ostream & orig, std::ostream & replacement )
: m_buf( orig.rdbuf() ), m_str( orig )
STREAMBUF_SWAPPER( std::ostream& orig, std::ostream& replacement ) :
m_buf( orig.rdbuf() ),
m_str( orig )
{
orig.rdbuf( replacement.rdbuf() );
}
@ -342,7 +343,7 @@ int PANEL_KICAD2STEP::RunConverter()
if( !fname.FileExists() )
{
wxMessageBox( wxString::Format( "No such file: %s", m_params.m_filename ) );
wxMessageBox( wxString::Format( _( "No such file: %s" ), m_params.m_filename ) );
return -1;
}
@ -365,8 +366,8 @@ int PANEL_KICAD2STEP::RunConverter()
if( out_fname.FileExists() && !m_params.m_overwrite )
{
ReportMessage( "** Output already exists.\n"
"Enable the force overwrite flag to overwrite it." );
ReportMessage( _( "** Output already exists.\n"
"Enable the force overwrite flag to overwrite it." ) );
return -1;
}
@ -376,7 +377,7 @@ int PANEL_KICAD2STEP::RunConverter()
pcb.SetOrigin( m_params.m_xOrigin, m_params.m_yOrigin );
pcb.SetMinDistance( m_params.m_minDistance );
ReportMessage( wxString::Format( "Read: %s\n", m_params.m_filename ) );
ReportMessage( wxString::Format( _( "Read file: '%s'\n" ), m_params.m_filename ) );
// create the new streams to "redirect" cout and cerr output to
// msgs_from_opencascade and errors_from_opencascade
@ -397,17 +398,17 @@ int PANEL_KICAD2STEP::RunConverter()
try
{
ReportMessage( "Build STEP data\n" );
ReportMessage( _( "Build STEP data\n" ) );
res = pcb.ComposePCB( m_params.m_includeVirtual, m_params.m_substModels );
if( !res )
{
ReportMessage( "\n**Error building STEP board model. Abort export **\n" );
ReportMessage( _( "\n**Error building STEP board model. Abort export **\n" ) );
return -1;
}
ReportMessage( "Write STEP file\n" );
ReportMessage( _( "Write STEP file\n" ) );
#ifdef SUPPORTS_IGES
if( m_fmtIGES )
@ -418,23 +419,23 @@ int PANEL_KICAD2STEP::RunConverter()
if( !res )
{
ReportMessage( "\nError Write STEP file\n" );
ReportMessage( _( "\nError Write STEP file\n" ) );
return -1;
}
}
catch( const Standard_Failure& e )
{
wxString err = e.GetMessageString();
wxMessageBox( err, "Export Error" );
wxMessageBox( err, _( "Export Error" ) );
ReportMessage( wxString::Format( "\nExport Error: %s\n", err ) );
ReportMessage( "\n*** Abort export ***\n" );
ReportMessage( wxString::Format( _( "\nExport Error: %s\n" ), err ) );
ReportMessage( _( "\n*** Abort export ***\n" ) );
return -1;
}
catch( ... )
{
wxMessageBox( "(no exception information)", "Unknown error" );
ReportMessage( "\nUnknown error\n*** Abort export ***\n" );
wxMessageBox( _( "(no exception information)" ), _( "Unknown error" ) );
ReportMessage( _( "\nUnknown error\n*** Abort export ***\n" ) );
return -1;
}
}
@ -443,7 +444,7 @@ int PANEL_KICAD2STEP::RunConverter()
msgs << msgs_from_opencascade.str();
ReportMessage( msgs );
ReportMessage( wxString::Format( "\nStep file %s created\n\n", outfile ) );
ReportMessage( wxString::Format( _( "\nStep file '%s' created\n\n" ), outfile ) );
errs << errors_from_opencascade.str();
ReportMessage( errs );
@ -456,17 +457,17 @@ int PANEL_KICAD2STEP::RunConverter()
{
if( !success )
{
msg = "Unable to create STEP file.\n"
"Check that the board has a valid outline and models.";
msg = _( "Unable to create STEP file.\n"
"Check that the board has a valid outline and models." );
}
else
{
msg = "STEP file has been created, but there are warnings.";
msg = _( "STEP file has been created, but there are warnings." );
}
}
else // No error messages: the file is expected OK
{
msg.Printf( "STEP file:\n%s\nhas been created successfully.", outfile );
msg.Printf( _( "STEP file:\n%s\nhas been created successfully." ), outfile );
}
ReportMessage( msg );

View File

@ -2,7 +2,7 @@
* This program source code file is part kicad2mcad
*
* Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2020-2021 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
@ -49,12 +49,21 @@
#define ERRFLG_RELPATH (2)
#define ERRFLG_ENVPATH (4)
#define MASK_3D_RESOLVER "3D_RESOLVER"
/**
* Flag to enable plugin loader trace output.
*
* @ingroup trace_env_vars
*/
const wxChar* const trace3dResolver = wxT( "KICAD_3D_RESOLVER" );
static std::mutex mutex3D_resolver;
static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
S3D_RESOLVER::S3D_RESOLVER()
{
m_errflags = 0;
@ -116,15 +125,9 @@ bool S3D_RESOLVER::SetProjectDir( const wxString& aProjDir, bool* flgChanged )
}
}
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * [INFO] changed project dir to ";
ostr << m_Paths.front().m_Pathexp.ToUTF8();
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * [INFO] changed project dir to '%s'" ),
__FILE__, __FUNCTION__, __LINE__, m_Paths.front().m_Pathexp );
return true;
}
@ -244,15 +247,10 @@ bool S3D_RESOLVER::createPathList( void )
return false;
#ifdef DEBUG
wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
std::list< SEARCH_PATH >::const_iterator sPL = m_Paths.begin();
std::list< SEARCH_PATH >::const_iterator ePL = m_Paths.end();
wxLogTrace( trace3dResolver, " * [3D model] search paths:\n" );
while( sPL != ePL )
{
wxLogTrace( MASK_3D_RESOLVER, " + '%s'\n", (*sPL).m_Pathexp.ToUTF8() );
++sPL;
}
for( const auto searchPath : m_Paths )
wxLogTrace( trace3dResolver, " + '%s'\n", searchPath.m_Pathexp );
#endif
return true;
@ -409,7 +407,7 @@ wxString S3D_RESOLVER::ResolvePath( const wxString& aFileName )
wxString errmsg = "[3D File Resolver] No such path";
errmsg.append( "\n" );
errmsg.append( tname );
wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() );
wxLogTrace( trace3dResolver, "%s\n", errmsg.ToUTF8() );
}
return wxEmptyString;
@ -443,10 +441,9 @@ wxString S3D_RESOLVER::ResolvePath( const wxString& aFileName )
if( !( m_errflags & ERRFLG_ALIAS ) )
{
m_errflags |= ERRFLG_ALIAS;
wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
errmsg.append( "\n" );
errmsg.append( tname.substr( 1 ) );
wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() );
wxLogTrace( trace3dResolver,
wxT( "[3D File Resolver] No such path; ensure the path alias is defined %s" ),
tname.substr( 1 ) );
}
return wxEmptyString;
@ -476,7 +473,8 @@ bool S3D_RESOLVER::addPath( const SEARCH_PATH& aPath )
if( !path.DirExists() )
{
// suppress the message if the missing pathvar is the legacy KICAD6_3DMODEL_DIR variable
if( aPath.m_Pathvar != "${KICAD6_3DMODEL_DIR}" && aPath.m_Pathvar != "$(KICAD6_3DMODEL_DIR)" )
if( aPath.m_Pathvar != "${KICAD6_3DMODEL_DIR}" &&
aPath.m_Pathvar != "$(KICAD6_3DMODEL_DIR)" )
{
wxString msg = _( "The given path does not exist" );
msg.append( "\n" );
@ -541,12 +539,10 @@ bool S3D_RESOLVER::readPathList( void )
if( !wxFileName::Exists( cfgname ) )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "no 3D configuration file";
ostr << " * " << errmsg.ToUTF8() << " '";
ostr << cfgname.ToUTF8() << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:d\n"
" * no 3D configuration file '%s'" ),
__FILE__, __FUNCTION__, __LINE__, cfgname );
return false;
}
@ -554,11 +550,10 @@ bool S3D_RESOLVER::readPathList( void )
if( !cfgFile.is_open() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "Could not open configuration file";
ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * Could not open configuration file '%s'" ),
__FILE__, __FUNCTION__, __LINE__, cfgname );
return false;
}
@ -729,8 +724,7 @@ wxString S3D_RESOLVER::ShortenPath( const wxString& aFullPathName )
while( sL != eL )
{
// undefined paths do not participate in the
// file name shortening procedure
// undefined paths do not participate in the file name shortening procedure.
if( sL->m_Pathexp.empty() )
{
++sL;
@ -793,8 +787,7 @@ const std::list< SEARCH_PATH >* S3D_RESOLVER::GetPaths( void )
}
bool S3D_RESOLVER::SplitAlias( const wxString& aFileName,
wxString& anAlias, wxString& aRelPath )
bool S3D_RESOLVER::SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath )
{
anAlias.clear();
aRelPath.clear();
@ -823,11 +816,9 @@ static bool getHollerith( const std::string& aString, size_t& aIndex, wxString&
if( aIndex >= aString.size() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "bad Hollerith string on line";
ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * Bad Hollerith string in line \"%s\"" ),
__FILE__, __FUNCTION__, __LINE__, aString );
return false;
}
@ -836,11 +827,9 @@ static bool getHollerith( const std::string& aString, size_t& aIndex, wxString&
if( std::string::npos == i2 )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "missing opening quote mark in config file";
ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * missing opening quote mark in line \"%s\"" ),
__FILE__, __FUNCTION__, __LINE__, aString );
return false;
}
@ -849,11 +838,9 @@ static bool getHollerith( const std::string& aString, size_t& aIndex, wxString&
if( i2 >= aString.size() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "invalid entry (unexpected end of line)";
ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * unexpected end of line in line \"%s\"" ),
__FILE__, __FUNCTION__, __LINE__, aString );
return false;
}
@ -865,11 +852,9 @@ static bool getHollerith( const std::string& aString, size_t& aIndex, wxString&
if( tnum.empty() || aString[i2++] != ':' )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "bad Hollerith string on line";
ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * Bad Hollerith string in line \"%s\"" ),
__FILE__, __FUNCTION__, __LINE__, aString );
return false;
}
@ -881,11 +866,9 @@ static bool getHollerith( const std::string& aString, size_t& aIndex, wxString&
if( (i2 + nchars) >= aString.size() )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "invalid entry (unexpected end of line)";
ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * unexpected end of line in line \"%s\"\n" ),
__FILE__, __FUNCTION__, __LINE__, aString );
return false;
}
@ -898,11 +881,9 @@ static bool getHollerith( const std::string& aString, size_t& aIndex, wxString&
if( i2 >= aString.size() || aString[i2] != '"' )
{
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
wxString errmsg = "missing closing quote mark in config file";
ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n"
" * missing closing quote mark in line \"%s\"" ),
__FILE__, __FUNCTION__, __LINE__, aString );
return false;
}

View File

@ -2,6 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 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
@ -38,7 +39,6 @@
namespace S3D
{
struct rsort_wxString
{
bool operator() (const wxString& strA, const wxString& strB ) const
@ -99,128 +99,114 @@ struct SEARCH_PATH
wxString m_Description; // description of the aliased path
};
class S3D_RESOLVER
{
private:
wxString m_ConfigDir; // 3D configuration directory
std::list< SEARCH_PATH > m_Paths; // list of base paths to search from
// mapping of (short) file names to resolved names
std::map< wxString, wxString, S3D::rsort_wxString > m_NameMap;
int m_errflags;
wxString m_curProjDir;
// environment variables
std::map< wxString, wxString > m_EnvVars;
public:
S3D_RESOLVER();
/**
* Function createPathList
* builds the path list using available information such as
* KICAD6_3DMODEL_DIR and the 3d_path_list configuration file. Invalid
* paths are silently discarded and removed from the configuration
* file.
* Set the user's configuration directory for 3D models.
*
* @param aConfigDir
* @return true if the call succeeds (directory exists).
*/
bool Set3DConfigDir( const wxString& aConfigDir );
/**
* Set the current KiCad project directory as the first entry in the model path list.
*
* @param aProjDir is the current project directory.
* @param flgChanged, if specified, is set to true if the directory actually changed.
* @return true if the call succeeds.
*/
bool SetProjectDir( const wxString& aProjDir, bool* flgChanged = NULL );
wxString GetProjectDir( void );
/**
* Determine the full path of the given file name.
*
* In the future remote files may be supported, in which case it is best to require a full
* URI in which case #ResolvePath should check that the URI conforms to RFC-2396 and related
* documents and copies \a aFileName into the resolved name if the URI is valid.
*/
wxString ResolvePath( const wxString& aFileName );
/**
* Produce a relative path based on the existing search directories or returns the same path
* if the path is not a superset of an existing search path.
*
* @param aFullPathName is an absolute path to shorten.
* @return the shortened path or aFullPathName.
*/
wxString ShortenPath( const wxString& aFullPathName );
/**
* Return a pointer to the internal path list.
*
* The list can be used to set up the list of search paths available to a 3D file browser.
*
* @return the search path list.
*/
const std::list< SEARCH_PATH >* GetPaths( void );
/**
* Return true if the given name contains an alias and populates the string with the alias
* and the relative path.
*/
bool SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath );
/**
* If the path contains an alias then \a hasAlias is set true.
*
* @return true if the given path is a valid aliased relative path.
*/
bool ValidateFileName( const wxString& aFileName, bool& hasAlias );
private:
/**
* Build the path list using available information such as KICAD6_3DMODEL_DIR and the
* 3d_path_list configuration file.
*
* Invalid paths are silently discarded and removed from the configuration file.
*
* @return true if at least one valid path was found
*/
bool createPathList( void );
/**
* Function addPath
* checks that a path is valid and adds it to the search list
* Check that \a aPath is valid and adds it to the search list.
*
* @param aPath is the alias set to be checked and added
* @return true if aPath is valid
* @param aPath is the alias set to be checked and added.
* @return true if \a aPath is valid.
*/
bool addPath( const SEARCH_PATH& aPath );
/**
* Function readPathList
* reads a list of path names from a configuration file
* Read a list of path names from a configuration file.
*
* @return true if a file was found and contained at least
* one valid path
* @return true if a file was found and contained at least one valid path.
*/
bool readPathList( void );
/**
* Function checkEnvVarPath
* checks the ${ENV_VAR} component of a path and adds
* it to the resolver's path list if it is not yet in
* the list
* Check the ${ENV_VAR} component of a path and adds it to the resolver's path list if it
* is not yet in the list.
*/
void checkEnvVarPath( const wxString& aPath );
wxString expandVars( const wxString& aPath );
public:
S3D_RESOLVER();
wxString m_ConfigDir; ///< 3D configuration directory.
std::list< SEARCH_PATH > m_Paths; ///< List of base search paths.
/**
* Function Set3DConfigDir
* sets the user's configuration directory
* for 3D models.
*
* @param aConfigDir
* @return true if the call succeeds (directory exists)
*/
bool Set3DConfigDir( const wxString& aConfigDir );
///< Mapping of (short) file names to resolved names.
std::map< wxString, wxString, S3D::rsort_wxString > m_NameMap;
int m_errflags;
wxString m_curProjDir;
/**
* Function SetProjectDir
* sets the current KiCad project directory as the first
* entry in the model path list
*
* @param aProjDir is the current project directory
* @param flgChanged, if specified, is set to true if the directory actually changed
* @return true if the call succeeds
*/
bool SetProjectDir( const wxString& aProjDir, bool* flgChanged = NULL );
wxString GetProjectDir( void );
/**
* Function ResolvePath
* determines the full path of the given file name. In the future
* remote files may be supported, in which case it is best to
* require a full URI in which case ResolvePath should check that
* the URI conforms to RFC-2396 and related documents and copies
* aFileName into aResolvedName if the URI is valid.
*/
wxString ResolvePath( const wxString& aFileName );
/**
* Function ShortenPath
* produces a relative path based on the existing
* search directories or returns the same path if
* the path is not a superset of an existing search path.
*
* @param aFullPathName is an absolute path to shorten
* @return the shortened path or aFullPathName
*/
wxString ShortenPath( const wxString& aFullPathName );
/**
* Function GetPaths
* returns a pointer to the internal path list; the items in:load
*
* the list can be used to set up the list of search paths
* available to a 3D file browser.
*
* @return pointer to the internal path list
*/
const std::list< SEARCH_PATH >* GetPaths( void );
/**
* Function SplitAlias
* returns true if the given name contains an alias and
* populates the string anAlias with the alias and aRelPath
* with the relative path.
*/
bool SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath );
/**
* Function ValidateName
* returns true if the given path is a valid aliased relative path.
* If the path contains an alias then hasAlias is set true.
*/
bool ValidateFileName( const wxString& aFileName, bool& hasAlias );
///< Environment variables.
std::map< wxString, wxString > m_EnvVars;
};
#endif // RESOLVER_3D_H

View File

@ -2,6 +2,7 @@
* 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 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
@ -43,6 +44,15 @@ class S3D_RESOLVER;
class KICADFOOTPRINT
{
public:
KICADFOOTPRINT( KICADPCB* aParent );
virtual ~KICADFOOTPRINT();
bool Read( SEXPR::SEXPR* aEntry );
bool ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
DOUBLET aOrigin, bool aComposeVirtual = true, bool aSubstituteModels = true );
private:
bool parseModel( SEXPR::SEXPR* data );
bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType );
@ -64,15 +74,6 @@ private:
std::vector< KICADPAD* > m_pads;
std::vector< KICADCURVE* > m_curves;
std::vector< KICADMODEL* > m_models;
public:
KICADFOOTPRINT( KICADPCB* aParent );
virtual ~KICADFOOTPRINT();
bool Read( SEXPR::SEXPR* aEntry );
bool ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
DOUBLET aOrigin, bool aComposeVirtual = true, bool aSubstituteModels = true );
};
#endif // KICADFOOTPRINT_H

View File

@ -2,6 +2,7 @@
* 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 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
@ -23,7 +24,7 @@
/**
* @file kicadpad.h
* declares the PAD description object.
* Declare the PAD description object.
*/
#ifndef KICADPAD_H
@ -43,10 +44,6 @@ struct KICADDRILL
class KICADPAD
{
private:
bool m_thruhole;
bool parseDrill( const SEXPR::SEXPR* aDrill );
public:
KICADPAD();
virtual ~KICADPAD();
@ -58,9 +55,16 @@ public:
return m_thruhole;
}
private:
bool parseDrill( const SEXPR::SEXPR* aDrill );
public:
DOUBLET m_position;
double m_rotation; // rotation (radians)
KICADDRILL m_drill;
private:
bool m_thruhole;
};
#endif // KICADPAD_H

View File

@ -2,6 +2,7 @@
* 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 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
@ -52,38 +53,6 @@ class PCBMODEL;
class KICADPCB
{
private:
S3D_RESOLVER m_resolver;
wxString m_filename;
PCBMODEL* m_pcb_model;
DOUBLET m_origin;
DOUBLET m_gridOrigin;
DOUBLET m_drillOrigin;
bool m_useGridOrigin;
bool m_useDrillOrigin;
// set to TRUE if the origin was actually parsed
bool m_hasGridOrigin;
bool m_hasDrillOrigin;
// minimum distance between points to treat them as separate entities (mm)
double m_minDistance;
// the names of layers in use, and the internal layer ID
std::map<std::string, int> m_layersNames;
// PCB parameters/entities
double m_thickness;
std::vector<KICADFOOTPRINT*> m_footprints;
std::vector<KICADCURVE*> m_curves;
bool parsePCB( SEXPR::SEXPR* data );
bool parseGeneral( SEXPR::SEXPR* data );
bool parseSetup( SEXPR::SEXPR* data );
bool parseLayers( SEXPR::SEXPR* data );
bool parseModule( SEXPR::SEXPR* data );
bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType );
bool parseRect( SEXPR::SEXPR* data );
bool parsePolygon( SEXPR::SEXPR* data );
public:
KICADPCB();
virtual ~KICADPCB();
@ -114,9 +83,44 @@ public:
bool ReadFile( const wxString& aFileName );
bool ComposePCB( bool aComposeVirtual = true, bool aSubstituteModels = true );
bool WriteSTEP( const wxString& aFileName );
#ifdef SUPPORTS_IGES
bool WriteIGES( const wxString& aFileName );
#endif
private:
bool parsePCB( SEXPR::SEXPR* data );
bool parseGeneral( SEXPR::SEXPR* data );
bool parseSetup( SEXPR::SEXPR* data );
bool parseLayers( SEXPR::SEXPR* data );
bool parseModule( SEXPR::SEXPR* data );
bool parseCurve( SEXPR::SEXPR* data, CURVE_TYPE aCurveType );
bool parseRect( SEXPR::SEXPR* data );
bool parsePolygon( SEXPR::SEXPR* data );
S3D_RESOLVER m_resolver;
wxString m_filename;
PCBMODEL* m_pcb_model;
DOUBLET m_origin;
DOUBLET m_gridOrigin;
DOUBLET m_drillOrigin;
bool m_useGridOrigin;
bool m_useDrillOrigin;
// set to TRUE if the origin was actually parsed
bool m_hasGridOrigin;
bool m_hasDrillOrigin;
// minimum distance between points to treat them as separate entities (mm)
double m_minDistance;
// the names of layers in use, and the internal layer ID
std::map<std::string, int> m_layersNames;
// PCB parameters/entities
double m_thickness;
std::vector<KICADFOOTPRINT*> m_footprints;
std::vector<KICADCURVE*> m_curves;
};

View File

@ -85,12 +85,16 @@
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;
@ -111,9 +115,9 @@ static void getEndPoints( const KICADCURVE& aCurve, double& spx0, double& spy0,
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 )
@ -129,7 +133,6 @@ static void getCurveEndPoint( const KICADCURVE& aCurve, DOUBLET& aEndPoint )
// assume a line
aEndPoint.x = aCurve.m_end.x;
aEndPoint.y = aCurve.m_end.y;
return;
}
@ -154,8 +157,6 @@ static void reverseCurve( KICADCURVE& aCurve )
std::swap( aCurve.m_end, aCurve.m_ep );
std::swap( aCurve.m_endangle, aCurve.m_startangle );
aCurve.m_angle = -aCurve.m_angle;
return;
}
@ -252,17 +253,15 @@ PCBMODEL::PCBMODEL()
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 )
@ -330,7 +329,8 @@ bool PCBMODEL::AddOutlineSegment( KICADCURVE* aCurve )
if( rad < m_minDistance2 )
{
wxString msg;
msg.Printf( " * AddOutlineSegment() rejected an arc with equivalent end points, %s\n",
msg.Printf( " * AddOutlineSegment() rejected an arc with equivalent end "
"points, %s\n",
aCurve->Describe() );
ReportMessage( msg );
return false;
@ -448,8 +448,7 @@ bool PCBMODEL::AddOutlineSegment( KICADCURVE* aCurve )
do
{
wxString msg;
msg.Printf( " * AddOutlineSegment() unsupported curve type: %d\n",
aCurve->m_form );
msg.Printf( " * AddOutlineSegment() unsupported curve type: %d\n", aCurve->m_form );
ReportMessage( msg );
} while( 0 );
@ -460,7 +459,6 @@ bool PCBMODEL::AddOutlineSegment( KICADCURVE* aCurve )
}
// add a pad hole or slot
bool PCBMODEL::AddPadHole( const KICADPAD* aPad )
{
if( NULL == aPad || !aPad->IsThruHole() )
@ -471,7 +469,8 @@ bool PCBMODEL::AddPadHole( const KICADPAD* aPad )
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 ) );
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;
@ -601,7 +600,6 @@ bool PCBMODEL::AddPadHole( const KICADPAD* aPad )
}
// 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,
@ -664,8 +662,6 @@ void PCBMODEL::SetPCBThickness( double aThickness )
m_thickness = THICKNESS_MIN;
else
m_thickness = aThickness;
return;
}
@ -677,7 +673,6 @@ void PCBMODEL::SetMinDistance( double aDistance )
}
// create the PCB (board only) model using the current outlines and drill holes
bool PCBMODEL::CreatePCB()
{
if( m_hasPCB )
@ -814,7 +809,8 @@ bool PCBMODEL::CreatePCB()
(int)m_cutouts.size() ) );
}
#if 0 // First version for holes removing: very slow when having many (> 300) holes
#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;
@ -863,8 +859,7 @@ bool PCBMODEL::CreatePCB()
return false;
// color the PCB
Handle(XCAFDoc_ColorTool) color =
XCAFDoc_DocumentTool::ColorTool( m_doc->Main () );
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 );
@ -915,7 +910,6 @@ bool PCBMODEL::WriteIGES( const wxString& aFileName )
#endif
// write the assembly model in STEP format
bool PCBMODEL::WriteSTEP( const wxString& aFileName )
{
if( m_pcb_label.IsNull() )
@ -942,9 +936,11 @@ bool PCBMODEL::WriteSTEP( const wxString& aFileName )
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" ) );
@ -952,8 +948,8 @@ bool PCBMODEL::WriteSTEP( const wxString& aFileName )
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
// Creates a temporary file with a ascii7 name, because writer does not know unicode filenames.
wxString currCWD = wxGetCwd();
wxString workCWD = fn.GetPath();
@ -1008,8 +1004,7 @@ bool PCBMODEL::getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_
case FMT_IGES:
if( !readIGES( doc, aFileName.c_str() ) )
{
ReportMessage( wxString::Format( "readIGES() failed on filename '%s'.\n",
aFileName ) );
ReportMessage( wxString::Format( "readIGES() failed on filename '%s'.\n", aFileName ) );
return false;
}
break;
@ -1017,8 +1012,7 @@ bool PCBMODEL::getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_
case FMT_STEP:
if( !readSTEP( doc, aFileName.c_str() ) )
{
ReportMessage( wxString::Format( "readSTEP() failed on filename '%s'.\n",
aFileName ) );
ReportMessage( wxString::Format( "readSTEP() failed on filename '%s'.\n", aFileName ) );
return false;
}
break;
@ -1035,8 +1029,7 @@ bool PCBMODEL::getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_
if( size == wxInvalidOffset )
{
ReportMessage( wxString::Format( "readSTEP() failed on filename '%s'.\n",
aFileName ) );
ReportMessage( wxString::Format( "readSTEP() failed on filename '%s'.\n", aFileName ) );
return false;
}
@ -1058,7 +1051,8 @@ bool PCBMODEL::getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_
success = true;
}
catch( ... )
{}
{
}
if( expanded.empty() )
{
@ -1089,16 +1083,13 @@ bool PCBMODEL::getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_
case FMT_WRL:
case FMT_WRZ:
/* WRL files are preferred for internal rendering,
* due to superior material properties, etc.
/* 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 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 )
{
@ -1338,7 +1329,6 @@ TDF_Label PCBMODEL::transferModel( Handle( TDocStd_Document )& source,
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() );
@ -1456,13 +1446,11 @@ OUTLINE::OUTLINE()
{
m_closed = false;
m_minDistance2 = MIN_LENGTH2;
return;
}
OUTLINE::~OUTLINE()
{
return;
}
@ -1470,7 +1458,6 @@ void OUTLINE::Clear()
{
m_closed = false;
m_curves.clear();
return;
}
@ -1639,10 +1626,9 @@ bool OUTLINE::addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBL
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 ) );
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:

View File

@ -49,15 +49,7 @@ class KICADPAD;
class OUTLINE
{
private:
bool m_closed; // set true if the loop is closed
double m_minDistance2; // min squared distance to treat points as separate entities (mm)
bool addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBLET& aLastPoint );
bool testClosed( const KICADCURVE& aFrontCurve, const KICADCURVE& aBackCurve );
public:
std::list< KICADCURVE > m_curves; // list of contiguous segments
OUTLINE();
virtual ~OUTLINE();
@ -77,51 +69,22 @@ public:
}
bool MakeShape( TopoDS_Shape& aShape, double aThickness );
private:
bool addEdge( BRepBuilderAPI_MakeWire* aWire, KICADCURVE& aCurve, DOUBLET& aLastPoint );
bool testClosed( const KICADCURVE& aFrontCurve, const KICADCURVE& aBackCurve );
public:
std::list< KICADCURVE > m_curves; // list of contiguous segments
private:
bool m_closed; // set true if the loop is closed
double m_minDistance2; // min squared distance to treat points as separate entities (mm)
};
class PCBMODEL
{
Handle( XCAFApp_Application ) m_app;
Handle( TDocStd_Document ) m_doc;
Handle( XCAFDoc_ShapeTool ) m_assy;
TDF_Label m_assy_label;
bool m_hasPCB; // set true if CreatePCB() has been invoked
TDF_Label m_pcb_label; // label for the PCB model
MODEL_MAP m_models; // map of file names to model labels
int m_components; // number of successfully loaded components;
double m_precision; // model (length unit) numeric precision
double m_angleprec; // angle numeric precision
double m_thickness; // PCB thickness, mm
double m_minx; // minimum X value in curves (leftmost curve feature)
double m_minDistance2; // minimum squared distance between items (mm)
std::list<KICADCURVE>::iterator m_mincurve; // iterator to the leftmost curve
std::list<KICADCURVE> m_curves;
std::vector<TopoDS_Shape> m_cutouts;
/**
* Load a 3D model data
* aFileName is the filename (different formats allowed) but for WRML files a model
* data can be loaded instead of the vrml data, not suitable in a step file
* @param aScale is the X,Y,Z scaling factors
* @param aLabel is the TDF_Label to store the data
* @param aSubstituteModels = true to allows data substitution, false to disallow.
* @param aErrorMessage (can be nullptr) is an error message to be displayed on error.
* @return true if successfully loaded, false on error
*/
bool getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_Label& aLabel,
bool aSubstituteModels, wxString* aErrorMessage = nullptr );
bool getModelLocation( bool aBottom, DOUBLET aPosition, double aRotation,
TRIPLET aOffset, TRIPLET aOrientation, TopLoc_Location& aLocation );
bool readIGES( Handle( TDocStd_Document )& m_doc, const char* fname );
bool readSTEP( Handle( TDocStd_Document )& m_doc, const char* fname );
TDF_Label transferModel( Handle( TDocStd_Document )& source,
Handle( TDocStd_Document )& dest, TRIPLET aScale );
public:
PCBMODEL();
virtual ~PCBMODEL();
@ -157,6 +120,50 @@ public:
// write the assembly model in STEP format
bool WriteSTEP( const wxString& aFileName );
private:
/**
* Load a 3D model data.
*
* @param aFileName is the filename (different formats allowed) but for WRML files a model
* data can be loaded instead of the vrml data, not suitable in a step file.
* @param aScale is the X,Y,Z scaling factors.
* @param aLabel is the TDF_Label to store the data.
* @param aSubstituteModels = true to allows data substitution, false to disallow.
* @param aErrorMessage (can be nullptr) is an error message to be displayed on error.
* @return true if successfully loaded, false on error.
*/
bool getModelLabel( const std::string& aFileName, TRIPLET aScale, TDF_Label& aLabel,
bool aSubstituteModels, wxString* aErrorMessage = nullptr );
bool getModelLocation( bool aBottom, DOUBLET aPosition, double aRotation, TRIPLET aOffset,
TRIPLET aOrientation, TopLoc_Location& aLocation );
bool readIGES( Handle( TDocStd_Document )& m_doc, const char* fname );
bool readSTEP( Handle( TDocStd_Document )& m_doc, const char* fname );
TDF_Label transferModel( Handle( TDocStd_Document )& source,
Handle( TDocStd_Document )& dest, TRIPLET aScale );
Handle( XCAFApp_Application ) m_app;
Handle( TDocStd_Document ) m_doc;
Handle( XCAFDoc_ShapeTool ) m_assy;
TDF_Label m_assy_label;
bool m_hasPCB; // set true if CreatePCB() has been invoked
TDF_Label m_pcb_label; // label for the PCB model
MODEL_MAP m_models; // map of file names to model labels
int m_components; // number of successfully loaded components;
double m_precision; // model (length unit) numeric precision
double m_angleprec; // angle numeric precision
double m_thickness; // PCB thickness, mm
// minimum X value in curves (leftmost curve feature).
double m_minx;
double m_minDistance2; // minimum squared distance between items (mm)
std::list<KICADCURVE>::iterator m_mincurve; // iterator to the leftmost curve
std::list<KICADCURVE> m_curves;
std::vector<TopoDS_Shape> m_cutouts;
};
#endif // OCE_VIS_OCE_UTILS_H