export_vrml: Better export of pad shapes, both on copper and paste layers.
It removes duplicate code between Pcbnew and the vrml exporter. Remove also an useless option (plain board) that exported just the board body.
This commit is contained in:
parent
acec6ad7fc
commit
af88b01068
|
@ -49,7 +49,6 @@ private:
|
|||
int m_unitsOpt; // Remember last units option
|
||||
bool m_copy3DFilesOpt; // Remember last copy model files option
|
||||
bool m_useRelativePathsOpt; // Remember last use absolute paths option
|
||||
bool m_usePlainPCBOpt; // Remember last Plain Board option
|
||||
int m_RefUnits; // Remember last units for Reference Point
|
||||
double m_XRef; // Remember last X Reference Point
|
||||
double m_YRef; // Remember last Y Reference Point
|
||||
|
@ -67,7 +66,6 @@ public:
|
|||
m_unitsOpt = cfg->m_ExportVrml.units;
|
||||
m_copy3DFilesOpt = cfg->m_ExportVrml.copy_3d_models;
|
||||
m_useRelativePathsOpt = cfg->m_ExportVrml.use_relative_paths;
|
||||
m_usePlainPCBOpt = cfg->m_ExportVrml.use_plain_pcb;
|
||||
m_RefUnits = cfg->m_ExportVrml.ref_units;
|
||||
m_XRef = cfg->m_ExportVrml.ref_x;
|
||||
m_YRef = cfg->m_ExportVrml.ref_y;
|
||||
|
@ -78,7 +76,6 @@ public:
|
|||
m_rbSelectUnits->SetSelection( m_unitsOpt );
|
||||
m_cbCopyFiles->SetValue( m_copy3DFilesOpt );
|
||||
m_cbUseRelativePaths->SetValue( m_useRelativePathsOpt );
|
||||
m_cbPlainPCB->SetValue( m_usePlainPCBOpt );
|
||||
m_VRML_RefUnitChoice->SetSelection( m_RefUnits );
|
||||
wxString tmpStr;
|
||||
tmpStr << m_XRef;
|
||||
|
@ -102,7 +99,6 @@ public:
|
|||
cfg->m_ExportVrml.units = m_unitsOpt;
|
||||
cfg->m_ExportVrml.copy_3d_models = m_copy3DFilesOpt;
|
||||
cfg->m_ExportVrml.use_relative_paths = m_useRelativePathsOpt;
|
||||
cfg->m_ExportVrml.use_plain_pcb = m_usePlainPCBOpt;
|
||||
cfg->m_ExportVrml.ref_units = m_VRML_RefUnitChoice->GetSelection();
|
||||
cfg->m_ExportVrml.origin_mode = m_rbCoordOrigin->GetSelection();
|
||||
|
||||
|
@ -164,11 +160,6 @@ public:
|
|||
return m_useRelativePathsOpt = m_cbUseRelativePaths->GetValue();
|
||||
}
|
||||
|
||||
bool GetUsePlainPCBOption()
|
||||
{
|
||||
return m_usePlainPCBOpt = m_cbPlainPCB->GetValue();
|
||||
}
|
||||
|
||||
void OnUpdateUseRelativePath( wxUpdateUIEvent& event ) override
|
||||
{
|
||||
// Making path relative or absolute has no meaning when VRML files are not copied.
|
||||
|
@ -246,7 +237,6 @@ void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event )
|
|||
double scale = scaleList[dlg.GetUnits()]; // final scale export
|
||||
bool export3DFiles = dlg.GetCopyFilesOption();
|
||||
bool useRelativePaths = dlg.GetUseRelativePathsOption();
|
||||
bool usePlainPCB = dlg.GetUsePlainPCBOption();
|
||||
|
||||
path = dlg.FilePicker()->GetPath();
|
||||
SetLastPath( LAST_PATH_VRML, path );
|
||||
|
@ -269,7 +259,7 @@ void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event )
|
|||
}
|
||||
|
||||
if( !ExportVRML_File( path, scale, export3DFiles, useRelativePaths,
|
||||
usePlainPCB, modelPath.GetPath(), aXRef, aYRef ) )
|
||||
modelPath.GetPath(), aXRef, aYRef ) )
|
||||
{
|
||||
wxString msg = wxString::Format( _( "Unable to create file \"%s\"" ), path );
|
||||
DisplayErrorMessage( this, msg );
|
||||
|
|
|
@ -131,9 +131,6 @@ DIALOG_EXPORT_3DFILE_BASE::DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindow
|
|||
|
||||
bSizer4->Add( m_cbUseRelativePaths, 0, wxALL, 5 );
|
||||
|
||||
m_cbPlainPCB = new wxCheckBox( this, wxID_ANY, _("Plain PCB (no copper or silk)"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
bSizer4->Add( m_cbPlainPCB, 0, wxALL, 5 );
|
||||
|
||||
|
||||
bLowerSizer->Add( bSizer4, 2, wxEXPAND, 5 );
|
||||
|
||||
|
|
|
@ -1075,70 +1075,6 @@
|
|||
<event name="OnUpdateUI">OnUpdateUseRelativePath</event>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem" expanded="0">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxALL</property>
|
||||
<property name="proportion">0</property>
|
||||
<object class="wxCheckBox" expanded="0">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
<property name="RightDockable">1</property>
|
||||
<property name="TopDockable">1</property>
|
||||
<property name="aui_layer"></property>
|
||||
<property name="aui_name"></property>
|
||||
<property name="aui_position"></property>
|
||||
<property name="aui_row"></property>
|
||||
<property name="best_size"></property>
|
||||
<property name="bg"></property>
|
||||
<property name="caption"></property>
|
||||
<property name="caption_visible">1</property>
|
||||
<property name="center_pane">0</property>
|
||||
<property name="checked">0</property>
|
||||
<property name="close_button">1</property>
|
||||
<property name="context_help"></property>
|
||||
<property name="context_menu">1</property>
|
||||
<property name="default_pane">0</property>
|
||||
<property name="dock">Dock</property>
|
||||
<property name="dock_fixed">0</property>
|
||||
<property name="docking">Left</property>
|
||||
<property name="enabled">1</property>
|
||||
<property name="fg"></property>
|
||||
<property name="floatable">1</property>
|
||||
<property name="font"></property>
|
||||
<property name="gripper">0</property>
|
||||
<property name="hidden">0</property>
|
||||
<property name="id">wxID_ANY</property>
|
||||
<property name="label">Plain PCB (no copper or silk)</property>
|
||||
<property name="max_size"></property>
|
||||
<property name="maximize_button">0</property>
|
||||
<property name="maximum_size"></property>
|
||||
<property name="min_size"></property>
|
||||
<property name="minimize_button">0</property>
|
||||
<property name="minimum_size"></property>
|
||||
<property name="moveable">1</property>
|
||||
<property name="name">m_cbPlainPCB</property>
|
||||
<property name="pane_border">1</property>
|
||||
<property name="pane_position"></property>
|
||||
<property name="pane_size"></property>
|
||||
<property name="permission">protected</property>
|
||||
<property name="pin_button">1</property>
|
||||
<property name="pos"></property>
|
||||
<property name="resize">Resizable</property>
|
||||
<property name="show">1</property>
|
||||
<property name="size"></property>
|
||||
<property name="style"></property>
|
||||
<property name="subclass"></property>
|
||||
<property name="toolbar_pane">0</property>
|
||||
<property name="tooltip"></property>
|
||||
<property name="validator_data_type"></property>
|
||||
<property name="validator_style">wxFILTER_NONE</property>
|
||||
<property name="validator_type">wxDefaultValidator</property>
|
||||
<property name="validator_variable"></property>
|
||||
<property name="window_extra_style"></property>
|
||||
<property name="window_name"></property>
|
||||
<property name="window_style"></property>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
|
|
|
@ -53,7 +53,6 @@ class DIALOG_EXPORT_3DFILE_BASE : public DIALOG_SHIM
|
|||
wxRadioBox* m_rbSelectUnits;
|
||||
wxCheckBox* m_cbCopyFiles;
|
||||
wxCheckBox* m_cbUseRelativePaths;
|
||||
wxCheckBox* m_cbPlainPCB;
|
||||
wxStaticLine* m_staticline1;
|
||||
wxStdDialogButtonSizer* m_sdbSizer;
|
||||
wxButton* m_sdbSizerOK;
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
#include <exporter_vrml.h>
|
||||
|
||||
|
||||
MODEL_VRML::MODEL_VRML() :
|
||||
EXPORTER_PCB_VRML::EXPORTER_PCB_VRML() :
|
||||
m_OutputPCB( (SGNODE*) NULL )
|
||||
{
|
||||
m_ReuseDef = true;
|
||||
|
@ -74,7 +74,7 @@ MODEL_VRML::MODEL_VRML() :
|
|||
|
||||
// pcb green
|
||||
vrml_colors_list[VRML_COLOR_PCB] = VRML_COLOR(
|
||||
0.07f, 0.3f, 0.12f, 0.01f, 0.03f, 0.01f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.02f );
|
||||
0.12f, 0.28f, 0.14f, 0.01f, 0.03f, 0.01f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.02f );
|
||||
// copper color
|
||||
vrml_colors_list[VRML_COLOR_COPPER] = VRML_COLOR(
|
||||
0.72f, 0.45f, 0.2f, 0.01f, 0.05f, 0.01f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.02f );
|
||||
|
@ -82,13 +82,12 @@ MODEL_VRML::MODEL_VRML() :
|
|||
vrml_colors_list[VRML_COLOR_SILK] = VRML_COLOR(
|
||||
0.9f, 0.9f, 0.9f, 0.1f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.02f );
|
||||
// solder paste silver
|
||||
vrml_colors_list[VRML_COLOR_PASTE] = VRML_COLOR( 0.749f, 0.756f, 0.761f, 0.749f, 0.756f, 0.761f, 0.0f,
|
||||
vrml_colors_list[VRML_COLOR_PASTE] = VRML_COLOR( 0.75f, 0.75f, 0.76f, 0.75f, 0.75f, 0.76f, 0.0f,
|
||||
0.0f, 0.0f, 0.8f, 0.0f, 0.8f );
|
||||
// solder mask green with transparency
|
||||
vrml_colors_list[VRML_COLOR_SOLDMASK] = VRML_COLOR(
|
||||
0.07f, 0.3f, 0.12f, 0.01f, 0.03f, 0.01f, 0.0f, 0.0f, 0.0f, 0.8f, 0.25f, 0.02f );
|
||||
|
||||
m_plainPCB = false;
|
||||
SetOffset( 0.0, 0.0 );
|
||||
m_text_layer = F_Cu;
|
||||
m_text_width = 1;
|
||||
|
@ -96,7 +95,7 @@ MODEL_VRML::MODEL_VRML() :
|
|||
}
|
||||
|
||||
|
||||
MODEL_VRML::~MODEL_VRML()
|
||||
EXPORTER_PCB_VRML::~EXPORTER_PCB_VRML()
|
||||
{
|
||||
// destroy any unassociated material appearances
|
||||
for( int j = 0; j < VRML_COLOR_LAST; ++j )
|
||||
|
@ -123,7 +122,7 @@ MODEL_VRML::~MODEL_VRML()
|
|||
}
|
||||
|
||||
|
||||
bool MODEL_VRML::SetScale( double aWorldScale )
|
||||
bool EXPORTER_PCB_VRML::SetScale( double aWorldScale )
|
||||
{
|
||||
// set the scaling of the VRML world
|
||||
if( aWorldScale < 0.001 || aWorldScale > 10.0 )
|
||||
|
@ -136,7 +135,7 @@ bool MODEL_VRML::SetScale( double aWorldScale )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::SetOffset( double aXoff, double aYoff )
|
||||
void EXPORTER_PCB_VRML::SetOffset( double aXoff, double aYoff )
|
||||
{
|
||||
m_tx = aXoff;
|
||||
m_ty = -aYoff;
|
||||
|
@ -155,7 +154,7 @@ void MODEL_VRML::SetOffset( double aXoff, double aYoff )
|
|||
}
|
||||
|
||||
|
||||
bool MODEL_VRML::GetLayer3D( LAYER_NUM layer, VRML_LAYER** vlayer )
|
||||
bool EXPORTER_PCB_VRML::GetLayer3D( LAYER_NUM layer, VRML_LAYER** vlayer )
|
||||
{
|
||||
// select the VRML layer object to draw on; return true if
|
||||
// a layer has been selected.
|
||||
|
@ -173,10 +172,11 @@ bool MODEL_VRML::GetLayer3D( LAYER_NUM layer, VRML_LAYER** vlayer )
|
|||
}
|
||||
}
|
||||
|
||||
void MODEL_VRML::ExportVrmlSolderMask()
|
||||
void EXPORTER_PCB_VRML::ExportVrmlSolderMask()
|
||||
{
|
||||
SHAPE_POLY_SET holes, outlines = m_pcbOutlines;
|
||||
// Build the solder mask opening. the actual shape is the negative shape
|
||||
// holes is the solder mask opening.
|
||||
// the actual shape is the negative shape of mask opening.
|
||||
PCB_LAYER_ID layer = F_Mask;
|
||||
VRML_LAYER* vrmllayer = &m_top_soldermask;
|
||||
|
||||
|
@ -198,10 +198,10 @@ void MODEL_VRML::ExportVrmlSolderMask()
|
|||
|
||||
|
||||
// static var. for dealing with text
|
||||
static MODEL_VRML* model_vrml;
|
||||
static EXPORTER_PCB_VRML* model_vrml;
|
||||
|
||||
|
||||
void MODEL_VRML::write_triangle_bag( std::ostream& aOut_file, const VRML_COLOR& aColor,
|
||||
void EXPORTER_PCB_VRML::write_triangle_bag( std::ostream& aOut_file, const VRML_COLOR& aColor,
|
||||
VRML_LAYER* aLayer, bool aPlane, bool aTop,
|
||||
double aTop_z, double aBottom_z )
|
||||
{
|
||||
|
@ -305,7 +305,7 @@ void MODEL_VRML::write_triangle_bag( std::ostream& aOut_file, const VRML_COLOR&
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::writeLayers( const char* aFileName,
|
||||
void EXPORTER_PCB_VRML::writeLayers( const char* aFileName,
|
||||
OSTREAM* aOutputFile )
|
||||
{
|
||||
// VRML_LAYER board;
|
||||
|
@ -323,14 +323,6 @@ void MODEL_VRML::writeLayers( const char* aFileName,
|
|||
create_vrml_shell( m_OutputPCB, VRML_COLOR_PCB, &m_3D_board, brdz, -brdz );
|
||||
}
|
||||
|
||||
if( m_plainPCB )
|
||||
{
|
||||
if( !m_UseInlineModelsInBrdfile )
|
||||
S3D::WriteVRML( aFileName, true, m_OutputPCB.GetRawPtr(), m_ReuseDef, true );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// VRML_LAYER m_top_copper;
|
||||
m_top_copper.Tesselate( &m_holes );
|
||||
|
||||
|
@ -478,7 +470,7 @@ void MODEL_VRML::writeLayers( const char* aFileName,
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ComputeLayer3D_Zpos()
|
||||
void EXPORTER_PCB_VRML::ComputeLayer3D_Zpos()
|
||||
{
|
||||
int copper_layers = m_Pcb->GetCopperLayerCount();
|
||||
|
||||
|
@ -515,7 +507,7 @@ void MODEL_VRML::ComputeLayer3D_Zpos()
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlLine( LAYER_NUM layer,
|
||||
void EXPORTER_PCB_VRML::ExportVrmlLine( LAYER_NUM layer,
|
||||
double startx, double starty,
|
||||
double endx, double endy, double width )
|
||||
{
|
||||
|
@ -540,7 +532,7 @@ void MODEL_VRML::ExportVrmlLine( LAYER_NUM layer,
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlCircle( LAYER_NUM layer,
|
||||
void EXPORTER_PCB_VRML::ExportVrmlCircle( LAYER_NUM layer,
|
||||
double startx, double starty,
|
||||
double endx, double endy, double width )
|
||||
{
|
||||
|
@ -571,7 +563,7 @@ void MODEL_VRML::ExportVrmlCircle( LAYER_NUM layer,
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlArc( LAYER_NUM layer,
|
||||
void EXPORTER_PCB_VRML::ExportVrmlArc( LAYER_NUM layer,
|
||||
double centerx, double centery,
|
||||
double arc_startx, double arc_starty,
|
||||
double width, double arc_angle )
|
||||
|
@ -593,7 +585,7 @@ void MODEL_VRML::ExportVrmlArc( LAYER_NUM layer,
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlPolygon( LAYER_NUM layer, PCB_SHAPE *aOutline,
|
||||
void EXPORTER_PCB_VRML::ExportVrmlPolygon( LAYER_NUM layer, PCB_SHAPE *aOutline,
|
||||
double aOrientation, wxPoint aPos )
|
||||
{
|
||||
if( aOutline->IsPolyShapeValid() )
|
||||
|
@ -629,7 +621,7 @@ void MODEL_VRML::ExportVrmlPolygon( LAYER_NUM layer, PCB_SHAPE *aOutline,
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlPolyPolygon( VRML_LAYER* aVlayer, SHAPE_POLY_SET& aOutlines,
|
||||
void EXPORTER_PCB_VRML::ExportVrmlPolyPolygon( VRML_LAYER* aVlayer, SHAPE_POLY_SET& aOutlines,
|
||||
double aOrientation, wxPoint aPos )
|
||||
{
|
||||
aOutlines.Rotate( -aOrientation, VECTOR2I( 0, 0 ) );
|
||||
|
@ -653,7 +645,7 @@ void MODEL_VRML::ExportVrmlPolyPolygon( VRML_LAYER* aVlayer, SHAPE_POLY_SET& aOu
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlDrawsegment( PCB_SHAPE* drawseg )
|
||||
void EXPORTER_PCB_VRML::ExportVrmlDrawsegment( PCB_SHAPE* drawseg )
|
||||
{
|
||||
LAYER_NUM layer = drawseg->GetLayer();
|
||||
double w = drawseg->GetWidth() * m_BoardToVrmlScale;
|
||||
|
@ -747,7 +739,7 @@ static void vrml_text_callback( int x0, int y0, int xf, int yf, void* aData )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlPcbtext( PCB_TEXT* text )
|
||||
void EXPORTER_PCB_VRML::ExportVrmlPcbtext( PCB_TEXT* text )
|
||||
{
|
||||
wxSize size = text->GetTextSize();
|
||||
|
||||
|
@ -785,7 +777,7 @@ void MODEL_VRML::ExportVrmlPcbtext( PCB_TEXT* text )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlDrawings()
|
||||
void EXPORTER_PCB_VRML::ExportVrmlDrawings()
|
||||
{
|
||||
// draw graphic items
|
||||
for( auto drawing : m_Pcb->Drawings() )
|
||||
|
@ -813,7 +805,7 @@ void MODEL_VRML::ExportVrmlDrawings()
|
|||
|
||||
|
||||
// board edges and cutouts
|
||||
void MODEL_VRML::ExportVrmlBoard()
|
||||
void EXPORTER_PCB_VRML::ExportVrmlBoard()
|
||||
{
|
||||
if( !m_Pcb->GetBoardPolygonOutlines( m_pcbOutlines ) )
|
||||
{
|
||||
|
@ -862,7 +854,7 @@ void MODEL_VRML::ExportVrmlBoard()
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportRoundPadstack( double x, double y, double r,
|
||||
void EXPORTER_PCB_VRML::ExportRoundPadstack( double x, double y, double r,
|
||||
LAYER_NUM bottom_layer, LAYER_NUM top_layer,
|
||||
double hole )
|
||||
{
|
||||
|
@ -876,9 +868,6 @@ void MODEL_VRML::ExportRoundPadstack( double x, double y, double r,
|
|||
if( thru && hole > 0 )
|
||||
m_holes.AddCircle( x, -y, hole, true );
|
||||
|
||||
if( m_plainPCB )
|
||||
return;
|
||||
|
||||
while( true )
|
||||
{
|
||||
if( layer == B_Cu )
|
||||
|
@ -906,7 +895,7 @@ void MODEL_VRML::ExportRoundPadstack( double x, double y, double r,
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlVia( const VIA* aVia )
|
||||
void EXPORTER_PCB_VRML::ExportVrmlVia( const VIA* aVia )
|
||||
{
|
||||
double x, y, r, hole;
|
||||
PCB_LAYER_ID top_layer, bottom_layer;
|
||||
|
@ -926,7 +915,7 @@ void MODEL_VRML::ExportVrmlVia( const VIA* aVia )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlTracks()
|
||||
void EXPORTER_PCB_VRML::ExportVrmlTracks()
|
||||
{
|
||||
for( TRACK* track : m_Pcb->Tracks() )
|
||||
{
|
||||
|
@ -934,7 +923,7 @@ void MODEL_VRML::ExportVrmlTracks()
|
|||
{
|
||||
ExportVrmlVia( (const VIA*) track );
|
||||
}
|
||||
else if( ( track->GetLayer() == B_Cu || track->GetLayer() == F_Cu ) && !m_plainPCB )
|
||||
else if( ( track->GetLayer() == B_Cu || track->GetLayer() == F_Cu ) )
|
||||
{
|
||||
if( track->Type() == PCB_ARC_T )
|
||||
{
|
||||
|
@ -977,7 +966,7 @@ void MODEL_VRML::ExportVrmlTracks()
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlZones()
|
||||
void EXPORTER_PCB_VRML::ExportVrmlZones()
|
||||
{
|
||||
for( ZONE* zone : m_Pcb->Zones() )
|
||||
{
|
||||
|
@ -1015,7 +1004,7 @@ void MODEL_VRML::ExportVrmlZones()
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlFpText( FP_TEXT* item )
|
||||
void EXPORTER_PCB_VRML::ExportVrmlFpText( FP_TEXT* item )
|
||||
{
|
||||
if( item->IsVisible() )
|
||||
{
|
||||
|
@ -1037,7 +1026,7 @@ void MODEL_VRML::ExportVrmlFpText( FP_TEXT* item )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlFpShape( FP_SHAPE* aOutline, FOOTPRINT* aFootprint )
|
||||
void EXPORTER_PCB_VRML::ExportVrmlFpShape( FP_SHAPE* aOutline, FOOTPRINT* aFootprint )
|
||||
{
|
||||
LAYER_NUM layer = aOutline->GetLayer();
|
||||
double x = aOutline->GetStart().x * m_BoardToVrmlScale;
|
||||
|
@ -1102,148 +1091,7 @@ void MODEL_VRML::ExportVrmlFpShape( FP_SHAPE* aOutline, FOOTPRINT* aFootprint )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlPadshape( VRML_LAYER* aTinLayer, PCB_LAYER_ID aPcbLayer, PAD* aPad )
|
||||
{
|
||||
// The (maybe offset) pad position
|
||||
wxPoint pad_pos = aPad->ShapePos();
|
||||
double pad_x = pad_pos.x * m_BoardToVrmlScale;
|
||||
double pad_y = pad_pos.y * m_BoardToVrmlScale;
|
||||
wxSize pad_delta = aPad->GetDelta();
|
||||
|
||||
double pad_dx = pad_delta.x * m_BoardToVrmlScale / 2.0;
|
||||
double pad_dy = pad_delta.y * m_BoardToVrmlScale / 2.0;
|
||||
|
||||
double pad_w = aPad->GetSize().x * m_BoardToVrmlScale / 2.0;
|
||||
double pad_h = aPad->GetSize().y * m_BoardToVrmlScale / 2.0;
|
||||
|
||||
switch( aPad->GetShape() )
|
||||
{
|
||||
case PAD_SHAPE_CIRCLE:
|
||||
|
||||
if( !aTinLayer->AddCircle( pad_x, -pad_y, pad_w, false ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
break;
|
||||
|
||||
case PAD_SHAPE_OVAL:
|
||||
|
||||
if( !aTinLayer->AddSlot( pad_x, -pad_y, pad_w * 2.0, pad_h * 2.0,
|
||||
aPad->GetOrientation()/10.0, false ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
break;
|
||||
|
||||
case PAD_SHAPE_ROUNDRECT:
|
||||
case PAD_SHAPE_CHAMFERED_RECT:
|
||||
{
|
||||
SHAPE_POLY_SET polySet;
|
||||
const int corner_radius = aPad->GetRoundRectCornerRadius();
|
||||
bool doChamfer = aPad->GetShape() == PAD_SHAPE_CHAMFERED_RECT;
|
||||
double chamferRatio = doChamfer ? aPad->GetChamferRectRatio() : 0.0;
|
||||
|
||||
TransformRoundChamferedRectToPolygon( polySet, wxPoint( 0, 0 ), aPad->GetSize(), 0.0,
|
||||
corner_radius, chamferRatio,
|
||||
doChamfer ? aPad->GetChamferPositions() : 0,
|
||||
ARC_HIGH_DEF, ERROR_INSIDE );
|
||||
std::vector< wxRealPoint > cornerList;
|
||||
// TransformRoundChamferedRectToPolygon creates only one convex polygon
|
||||
SHAPE_LINE_CHAIN& poly = polySet.Outline( 0 );
|
||||
|
||||
cornerList.reserve( poly.PointCount() );
|
||||
|
||||
for( int ii = 0; ii < poly.PointCount(); ++ii )
|
||||
cornerList.emplace_back(
|
||||
poly.CPoint( ii ).x * m_BoardToVrmlScale,
|
||||
-poly.CPoint( ii ).y * m_BoardToVrmlScale );
|
||||
|
||||
// Close polygon
|
||||
cornerList.push_back( cornerList[0] );
|
||||
|
||||
if( !aTinLayer->AddPolygon( cornerList, pad_x, -pad_y, aPad->GetOrientation() ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PAD_SHAPE_CUSTOM:
|
||||
{
|
||||
SHAPE_POLY_SET polySet;
|
||||
std::vector< wxRealPoint > cornerList;
|
||||
aPad->MergePrimitivesAsPolygon( &polySet, UNDEFINED_LAYER );
|
||||
|
||||
for( int cnt = 0; cnt < polySet.OutlineCount(); ++cnt )
|
||||
{
|
||||
SHAPE_LINE_CHAIN& poly = polySet.Outline( cnt );
|
||||
cornerList.clear();
|
||||
|
||||
for( int ii = 0; ii < poly.PointCount(); ++ii )
|
||||
cornerList.emplace_back(
|
||||
poly.CPoint( ii ).x * m_BoardToVrmlScale, -poly.CPoint( ii ).y * m_BoardToVrmlScale );
|
||||
|
||||
// Close polygon
|
||||
cornerList.push_back( cornerList[0] );
|
||||
|
||||
if( !aTinLayer->AddPolygon( cornerList, pad_x, -pad_y, aPad->GetOrientation() ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case PAD_SHAPE_RECT:
|
||||
// Just to be sure :D
|
||||
pad_dx = 0;
|
||||
pad_dy = 0;
|
||||
|
||||
// Intentionally fall through and treat a rectangle as a trapezoid with no sloped sides
|
||||
KI_FALLTHROUGH;
|
||||
|
||||
case PAD_SHAPE_TRAPEZOID:
|
||||
{
|
||||
double coord[8] =
|
||||
{
|
||||
-pad_w + pad_dy, -pad_h - pad_dx,
|
||||
-pad_w - pad_dy, pad_h + pad_dx,
|
||||
+pad_w - pad_dy, -pad_h + pad_dx,
|
||||
+pad_w + pad_dy, pad_h - pad_dx
|
||||
};
|
||||
|
||||
for( int i = 0; i < 4; i++ )
|
||||
{
|
||||
RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() );
|
||||
coord[i * 2] += pad_x;
|
||||
coord[i * 2 + 1] += pad_y;
|
||||
}
|
||||
|
||||
int lines;
|
||||
|
||||
lines = aTinLayer->NewContour();
|
||||
|
||||
if( lines < 0 )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
if( !aTinLayer->AddVertex( lines, coord[0], -coord[1] ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
if( !aTinLayer->AddVertex( lines, coord[4], -coord[5] ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
if( !aTinLayer->AddVertex( lines, coord[6], -coord[7] ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
if( !aTinLayer->AddVertex( lines, coord[2], -coord[3] ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
if( !aTinLayer->EnsureWinding( lines, false ) )
|
||||
throw( std::runtime_error( aTinLayer->GetError() ) );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlPad( PAD* aPad )
|
||||
void EXPORTER_PCB_VRML::ExportVrmlPadHole( PAD* aPad )
|
||||
{
|
||||
double hole_drill_w = (double) aPad->GetDrillSize().x * m_BoardToVrmlScale / 2.0;
|
||||
double hole_drill_h = (double) aPad->GetDrillSize().y * m_BoardToVrmlScale / 2.0;
|
||||
|
@ -1256,7 +1104,7 @@ void MODEL_VRML::ExportVrmlPad( PAD* aPad )
|
|||
{
|
||||
bool pth = false;
|
||||
|
||||
if( ( aPad->GetAttribute() != PAD_ATTRIB_NPTH ) && !m_plainPCB )
|
||||
if( ( aPad->GetAttribute() != PAD_ATTRIB_NPTH ) )
|
||||
pth = true;
|
||||
|
||||
if( aPad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
|
||||
|
@ -1296,24 +1144,6 @@ void MODEL_VRML::ExportVrmlPad( PAD* aPad )
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
if( m_plainPCB )
|
||||
return;
|
||||
|
||||
// The pad proper, on the selected layers
|
||||
LSET layer_mask = aPad->GetLayerSet();
|
||||
|
||||
if( layer_mask[B_Paste] )
|
||||
ExportVrmlPadshape( &m_bot_paste, B_Paste, aPad );
|
||||
|
||||
if( layer_mask[B_Cu] )
|
||||
ExportVrmlPadshape( &m_bot_copper, B_Cu, aPad );
|
||||
|
||||
if( layer_mask[F_Paste] )
|
||||
ExportVrmlPadshape( &m_top_paste, F_Paste, aPad );
|
||||
|
||||
if( layer_mask[F_Cu] )
|
||||
ExportVrmlPadshape( &m_top_copper, F_Cu, aPad );
|
||||
}
|
||||
|
||||
|
||||
|
@ -1356,10 +1186,8 @@ static void compose_quat( double q1[4], double q2[4], double qr[4] )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint,
|
||||
void EXPORTER_PCB_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint,
|
||||
std::ostream* aOutputFile )
|
||||
{
|
||||
if( !m_plainPCB )
|
||||
{
|
||||
// Reference and value
|
||||
if( aFootprint->Reference().IsVisible() )
|
||||
|
@ -1386,11 +1214,40 @@ void MODEL_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint,
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export pads
|
||||
for( PAD* pad : aFootprint->Pads() )
|
||||
ExportVrmlPad( pad );
|
||||
ExportVrmlPadHole( pad );
|
||||
|
||||
int maxError = Millimeter2iu( 0.005 );
|
||||
SHAPE_POLY_SET poly;
|
||||
VRML_LAYER* v_layer[4] = { &m_top_copper, &m_bot_copper, &m_top_paste, &m_bot_paste };
|
||||
PCB_LAYER_ID pcb_layer[4] = { F_Cu, B_Cu, F_Paste, B_Paste };
|
||||
|
||||
for( int ly = 0; ly < 4; ly++ ) // Output F_Cu, B_Cu, F_Paste, B_Paste
|
||||
{
|
||||
poly.RemoveAllContours();
|
||||
aFootprint->TransformPadsWithClearanceToPolygon( poly, pcb_layer[ly], 0,
|
||||
maxError, ERROR_INSIDE,
|
||||
true, false, false );
|
||||
for( int i = 0; i < poly.OutlineCount(); i++ )
|
||||
{
|
||||
const SHAPE_LINE_CHAIN& outline = poly.COutline( i );
|
||||
|
||||
int seg =v_layer[ly]->NewContour();
|
||||
|
||||
for( int j = 0; j < outline.PointCount(); j++ )
|
||||
{
|
||||
if( !v_layer[ly]->AddVertex( seg, (double) outline.CPoint( j ).x * m_BoardToVrmlScale,
|
||||
-( (double) outline.CPoint( j ).y * m_BoardToVrmlScale ) ) )
|
||||
{
|
||||
throw( std::runtime_error( v_layer[ly]->GetError() ) );
|
||||
}
|
||||
}
|
||||
|
||||
v_layer[ly]->EnsureWinding( seg, false );
|
||||
}
|
||||
}
|
||||
|
||||
bool isFlipped = aFootprint->GetLayer() == B_Cu;
|
||||
|
||||
|
@ -1561,13 +1418,13 @@ void MODEL_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint,
|
|||
|
||||
bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit,
|
||||
bool aExport3DFiles, bool aUseRelativePaths,
|
||||
bool aUsePlainPCB, const wxString& a3D_Subdir,
|
||||
const wxString& a3D_Subdir,
|
||||
double aXRef, double aYRef )
|
||||
{
|
||||
BOARD* pcb = GetBoard();
|
||||
bool success = true;
|
||||
|
||||
MODEL_VRML model3d;
|
||||
EXPORTER_PCB_VRML model3d;
|
||||
model_vrml = &model3d;
|
||||
model3d.m_Pcb = GetBoard();
|
||||
model3d.SetScale( aMMtoWRMLunit );
|
||||
|
@ -1587,9 +1444,6 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMt
|
|||
model3d.SetOffset( -aXRef, aYRef );
|
||||
}
|
||||
|
||||
// plain PCB or else PCB with copper and silkscreen
|
||||
model3d.m_plainPCB = aUsePlainPCB;
|
||||
|
||||
try
|
||||
{
|
||||
// Preliminary computation: the z value for each layer
|
||||
|
@ -1598,19 +1452,16 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMt
|
|||
// board edges and cutouts
|
||||
model3d.ExportVrmlBoard();
|
||||
|
||||
// Drawing and text on the board
|
||||
if( !aUsePlainPCB )
|
||||
// Draw solder mask layer
|
||||
model3d.ExportVrmlSolderMask();
|
||||
|
||||
// Drawing and text on the board
|
||||
if( !aUsePlainPCB )
|
||||
model3d.ExportVrmlDrawings();
|
||||
|
||||
// Export vias and trackage
|
||||
model3d.ExportVrmlTracks();
|
||||
|
||||
// Export zone fills
|
||||
if( !aUsePlainPCB )
|
||||
model3d.ExportVrmlZones();
|
||||
|
||||
if( model3d.m_UseInlineModelsInBrdfile )
|
||||
|
@ -1641,7 +1492,7 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMt
|
|||
return success;
|
||||
}
|
||||
|
||||
void MODEL_VRML::ExportFp3DModelsAsLinkedFile( const wxString& aFullFileName )
|
||||
void EXPORTER_PCB_VRML::ExportFp3DModelsAsLinkedFile( const wxString& aFullFileName )
|
||||
{
|
||||
// check if the 3D Subdir exists - create if not
|
||||
wxFileName subdir( m_Subdir3DFpModels, "" );
|
||||
|
@ -1690,7 +1541,7 @@ void MODEL_VRML::ExportFp3DModelsAsLinkedFile( const wxString& aFullFileName )
|
|||
CLOSE_STREAM( output_file );
|
||||
}
|
||||
|
||||
SGNODE* MODEL_VRML::getSGColor( VRML_COLOR_INDEX colorIdx )
|
||||
SGNODE* EXPORTER_PCB_VRML::getSGColor( VRML_COLOR_INDEX colorIdx )
|
||||
{
|
||||
if( colorIdx == -1 )
|
||||
colorIdx = VRML_COLOR_PCB;
|
||||
|
@ -1717,7 +1568,7 @@ SGNODE* MODEL_VRML::getSGColor( VRML_COLOR_INDEX colorIdx )
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::create_vrml_plane( IFSG_TRANSFORM& PcbOutput, VRML_COLOR_INDEX colorID,
|
||||
void EXPORTER_PCB_VRML::create_vrml_plane( IFSG_TRANSFORM& PcbOutput, VRML_COLOR_INDEX colorID,
|
||||
VRML_LAYER* layer, double top_z, bool aTopPlane )
|
||||
{
|
||||
std::vector< double > vertices;
|
||||
|
@ -1775,7 +1626,7 @@ void MODEL_VRML::create_vrml_plane( IFSG_TRANSFORM& PcbOutput, VRML_COLOR_INDEX
|
|||
}
|
||||
|
||||
|
||||
void MODEL_VRML::create_vrml_shell( IFSG_TRANSFORM& PcbOutput, VRML_COLOR_INDEX colorID,
|
||||
void EXPORTER_PCB_VRML::create_vrml_shell( IFSG_TRANSFORM& PcbOutput, VRML_COLOR_INDEX colorID,
|
||||
VRML_LAYER* layer, double top_z, double bottom_z )
|
||||
{
|
||||
std::vector< double > vertices;
|
||||
|
|
|
@ -44,19 +44,17 @@ public:
|
|||
* false to include them in the vrml board file
|
||||
* @param aUseRelativePaths = true to use fp 3D relative paths,
|
||||
* false to use absolute paths
|
||||
* @param aUsePlainPCB = true to create a body board shape only,
|
||||
* false to create a full board shape with tracks, vias ...
|
||||
* @param a3D_Subdir is the folder to copy 3D fp models
|
||||
* @param aXRef = X position of board (in mm)
|
||||
* @param aYRef = Y position of board (in mm)
|
||||
*/
|
||||
bool ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit,
|
||||
bool aExport3DFiles, bool aUseRelativePaths,
|
||||
bool aUsePlainPCB, const wxString& a3D_Subdir,
|
||||
const wxString& a3D_Subdir,
|
||||
double aXRef, double aYRef )
|
||||
{
|
||||
return ExportVRML( aFullFileName, aMMtoWRMLunit,
|
||||
aExport3DFiles, aUseRelativePaths,
|
||||
aUsePlainPCB, a3D_Subdir, aXRef, aYRef);
|
||||
a3D_Subdir, aXRef, aYRef);
|
||||
}
|
||||
};
|
|
@ -103,7 +103,7 @@ struct VRML_COLOR
|
|||
|
||||
|
||||
// Handle the board ans its board items to convert them to a VRML representation:
|
||||
class MODEL_VRML
|
||||
class EXPORTER_PCB_VRML
|
||||
{
|
||||
private:
|
||||
VRML_COLOR vrml_colors_list[VRML_COLOR_LAST];
|
||||
|
@ -134,9 +134,6 @@ public:
|
|||
S3D_CACHE* m_Cache3Dmodels;
|
||||
BOARD* m_Pcb;
|
||||
|
||||
|
||||
bool m_plainPCB;
|
||||
|
||||
/* true to use VRML inline{} syntax for footprint 3D models, like:
|
||||
* Inline { url "F:/tmp/pic_programmer/shapes3D/DIP-18_W7.62mm_Socket.wrl" }
|
||||
* false to merge VRML 3D modeles in the .wrl board file
|
||||
|
@ -169,8 +166,8 @@ public:
|
|||
LAYER_NUM m_text_layer;
|
||||
int m_text_width;
|
||||
|
||||
MODEL_VRML();
|
||||
~MODEL_VRML();
|
||||
EXPORTER_PCB_VRML();
|
||||
~EXPORTER_PCB_VRML();
|
||||
|
||||
VRML_COLOR& GetColor( VRML_COLOR_INDEX aIndex )
|
||||
{
|
||||
|
@ -226,12 +223,10 @@ public:
|
|||
|
||||
void ExportVrmlFpShape( FP_SHAPE* aOutline, FOOTPRINT* aFootprint );
|
||||
|
||||
void ExportVrmlPad( PAD* aPad );
|
||||
void ExportVrmlPadHole( PAD* aPad );
|
||||
|
||||
void ExportVrmlFootprint( FOOTPRINT* aFootprint, std::ostream* aOutputFile );
|
||||
|
||||
void ExportVrmlPadshape( VRML_LAYER* aTinLayer, PCB_LAYER_ID aPcbLayer, PAD* aPad );
|
||||
|
||||
void ExportVrmlDrawings();
|
||||
|
||||
void ExportVrmlArc( LAYER_NUM layer,
|
||||
|
|
|
@ -627,9 +627,6 @@ public:
|
|||
* @param aExport3DFiles true to copy 3D shapes in the subir a3D_Subdir
|
||||
* @param aUseRelativePaths set to true to use relative paths instead of absolute paths
|
||||
* in the board VRML file URLs.
|
||||
* @param aUsePlainPCB set to true to export a board with no copper or silkscreen;
|
||||
* this is useful for generating a VRML file which can be
|
||||
* converted to a STEP model.
|
||||
* @param a3D_Subdir sub directory where 3D shapes files are copied. This is only used
|
||||
* when aExport3DFiles == true.
|
||||
* @param aXRef X value of PCB (0,0) reference point.
|
||||
|
@ -637,7 +634,7 @@ public:
|
|||
* @return true if Ok.
|
||||
*/
|
||||
bool ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit,
|
||||
bool aExport3DFiles, bool aUseRelativePaths, bool aUsePlainPCB,
|
||||
bool aExport3DFiles, bool aUseRelativePaths,
|
||||
const wxString& a3D_Subdir, double aXRef, double aYRef );
|
||||
|
||||
/**
|
||||
|
|
|
@ -300,9 +300,6 @@ PCBNEW_SETTINGS::PCBNEW_SETTINGS()
|
|||
m_params.emplace_back( new PARAM<bool>( "export_vrml.use_relative_paths",
|
||||
&m_ExportVrml.use_relative_paths, false ) );
|
||||
|
||||
m_params.emplace_back( new PARAM<bool>( "export_vrml.use_plain_pcb",
|
||||
&m_ExportVrml.use_plain_pcb, false ) );
|
||||
|
||||
m_params.emplace_back( new PARAM<int>( "export_vrml.ref_units",
|
||||
&m_ExportVrml.ref_units, 0 ) );
|
||||
|
||||
|
@ -646,7 +643,6 @@ bool PCBNEW_SETTINGS::MigrateFromLegacy( wxConfigBase* aCfg )
|
|||
ret &= fromLegacy<int>( aCfg, "VrmlExportUnit", "export_vrml.units" );
|
||||
ret &= fromLegacy<bool>( aCfg, "VrmlExportCopyFiles", "export_vrml.copy_3d_models" );
|
||||
ret &= fromLegacy<bool>( aCfg, "VrmlUseRelativePaths", "export_vrml.use_relative_paths" );
|
||||
ret &= fromLegacy<bool>( aCfg, "VrmlUsePlainPCB", "export_vrml.use_plain_pcb" );
|
||||
ret &= fromLegacy<int>( aCfg, "VrmlRefUnits", "export_vrml.ref_units" );
|
||||
ret &= fromLegacy<double>( aCfg, "VrmlRefX", "export_vrml.ref_x" );
|
||||
ret &= fromLegacy<double>( aCfg, "VrmlRefY", "export_vrml.ref_y" );
|
||||
|
|
|
@ -124,7 +124,6 @@ public:
|
|||
int units;
|
||||
bool copy_3d_models;
|
||||
bool use_relative_paths;
|
||||
bool use_plain_pcb;
|
||||
int ref_units;
|
||||
double ref_x;
|
||||
double ref_y;
|
||||
|
|
|
@ -270,14 +270,14 @@ bool ExportSpecctraDSN( wxString& aFullFilename )
|
|||
|
||||
bool ExportVRML( const wxString& aFullFileName, double aMMtoWRMLunit,
|
||||
bool aExport3DFiles, bool aUseRelativePaths,
|
||||
bool aUsePlainPCB, const wxString& a3D_Subdir,
|
||||
const wxString& a3D_Subdir,
|
||||
double aXRef, double aYRef )
|
||||
{
|
||||
if( s_PcbEditFrame )
|
||||
{
|
||||
bool ok = s_PcbEditFrame->ExportVRML_File( aFullFileName, aMMtoWRMLunit,
|
||||
aExport3DFiles, aUseRelativePaths,
|
||||
aUsePlainPCB, a3D_Subdir, aXRef, aYRef );
|
||||
a3D_Subdir, aXRef, aYRef );
|
||||
return ok;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -87,7 +87,7 @@ bool ExportSpecctraDSN( wxString& aFullFilename );
|
|||
*/
|
||||
bool ExportVRML( const wxString& aFullFileName, double aMMtoWRMLunit,
|
||||
bool aExport3DFiles, bool aUseRelativePaths,
|
||||
bool aUsePlainPCB, const wxString& a3D_Subdir,
|
||||
const wxString& a3D_Subdir,
|
||||
double aXRef, double aYRef );
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue