KiCad PCB EDA Suite
Loading...
Searching...
No Matches
panel_fp_properties_3d_model.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2015 Dick Hollenbeck, [email protected]
6 * Copyright (C) 2008 Wayne Stambaugh <[email protected]>
7 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
28
29#include <algorithm>
32#include <env_vars.h>
33#include <bitmaps.h>
36#include <widgets/wx_grid.h>
38#include <board.h>
39#include <footprint.h>
44#include "filename_resolver.h"
45#include <pgm_base.h>
46#include <kiplatform/ui.h>
51#include <project_pcb.h>
56
57#include <reporter.h>
58
59#include <wx/defs.h>
60#include <wx/filedlg.h>
61#include <wx/msgdlg.h>
62
69
70
71wxDEFINE_EVENT( wxCUSTOM_PANEL_SHOWN_EVENT, wxCommandEvent );
72
74 FOOTPRINT* aFootprint,
75 DIALOG_SHIM* aDialogParent,
76 PANEL_EMBEDDED_FILES* aFilesPanel,
77 wxWindow* aParent ) :
79 m_parentDialog( aDialogParent ),
80 m_frame( aFrame ),
81 m_footprint( aFootprint ),
82 m_filesPanel( aFilesPanel ),
83 m_inSelect( false )
84{
85 m_splitter1->SetSashPosition( FromDIP( m_splitter1->GetSashPosition() ) );
86 m_splitter1->SetMinimumPaneSize( FromDIP( m_splitter1->GetMinimumPaneSize() ) );
87
88 GRID_TRICKS* trick = new GRID_TRICKS( m_modelsGrid, [this]( wxCommandEvent& aEvent )
89 {
90 OnAdd3DRow( aEvent );
91 } );
93 m_modelsGrid->PushEventHandler( trick );
94 m_modelsGrid->SetupColumnAutosizer( COL_FILENAME );
95
96 // Get the last 3D directory
98
99 if( cfg && cfg->m_LastFootprint3dDir.IsEmpty() )
100 {
101 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ),
102 &cfg->m_LastFootprint3dDir );
103 }
104
105 // Icon showing warning/error information
106 wxGridCellAttr* attr = new wxGridCellAttr;
107 attr->SetReadOnly();
108 m_modelsGrid->SetColAttr( COL_PROBLEM, attr );
109
110 // Filename
111 attr = new wxGridCellAttr;
112
113 if( cfg )
114 {
116 wxT( "*.*" ), true, m_frame->Prj().GetProjectPath(),
117 [this]( const wxString& aFile ) -> wxString
118 {
119 EMBEDDED_FILES::EMBEDDED_FILE* result = m_filesPanel->AddEmbeddedFile( aFile );
120
121 if( !result )
122 {
123 wxString msg = wxString::Format( _( "Error adding 3D model" ) );
124 wxMessageBox( msg, _( "Error" ), wxICON_ERROR | wxOK, this );
125 return wxString();
126 }
127
128 return result->GetLink();
129 } ) );
130 }
131
132 m_modelsGrid->SetColAttr( COL_FILENAME, attr );
133
134 // Show checkbox
135 attr = new wxGridCellAttr;
136 attr->SetRenderer( new wxGridCellBoolRenderer() );
137 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
138 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
139 m_modelsGrid->SetColAttr( COL_SHOWN, attr );
140 m_modelsGrid->SetWindowStyleFlag( m_modelsGrid->GetWindowStyle() & ~wxHSCROLL );
141
143
144 m_previewPane = new PANEL_PREVIEW_3D_MODEL( m_lowerPanel, m_frame, m_footprint, &m_shapes3D_list );
145
146 m_previewPane->SetEmbeddedFilesDelegate( m_filesPanel->GetLocalFiles() );
147
148 m_LowerSizer3D->Add( m_previewPane, 1, wxEXPAND, 5 );
149
150 // Configure button logos
151 m_button3DShapeAdd->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
152 m_button3DShapeBrowse->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
153 m_button3DShapeRemove->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
154
155 m_modelsGrid->Bind( wxEVT_GRID_CELL_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::on3DModelCellChanging, this );
156 Bind( wxEVT_SHOW, &PANEL_FP_PROPERTIES_3D_MODEL::onShowEvent, this );
157 m_parentDialog->Bind( wxEVT_ACTIVATE, &PANEL_FP_PROPERTIES_3D_MODEL::onDialogActivateEvent, this );
158
159 // Bind extrusion control events to update the 3D preview
160 m_componentHeightCtrl->Bind( wxEVT_TEXT, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
161 m_standoffHeightCtrl->Bind( wxEVT_TEXT, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
162 m_extrusionLayerChoice->Bind( wxEVT_CHOICE, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
163 m_extrusionColorSwatch->Bind( COLOR_SWATCH_CHANGED, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionColorChanged, this );
164 m_extrusionMaterialChoice->Bind( wxEVT_CHOICE, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionMaterialChanged, this );
165 m_showExtrusionCheckbox->Bind( wxEVT_CHECKBOX, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
166}
167
168
170{
171 // Delete the GRID_TRICKS.
172 m_modelsGrid->PopEventHandler( true );
173
174 m_modelsGrid->Unbind( wxEVT_GRID_CELL_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::on3DModelCellChanging, this );
175 // Unbind OnShowEvent to prevent unnecessary event handling.
176 Unbind( wxEVT_SHOW, &PANEL_FP_PROPERTIES_3D_MODEL::onShowEvent, this );
177
182 this );
185
186 // free the memory used by all models, otherwise models which were
187 // browsed but not used would consume memory
189
190 delete m_previewPane;
191}
192
193
195{
197
198 bool hasExtrusion = m_footprint->HasExtrudedBody();
199 m_enableExtrusionCheckbox->SetValue( hasExtrusion );
200
201 const EXTRUDED_3D_BODY* body = m_footprint->GetExtrudedBody();
202 m_showExtrusionCheckbox->SetValue( body ? body->m_show : true );
203 m_componentHeightCtrl->SetValue(
204 wxString::Format( wxT( "%.4f" ), body ? pcbIUScale.IUTomm( body->m_height ) : 0.0 ) );
205 m_standoffHeightCtrl->SetValue(
206 wxString::Format( wxT( "%.4f" ), body ? pcbIUScale.IUTomm( body->m_standoff ) : 0.0 ) );
207
209
210 m_extrusionLayerChoice->Clear();
211 m_extrusionLayerChoice->Append( _( "Auto" ) );
212 m_extrusionLayerChoice->Append( _( "Courtyard layer" ) );
213 m_extrusionLayerChoice->Append( _( "Fabrication layer" ) );
214 m_extrusionLayerChoice->Append( _( "Silkscreen layer" ) );
215 m_extrusionLayerChoice->Append( _( "Pin bounding box" ) );
216
217 PCB_LAYER_ID layer = body ? body->m_layer : UNDEFINED_LAYER;
218 int selection = 0; // Auto
219
220 for( size_t i = 0; i < m_extrusionLayers.size(); i++ )
221 {
222 if( m_extrusionLayers[i] == layer )
223 {
224 selection = static_cast<int>( i );
225 break;
226 }
227 }
228
229 m_extrusionLayerChoice->SetSelection( selection );
230
233
234 if( color == KIGFX::COLOR4D::UNSPECIFIED )
236
237 m_extrusionColorSwatch->SetSwatchColor( color, false );
238
239 m_extrusionMaterialChoice->SetSelection(
240 static_cast<int>( body ? body->m_material : EXTRUSION_MATERIAL::PLASTIC ) );
241
244
245 Layout();
246
247 if( GetSizer() )
248 GetSizer()->Fit( this );
249
250 return true;
251}
252
253
255{
256 if( !m_modelsGrid->CommitPendingChanges() )
257 return false;
258
259 if( m_enableExtrusionCheckbox->GetValue() )
260 {
261 double compHeight = 0.0;
262 double standoff = 0.0;
263 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
264 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
265
266 if( compHeight <= 0.0 )
267 {
268 wxMessageBox( _( "Component height must be greater than zero." ), _( "Extruded 3D Body" ),
269 wxOK | wxICON_WARNING, this );
270 return false;
271 }
272
273 if( standoff < 0.0 )
274 standoff = 0.0;
275
276 if( standoff >= compHeight )
277 {
278 wxMessageBox( _( "Standoff height must be less than the overall height." ), _( "Extruded 3D Body" ),
279 wxOK | wxICON_WARNING, this );
280 return false;
281 }
282
283 int sel = m_extrusionLayerChoice->GetSelection();
284
285 EXTRUDED_3D_BODY& body = m_footprint->EnsureExtrudedBody();
286 body.m_height = pcbIUScale.mmToIU( compHeight );
287 body.m_standoff = pcbIUScale.mmToIU( standoff );
288 body.m_layer = m_extrusionLayers[sel];
290 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
291
292 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
293
294 if( dummyFp && dummyFp->HasExtrudedBody() )
295 {
296 const EXTRUDED_3D_BODY* dummyBody = dummyFp->GetExtrudedBody();
297 body.m_scale = dummyBody->m_scale;
298 body.m_rotation = dummyBody->m_rotation;
299 body.m_offset = dummyBody->m_offset;
300 }
301
302 body.m_show = m_showExtrusionCheckbox->GetValue();
303 }
304 else
305 {
306 m_footprint->ClearExtrudedBody();
307 }
308
309 return true;
310}
311
312
314{
315 wxString default_path;
316 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &default_path );
317
318#ifdef __WINDOWS__
319 default_path.Replace( wxT( "/" ), wxT( "\\" ) );
320#endif
321
322 m_shapes3D_list.clear();
323 m_modelsGrid->ClearRows();
324
325 wxString origPath, alias, shortPath;
327
328 for( const FP_3DMODEL& model : m_footprint->Models() )
329 {
330 m_shapes3D_list.push_back( model );
331 origPath = model.m_Filename;
332
333 if( res && res->SplitAlias( origPath, alias, shortPath ) )
334 origPath = alias + wxT( ":" ) + shortPath;
335
336 m_modelsGrid->AppendRows( 1 );
337 int row = m_modelsGrid->GetNumberRows() - 1;
338 m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath );
339 m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
340
341 // Must be after the filename is set
343 }
344
345 select3DModel( 0 );
346
347 m_previewPane->UpdateDummyFootprint();
348 m_modelsGrid->SetGridWidthsDirty();
349
350 Layout();
351}
352
353
355{
356 m_inSelect = true;
357
358 aModelIdx = std::max( 0, aModelIdx );
359 aModelIdx = std::min( aModelIdx, m_modelsGrid->GetNumberRows() - 1 );
360
361 if( m_modelsGrid->GetNumberRows() )
362 {
363 m_modelsGrid->SelectRow( aModelIdx );
364 m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME );
365 }
366
367 m_previewPane->SetSelectedModel( aModelIdx );
368
369 m_inSelect = false;
370}
371
372
374{
375 if( !m_inSelect )
376 select3DModel( aEvent.GetRow() );
377}
378
379
381{
382 if( !aFilename->empty() )
383 {
384 bool hasAlias = false;
386
387 aFilename->Replace( wxT( "\n" ), wxT( "" ) );
388 aFilename->Replace( wxT( "\r" ), wxT( "" ) );
389 aFilename->Replace( wxT( "\t" ), wxT( "" ) );
390
391 res->ValidateFileName( *aFilename, hasAlias );
392
393 // If the user has specified an alias in the name then prepend ':'
394 if( hasAlias )
395 aFilename->insert( 0, wxT( ":" ) );
396
397#ifdef __WINDOWS__
398 // In KiCad files, filenames and paths are stored using Unix notation
399 aFilename->Replace( wxT( "\\" ), wxT( "/" ) );
400#endif
401 }
402}
403
404
406{
407 if( aEvent.GetCol() == COL_FILENAME )
408 updateValidateStatus( aEvent.GetRow() );
409}
410
411
413{
414 if( aEvent.GetCol() == COL_FILENAME )
415 {
416 wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME );
417
418 if( !filename.empty() )
419 {
420 cleanupFilename( &filename );
421
422 // Update the grid with the modified filename
423 m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename );
424 }
425
426 // Save the filename in the 3D shapes table
427 m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename;
428
429 // Update the validation status
430 updateValidateStatus( aEvent.GetRow() );
431 }
432 else if( aEvent.GetCol() == COL_SHOWN )
433 {
434 wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN );
435
436 m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) );
437 }
438
439 m_previewPane->UpdateDummyFootprint();
440 onModify();
441}
442
443
445{
446 if( !m_modelsGrid->CommitPendingChanges() )
447 return;
448
449 if( !m_modelsGrid->GetNumberRows() || m_shapes3D_list.empty() )
450 return;
451
452 wxArrayInt selectedRows = m_modelsGrid->GetSelectedRows();
453 wxGridCellCoordsArray selectedCells = m_modelsGrid->GetSelectedCells();
454 wxGridCellCoordsArray blockTopLeft = m_modelsGrid->GetSelectionBlockTopLeft();
455 wxGridCellCoordsArray blockBottomRight = m_modelsGrid->GetSelectionBlockBottomRight();
456
457 for( unsigned ii = 0; ii < selectedCells.GetCount(); ++ii )
458 selectedRows.Add( selectedCells[ii].GetRow() );
459
460 if( !blockTopLeft.IsEmpty() && !blockBottomRight.IsEmpty() )
461 {
462 for( int row = blockTopLeft[0].GetRow(); row <= blockBottomRight[0].GetRow(); ++row )
463 selectedRows.Add( row );
464 }
465
466 if( selectedRows.empty() && m_modelsGrid->GetGridCursorRow() >= 0 )
467 selectedRows.Add( m_modelsGrid->GetGridCursorRow() );
468
469 if( selectedRows.empty() )
470 {
471 wxBell();
472 return;
473 }
474
475 std::sort( selectedRows.begin(), selectedRows.end() );
476
477 int nextSelection = selectedRows.front();
478 int lastRow = -1;
479
480 // Don't allow selection until we call select3DModel(), below. Otherwise wxWidgets
481 // has a tendency to get its knickers in a knot....
482 m_inSelect = true;
483
484 m_modelsGrid->ClearSelection();
485
486 for( int ii = selectedRows.size() - 1; ii >= 0; --ii )
487 {
488 int row = selectedRows[ii];
489
490 if( row == lastRow )
491 continue;
492
493 lastRow = row;
494
495 if( row < 0 || row >= (int) m_shapes3D_list.size() )
496 continue;
497
498 // Not all files are embedded but this will ignore the ones that are not
499 m_filesPanel->RemoveEmbeddedFile( m_shapes3D_list[row].m_Filename );
500 m_shapes3D_list.erase( m_shapes3D_list.begin() + row );
501 m_modelsGrid->DeleteRows( row );
502 }
503
504 if( m_modelsGrid->GetNumberRows() > 0 )
505 nextSelection = std::min( nextSelection, m_modelsGrid->GetNumberRows() - 1 );
506 else
507 nextSelection = 0;
508
509 select3DModel( nextSelection ); // will clamp index within bounds
510 m_previewPane->UpdateDummyFootprint();
511 m_inSelect = false;
512
513 onModify();
514}
515
516
518{
519 if( !m_modelsGrid->CommitPendingChanges() )
520 return;
521
522 int selected = m_modelsGrid->GetGridCursorRow();
523
524 PROJECT& prj = m_frame->Prj();
528
529 wxString initialpath = prj.GetRString( PROJECT::VIEWER_3D_PATH );
530 wxString sidx = prj.GetRString( PROJECT::VIEWER_3D_FILTER_INDEX );
531 int filter = 0;
532
533 // If the PROJECT::VIEWER_3D_PATH hasn't been set yet, use the 3DMODEL_DIR environment
534 // variable and fall back to the project path if necessary.
535 if( initialpath.IsEmpty() )
536 {
537 if( !wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &initialpath )
538 || initialpath.IsEmpty() )
539 {
540 initialpath = prj.GetProjectPath();
541 }
542 }
543
544 if( !sidx.empty() )
545 {
546 long tmp;
547 sidx.ToLong( &tmp );
548
549 if( tmp > 0 && tmp <= INT_MAX )
550 filter = (int) tmp;
551 }
552
553 DIALOG_SELECT_3DMODEL dm( m_parentDialog, cache, &model, initialpath, filter );
554
555 // Use QuasiModal so that Configure3DPaths (and its help window) will work
556 int retval = dm.ShowQuasiModal();
557
558 if( retval != wxID_OK || model.m_Filename.empty() )
559 {
560 if( selected >= 0 )
561 {
562 select3DModel( selected );
563 updateValidateStatus( selected );
564 }
565
566 return;
567 }
568
569 if( dm.IsEmbedded3DModel() )
570 {
571 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
572 wxString footprintBasePath = wxEmptyString;
573
574 std::optional<LIBRARY_TABLE_ROW*> fpRow =
575 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
576 if( fpRow )
577 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
578
579 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
580 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
581 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
582
583 wxString fullPath = res->ResolvePath( model.m_Filename, footprintBasePath, std::move( embeddedFilesStack ) );
584 wxFileName fname( fullPath );
585
586 EMBEDDED_FILES::EMBEDDED_FILE* result = m_filesPanel->AddEmbeddedFile( fname.GetFullPath() ); ;
587
588 if( !result )
589 {
590
591 wxString msg = wxString::Format( _( "Error adding 3D model" ) );
592 wxMessageBox( msg, _( "Error" ), wxICON_ERROR | wxOK, this );
593 return;
594 }
595
596 model.m_Filename = result->GetLink();
597 }
598
599 prj.SetRString( PROJECT::VIEWER_3D_PATH, initialpath );
600 sidx = wxString::Format( wxT( "%i" ), filter );
602
603 wxString alias;
604 wxString shortPath;
605 wxString filename = model.m_Filename;
606
607 if( res && res->SplitAlias( filename, alias, shortPath ) )
608 filename = alias + wxT( ":" ) + shortPath;
609
610#ifdef __WINDOWS__
611 // In KiCad files, filenames and paths are stored using Unix notation
612 model.m_Filename.Replace( wxT( "\\" ), wxT( "/" ) );
613#endif
614
615 model.m_Show = true;
616 m_shapes3D_list.push_back( model );
617
618 int idx = m_modelsGrid->GetNumberRows();
619 m_modelsGrid->AppendRows( 1 );
620 m_modelsGrid->SetCellValue( idx, COL_FILENAME, filename );
621 m_modelsGrid->SetCellValue( idx, COL_SHOWN, wxT( "1" ) );
622
623 select3DModel( idx );
625
626 m_previewPane->UpdateDummyFootprint();
627 onModify();
628}
629
630
632{
633 m_modelsGrid->OnAddRow(
634 [&]() -> std::pair<int, int>
635 {
637
638 model.m_Show = true;
639 m_shapes3D_list.push_back( model );
640
641 int row = m_modelsGrid->GetNumberRows();
642 m_modelsGrid->AppendRows( 1 );
643 m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) );
644 m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" );
645
646 select3DModel( row );
648 onModify();
649
650 return { row, COL_FILENAME };
651 } );
652}
653
654
656{
657 int icon = 0;
658 wxString errStr;
659 wxString filename = m_modelsGrid->GetCellValue( aRow, COL_FILENAME );
660
661 if( wxGridCellEditor* cellEditor = m_modelsGrid->GetCellEditor( aRow, COL_FILENAME ) )
662 {
663 if( cellEditor->IsCreated() && cellEditor->GetWindow()->IsShown() )
664 filename = cellEditor->GetValue();
665
666 cellEditor->DecRef();
667 }
668
669 switch( validateModelExists( filename ) )
670 {
672 icon = 0;
673 errStr = "";
674 break;
675
677 icon = wxICON_WARNING;
678 errStr = _( "No filename entered" );
679 break;
680
682 icon = wxICON_ERROR;
683 errStr = _( "Illegal filename" );
684 break;
685
687 icon = wxICON_ERROR;
688 errStr = _( "File not found" );
689 break;
690
692 icon = wxICON_ERROR;
693 errStr = _( "Unable to open file" );
694 break;
695
696 default:
697 icon = wxICON_ERROR;
698 errStr = _( "Unknown error" );
699 break;
700 }
701
702 m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr );
703 m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM, new GRID_CELL_STATUS_ICON_RENDERER( icon ) );
704}
705
706
708{
709 if( aFilename.empty() )
711
712 bool hasAlias = false;
714
715 if( !resolv )
717
718 if( !resolv->ValidateFileName( aFilename, hasAlias ) )
720
721 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
722 wxString footprintBasePath = wxEmptyString;
723
724 std::optional<LIBRARY_TABLE_ROW*> fpRow =
725 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
726 if( fpRow )
727 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
728
729 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
730 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
731 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
732
733 wxString fullPath = resolv->ResolvePath( aFilename, footprintBasePath, std::move( embeddedFilesStack ) );
734
735 if( fullPath.IsEmpty() )
737
738 if( !wxFileName::IsFileReadable( fullPath ) )
740
742}
743
744
745void PANEL_FP_PROPERTIES_3D_MODEL::Cfg3DPath( wxCommandEvent& event )
746{
747 DIALOG_CONFIGURE_PATHS dlg( this );
748
749 if( dlg.ShowQuasiModal() == wxID_OK )
750 m_previewPane->UpdateDummyFootprint();
751}
752
753
754void PANEL_FP_PROPERTIES_3D_MODEL::OnUpdateUI( wxUpdateUIEvent& event )
755{
756 m_button3DShapeRemove->Enable( m_modelsGrid->GetNumberRows() > 0 );
757}
758
759
761{
762 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
763 dlg->OnModify();
764}
765
766
768{
769 postCustomPanelShownEventWithPredicate( static_cast<int>( aEvent.IsShown() ) );
770 aEvent.Skip();
771}
772
773
775{
776 postCustomPanelShownEventWithPredicate( aEvent.GetActive() && m_previewPane->IsShownOnScreen() );
777 aEvent.Skip();
778}
779
780
782{
783 wxCommandEvent event( wxCUSTOM_PANEL_SHOWN_EVENT );
784 event.SetEventObject( m_previewPane );
785 event.SetInt( static_cast<int>( predicate ) );
786 m_previewPane->ProcessWindowEvent( event );
787}
788
789
796
797
799{
801 onModify();
802 event.Skip();
803}
804
805
807{
810 onModify();
811 event.Skip();
812}
813
814
816{
817 bool enabled = m_enableExtrusionCheckbox->GetValue();
818 m_showExtrusionCheckbox->Enable( enabled );
819 m_componentHeightCtrl->Enable( enabled );
820 m_standoffHeightCtrl->Enable( enabled );
821 m_extrusionLayerChoice->Enable( enabled );
822 m_extrusionColorSwatch->Enable( enabled );
823 m_extrusionMaterialChoice->Enable( enabled );
824 m_buttonExportExtruded->Enable( enabled );
825
826 if( enabled )
827 {
828 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
829
830 if( dummyFp )
831 m_previewPane->SetExtrusionTransformMode( &dummyFp->EnsureExtrudedBody() );
832 }
833 else
834 {
835 m_previewPane->SetExtrusionTransformMode( nullptr );
836 }
837}
838
839
841{
842 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
843
844 if( !dummyFp || m_extrusionLayers.empty() )
845 return;
846
847 if( m_enableExtrusionCheckbox->GetValue() && m_showExtrusionCheckbox->GetValue() )
848 {
849 double compHeight = 0.0;
850 double standoff = 0.0;
851 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
852 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
853
854 int sel = m_extrusionLayerChoice->GetSelection();
855
856 EXTRUDED_3D_BODY& body = dummyFp->EnsureExtrudedBody();
857 body.m_height = pcbIUScale.mmToIU( compHeight );
858 body.m_standoff = pcbIUScale.mmToIU( standoff );
859 body.m_layer = m_extrusionLayers[sel];
861 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
862 }
863 else
864 {
865 dummyFp->ClearExtrudedBody();
866 }
867
868 m_previewPane->UpdateDummyFootprint( true );
869}
870
871
873{
875 {
876 EXTRUSION_MATERIAL mat = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
877 m_extrusionColorSwatch->SetSwatchColor( EXTRUDED_3D_BODY::GetDefaultColor( mat ), false );
878 }
879
881 onModify();
882 event.Skip();
883}
884
885
887{
888 double height = 0.0;
889 double standoff = 0.0;
890 m_componentHeightCtrl->GetValue().ToDouble( &height );
891 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
892
893 if( height <= 0.0 )
894 {
895 wxMessageBox( _( "Component height must be greater than zero." ), _( "Export Extruded Body" ),
896 wxOK | wxICON_WARNING, this );
897 return;
898 }
899
900 int layerSel = m_extrusionLayerChoice->GetSelection();
901
902 SHAPE_POLY_SET outline;
903 bool gotOutline =
904 GetExtrusionOutline( m_footprint, outline, m_extrusionLayers[layerSel] ) && outline.OutlineCount() > 0;
905
906 if( !gotOutline )
907 {
908 wxMessageBox( _( "No extrusion outline could be generated for this footprint." ), _( "Export Extruded Body" ),
909 wxOK | wxICON_WARNING, this );
910 return;
911 }
912
913 wxString defaultName = m_footprint->GetReference() + wxT( "_extruded" );
914
915 wxFileDialog dlg( this, _( "Export Extruded 3D Body" ), wxEmptyString, defaultName,
916 _( "STEP files" ) + wxT( " (*.step)|*.step|" ) + _( "GLB files" ) + wxT( " (*.glb)|*.glb|" )
917 + _( "STL files" ) + wxT( " (*.stl)|*.stl|" ) + _( "BREP files" )
918 + wxT( " (*.brep)|*.brep" ),
919 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
920
921 if( dlg.ShowModal() == wxID_CANCEL )
922 return;
923
924 wxString path = dlg.GetPath();
925 int filterIdx = dlg.GetFilterIndex();
926
927 bool bottom = m_footprint->IsFlipped();
928 VECTOR2D origin( m_footprint->GetPosition().x, m_footprint->GetPosition().y );
929
930 EXTRUSION_MATERIAL material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
931
932 KIGFX::COLOR4D c = m_extrusionColorSwatch->GetSwatchColor();
933
935 c = EXTRUDED_3D_BODY::GetDefaultColor( material );
936
937 uint32_t colorKey = EXTRUDED_3D_BODY::PackColorKey( c );
938
939 NULL_REPORTER reporter;
940 STEP_PCB_MODEL model( wxT( "extruded_body" ), &reporter );
941
942 if( !model.AddExtrudedBody( outline, bottom, standoff, height, origin, colorKey, material,
943 m_footprint->GetReference() ) )
944 {
945 wxMessageBox( _( "Failed to create extruded body geometry." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR,
946 this );
947 return;
948 }
949
950 if( standoff > 0.0 )
951 model.AddExtrudedPins( m_footprint, bottom, standoff, origin );
952
953 SHAPE_POLY_SET boardOutline( outline );
954 model.CreatePCB( boardOutline, origin, false );
955
956 bool ok = false;
957
958 switch( filterIdx )
959 {
960 case 0: ok = model.WriteSTEP( path, true, false ); break;
961 case 1: ok = model.WriteGLTF( path ); break;
962 case 2: ok = model.WriteSTL( path ); break;
963 case 3: ok = model.WriteBREP( path ); break;
964 }
965
966 if( !ok )
967 {
968 wxMessageBox( _( "Failed to write file." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR, this );
969 }
970}
bool GetExtrusionOutline(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aOutline, PCB_LAYER_ID aLayerOverride)
Get the extrusion outline polygon for a footprint in board coordinates.
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:68
KIGFX::COLOR4D m_color
Definition footprint.h:110
VECTOR3D m_offset
Definition footprint.h:116
PCB_LAYER_ID m_layer
Definition footprint.h:109
static KIGFX::COLOR4D GetDefaultColor(EXTRUSION_MATERIAL aMaterial)
Definition footprint.h:118
VECTOR3D m_rotation
Definition footprint.h:115
static uint32_t PackColorKey(const KIGFX::COLOR4D &aColor)
Definition footprint.h:137
VECTOR3D m_scale
Definition footprint.h:114
EXTRUSION_MATERIAL m_material
Definition footprint.h:111
Provide an extensible class to resolve 3D model paths.
bool ValidateFileName(const wxString &aFileName, bool &hasAlias) const
Return true if the given path is a valid aliased relative path.
wxString ResolvePath(const wxString &aFileName, const wxString &aWorkingPath, std::vector< const EMBEDDED_FILES * > aEmbeddedFilesStack)
Determine the full path of the given file name.
void SetProgramBase(PGM_BASE *aBase)
Set a pointer to the application's PGM_BASE instance used to extract the local env vars.
const EXTRUDED_3D_BODY * GetExtrudedBody() const
Definition footprint.h:398
bool HasExtrudedBody() const
Definition footprint.h:397
void ClearExtrudedBody()
Definition footprint.h:402
EXTRUDED_3D_BODY & EnsureExtrudedBody()
Editor for wxGrid cells that adds a file/folder browser to the grid input field.
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:61
void SetTooltipEnable(int aCol, bool aEnable=true)
Enable the tooltip for a column.
Definition grid_tricks.h:75
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:402
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false)
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
A singleton reporter that reports to nowhere.
Definition reporter.h:218
PANEL_FP_PROPERTIES_3D_MODEL_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(778, 420), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
void on3DModelCellChanging(wxGridEvent &aEvent)
void OnUpdateUI(wxUpdateUIEvent &event) override
void Cfg3DPath(wxCommandEvent &event) override
void OnEnableExtrusion(wxCommandEvent &event) override
void OnRemove3DModel(wxCommandEvent &event) override
virtual void onShowEvent(wxShowEvent &aEvent)
void On3DModelCellChanged(wxGridEvent &aEvent) override
virtual void onDialogActivateEvent(wxActivateEvent &aEvent)
std::vector< PCB_LAYER_ID > m_extrusionLayers
PANEL_FP_PROPERTIES_3D_MODEL(PCB_BASE_EDIT_FRAME *aFrame, FOOTPRINT *aFootprint, DIALOG_SHIM *aDialogParent, PANEL_EMBEDDED_FILES *aFilesPanel, wxWindow *aParent)
void postCustomPanelShownEventWithPredicate(bool predicate)
void OnExportExtrudedModel(wxCommandEvent &event) override
MODEL_VALIDATE_ERRORS validateModelExists(const wxString &aFilename)
void OnAdd3DModel(wxCommandEvent &event) override
void OnAdd3DRow(wxCommandEvent &event) override
void onExtrusionColorChanged(wxCommandEvent &event)
void onExtrusionControlChanged(wxCommandEvent &event)
void onExtrusionMaterialChanged(wxCommandEvent &event)
void On3DModelSelected(wxGridEvent &) override
wxString m_LastFootprint3dDir
Common, abstract interface for edit frames.
static S3D_CACHE * Get3DCacheManager(PROJECT *aProject, bool updateProjDir=false)
Return a pointer to an instance of the 3D cache manager.
static FILENAME_RESOLVER * Get3DFilenameResolver(PROJECT *aProject)
Accessor for 3D path resolver.
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
Definition project.h:66
@ VIEWER_3D_FILTER_INDEX
Definition project.h:226
@ VIEWER_3D_PATH
Definition project.h:225
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:187
virtual void SetRString(RSTRING_T aStringId, const wxString &aString)
Store a "retained string", which is any session and project specific string identified in enum RSTRIN...
Definition project.cpp:359
virtual const wxString & GetRString(RSTRING_T aStringId)
Return a "retained string", which is any session and project specific string identified in enum RSTRI...
Definition project.cpp:370
Cache for storing the 3D shapes.
Definition 3d_cache.h:57
void FlushCache(bool closePlugins=true)
Free all data in the cache and by default closes all plugins.
Definition 3d_cache.cpp:557
FILENAME_RESOLVER * GetResolver() noexcept
Definition 3d_cache.cpp:545
Represent a set of closed polygons.
int OutlineCount() const
Return the number of outlines in the set.
#define _(s)
Declaration of the eda_3d_viewer class.
Functions related to environment variables, including help functions.
EXTRUSION_MATERIAL
Definition footprint.h:95
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_CrtYd
Definition layer_ids.h:116
@ UNSELECTED_LAYER
Definition layer_ids.h:62
@ F_Fab
Definition layer_ids.h:119
@ F_SilkS
Definition layer_ids.h:100
@ UNDEFINED_LAYER
Definition layer_ids.h:61
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:77
wxDEFINE_EVENT(wxCUSTOM_PANEL_SHOWN_EVENT, wxCommandEvent)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
T * GetAppSettings(const char *aFilename)
std::string path
KIBIS_MODEL * model
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< double > VECTOR2D
Definition vector2d.h:686