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, see <https://www.gnu.org/licenses/>.
21 */
22
24
25#include <algorithm>
28#include <env_vars.h>
29#include <bitmaps.h>
32#include <widgets/wx_grid.h>
34#include <board.h>
35#include <footprint.h>
40#include "filename_resolver.h"
41#include <pgm_base.h>
42#include <kiplatform/ui.h>
47#include <project_pcb.h>
52
53#include <reporter.h>
54
55#include <wx/defs.h>
56#include <wx/filedlg.h>
57#include <wx/msgdlg.h>
58
65
66
67wxDEFINE_EVENT( wxCUSTOM_PANEL_SHOWN_EVENT, wxCommandEvent );
68
70 FOOTPRINT* aFootprint,
71 DIALOG_SHIM* aDialogParent,
72 PANEL_EMBEDDED_FILES* aFilesPanel,
73 wxWindow* aParent ) :
75 m_parentDialog( aDialogParent ),
76 m_frame( aFrame ),
77 m_footprint( aFootprint ),
78 m_filesPanel( aFilesPanel ),
79 m_inSelect( false )
80{
81 m_splitter1->SetSashPosition( FromDIP( m_splitter1->GetSashPosition() ) );
82 m_splitter1->SetMinimumPaneSize( FromDIP( m_splitter1->GetMinimumPaneSize() ) );
83
84 GRID_TRICKS* trick = new GRID_TRICKS( m_modelsGrid, [this]( wxCommandEvent& aEvent )
85 {
86 OnAdd3DRow( aEvent );
87 } );
89 m_modelsGrid->PushEventHandler( trick );
90 m_modelsGrid->SetupColumnAutosizer( COL_FILENAME );
91
92 // Get the last 3D directory
94
95 if( cfg && cfg->m_LastFootprint3dDir.IsEmpty() )
96 {
97 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ),
99 }
100
101 // Icon showing warning/error information
102 wxGridCellAttr* attr = new wxGridCellAttr;
103 attr->SetReadOnly();
104 m_modelsGrid->SetColAttr( COL_PROBLEM, attr );
105
106 // Filename
107 attr = new wxGridCellAttr;
108
109 if( cfg )
110 {
112 wxT( "*.*" ), true, m_frame->Prj().GetProjectPath(),
113 [this]( const wxString& aFile ) -> wxString
114 {
115 EMBEDDED_FILES::EMBEDDED_FILE* result = m_filesPanel->AddEmbeddedFile( aFile );
116
117 if( !result )
118 {
119 wxString msg = wxString::Format( _( "Error adding 3D model" ) );
120 wxMessageBox( msg, _( "Error" ), wxICON_ERROR | wxOK, this );
121 return wxString();
122 }
123
124 return result->GetLink();
125 } ) );
126 }
127
128 m_modelsGrid->SetColAttr( COL_FILENAME, attr );
129
130 // Show checkbox
131 attr = new wxGridCellAttr;
132 attr->SetRenderer( new wxGridCellBoolRenderer() );
133 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
134 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
135 m_modelsGrid->SetColAttr( COL_SHOWN, attr );
136 m_modelsGrid->SetWindowStyleFlag( m_modelsGrid->GetWindowStyle() & ~wxHSCROLL );
137
139
140 m_previewPane = new PANEL_PREVIEW_3D_MODEL( m_lowerPanel, m_frame, m_footprint, &m_shapes3D_list );
141
142 m_previewPane->SetEmbeddedFilesDelegate( m_filesPanel->GetLocalFiles() );
143
144 m_LowerSizer3D->Add( m_previewPane, 1, wxEXPAND, 5 );
145
146 // Configure button logos
147 m_button3DShapeAdd->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
148 m_button3DShapeBrowse->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
149 m_button3DShapeRemove->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
150
151 m_modelsGrid->Bind( wxEVT_GRID_CELL_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::on3DModelCellChanging, this );
152 Bind( wxEVT_SHOW, &PANEL_FP_PROPERTIES_3D_MODEL::onShowEvent, this );
153 m_parentDialog->Bind( wxEVT_ACTIVATE, &PANEL_FP_PROPERTIES_3D_MODEL::onDialogActivateEvent, this );
154
155 // Bind extrusion control events to update the 3D preview
156 m_componentHeightCtrl->Bind( wxEVT_TEXT, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
157 m_standoffHeightCtrl->Bind( wxEVT_TEXT, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
158 m_extrusionLayerChoice->Bind( wxEVT_CHOICE, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
159 m_extrusionColorSwatch->Bind( COLOR_SWATCH_CHANGED, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionColorChanged, this );
160 m_extrusionMaterialChoice->Bind( wxEVT_CHOICE, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionMaterialChanged, this );
161 m_showExtrusionCheckbox->Bind( wxEVT_CHECKBOX, &PANEL_FP_PROPERTIES_3D_MODEL::onExtrusionControlChanged, this );
162}
163
164
166{
167 // Delete the GRID_TRICKS.
168 m_modelsGrid->PopEventHandler( true );
169
170 m_modelsGrid->Unbind( wxEVT_GRID_CELL_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::on3DModelCellChanging, this );
171 // Unbind OnShowEvent to prevent unnecessary event handling.
172 Unbind( wxEVT_SHOW, &PANEL_FP_PROPERTIES_3D_MODEL::onShowEvent, this );
173
178 this );
181
182 // free the memory used by all models, otherwise models which were
183 // browsed but not used would consume memory
185
186 delete m_previewPane;
187}
188
189
191{
193
194 bool hasExtrusion = m_footprint->HasExtrudedBody();
195 m_enableExtrusionCheckbox->SetValue( hasExtrusion );
196
197 const EXTRUDED_3D_BODY* body = m_footprint->GetExtrudedBody();
198 m_showExtrusionCheckbox->SetValue( body ? body->m_show : true );
199 m_componentHeightCtrl->SetValue(
200 wxString::Format( wxT( "%.4f" ), body ? pcbIUScale.IUTomm( body->m_height ) : 0.0 ) );
201 m_standoffHeightCtrl->SetValue(
202 wxString::Format( wxT( "%.4f" ), body ? pcbIUScale.IUTomm( body->m_standoff ) : 0.0 ) );
203
205
206 m_extrusionLayerChoice->Clear();
207 m_extrusionLayerChoice->Append( _( "Auto" ) );
208 m_extrusionLayerChoice->Append( _( "Courtyard layer" ) );
209 m_extrusionLayerChoice->Append( _( "Fabrication layer" ) );
210 m_extrusionLayerChoice->Append( _( "Silkscreen layer" ) );
211 m_extrusionLayerChoice->Append( _( "Pin bounding box" ) );
212
213 PCB_LAYER_ID layer = body ? body->m_layer : UNDEFINED_LAYER;
214 int selection = 0; // Auto
215
216 for( size_t i = 0; i < m_extrusionLayers.size(); i++ )
217 {
218 if( m_extrusionLayers[i] == layer )
219 {
220 selection = static_cast<int>( i );
221 break;
222 }
223 }
224
225 m_extrusionLayerChoice->SetSelection( selection );
226
229
230 if( color == KIGFX::COLOR4D::UNSPECIFIED )
232
233 m_extrusionColorSwatch->SetSwatchColor( color, false );
234
235 m_extrusionMaterialChoice->SetSelection(
236 static_cast<int>( body ? body->m_material : EXTRUSION_MATERIAL::PLASTIC ) );
237
240
241 Layout();
242
243 if( GetSizer() )
244 GetSizer()->Fit( this );
245
246 return true;
247}
248
249
251{
252 if( !m_modelsGrid->CommitPendingChanges() )
253 return false;
254
255 if( m_enableExtrusionCheckbox->GetValue() )
256 {
257 double compHeight = 0.0;
258 double standoff = 0.0;
259 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
260 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
261
262 if( compHeight <= 0.0 )
263 {
264 wxMessageBox( _( "Component height must be greater than zero." ), _( "Extruded 3D Body" ),
265 wxOK | wxICON_WARNING, this );
266 return false;
267 }
268
269 if( standoff < 0.0 )
270 standoff = 0.0;
271
272 if( standoff >= compHeight )
273 {
274 wxMessageBox( _( "Standoff height must be less than the overall height." ), _( "Extruded 3D Body" ),
275 wxOK | wxICON_WARNING, this );
276 return false;
277 }
278
279 int sel = m_extrusionLayerChoice->GetSelection();
280
281 EXTRUDED_3D_BODY& body = m_footprint->EnsureExtrudedBody();
282 body.m_height = pcbIUScale.mmToIU( compHeight );
283 body.m_standoff = pcbIUScale.mmToIU( standoff );
284 body.m_layer = m_extrusionLayers[sel];
286 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
287
288 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
289
290 if( dummyFp && dummyFp->HasExtrudedBody() )
291 {
292 const EXTRUDED_3D_BODY* dummyBody = dummyFp->GetExtrudedBody();
293 body.m_scale = dummyBody->m_scale;
294 body.m_rotation = dummyBody->m_rotation;
295 body.m_offset = dummyBody->m_offset;
296 }
297
298 body.m_show = m_showExtrusionCheckbox->GetValue();
299 }
300 else
301 {
302 m_footprint->ClearExtrudedBody();
303 }
304
305 return true;
306}
307
308
310{
311 wxString default_path;
312 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &default_path );
313
314#ifdef __WINDOWS__
315 default_path.Replace( wxT( "/" ), wxT( "\\" ) );
316#endif
317
318 m_shapes3D_list.clear();
319 m_modelsGrid->ClearRows();
320
321 wxString origPath, alias, shortPath;
323
324 for( const FP_3DMODEL& model : m_footprint->Models() )
325 {
326 m_shapes3D_list.push_back( model );
327 origPath = model.m_Filename;
328
329 if( res && res->SplitAlias( origPath, alias, shortPath ) )
330 origPath = alias + wxT( ":" ) + shortPath;
331
332 m_modelsGrid->AppendRows( 1 );
333 int row = m_modelsGrid->GetNumberRows() - 1;
334 m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath );
335 m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
336
337 // Must be after the filename is set
339 }
340
341 select3DModel( 0 );
342
343 m_previewPane->UpdateDummyFootprint();
344 m_modelsGrid->SetGridWidthsDirty();
345
346 Layout();
347}
348
349
351{
352 m_inSelect = true;
353
354 aModelIdx = std::max( 0, aModelIdx );
355 aModelIdx = std::min( aModelIdx, m_modelsGrid->GetNumberRows() - 1 );
356
357 if( m_modelsGrid->GetNumberRows() )
358 {
359 m_modelsGrid->SelectRow( aModelIdx );
360 m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME );
361 }
362
363 m_previewPane->SetSelectedModel( aModelIdx );
364
365 m_inSelect = false;
366}
367
368
370{
371 if( !m_inSelect )
372 select3DModel( aEvent.GetRow() );
373}
374
375
377{
378 if( !aFilename->empty() )
379 {
380 bool hasAlias = false;
382
383 aFilename->Replace( wxT( "\n" ), wxT( "" ) );
384 aFilename->Replace( wxT( "\r" ), wxT( "" ) );
385 aFilename->Replace( wxT( "\t" ), wxT( "" ) );
386
387 res->ValidateFileName( *aFilename, hasAlias );
388
389 // If the user has specified an alias in the name then prepend ':'
390 if( hasAlias )
391 aFilename->insert( 0, wxT( ":" ) );
392
393#ifdef __WINDOWS__
394 // In KiCad files, filenames and paths are stored using Unix notation
395 aFilename->Replace( wxT( "\\" ), wxT( "/" ) );
396#endif
397 }
398}
399
400
402{
403 if( aEvent.GetCol() == COL_FILENAME )
404 updateValidateStatus( aEvent.GetRow() );
405}
406
407
409{
410 if( aEvent.GetCol() == COL_FILENAME )
411 {
412 wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME );
413
414 if( !filename.empty() )
415 {
416 cleanupFilename( &filename );
417
418 // Update the grid with the modified filename
419 m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename );
420 }
421
422 // Save the filename in the 3D shapes table
423 m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename;
424
425 // Update the validation status
426 updateValidateStatus( aEvent.GetRow() );
427 }
428 else if( aEvent.GetCol() == COL_SHOWN )
429 {
430 wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN );
431
432 m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) );
433 }
434
435 m_previewPane->UpdateDummyFootprint();
436 onModify();
437}
438
439
441{
442 if( !m_modelsGrid->CommitPendingChanges() )
443 return;
444
445 if( !m_modelsGrid->GetNumberRows() || m_shapes3D_list.empty() )
446 return;
447
448 wxArrayInt selectedRows = m_modelsGrid->GetSelectedRows();
449 wxGridCellCoordsArray selectedCells = m_modelsGrid->GetSelectedCells();
450 wxGridCellCoordsArray blockTopLeft = m_modelsGrid->GetSelectionBlockTopLeft();
451 wxGridCellCoordsArray blockBottomRight = m_modelsGrid->GetSelectionBlockBottomRight();
452
453 for( unsigned ii = 0; ii < selectedCells.GetCount(); ++ii )
454 selectedRows.Add( selectedCells[ii].GetRow() );
455
456 if( !blockTopLeft.IsEmpty() && !blockBottomRight.IsEmpty() )
457 {
458 for( int row = blockTopLeft[0].GetRow(); row <= blockBottomRight[0].GetRow(); ++row )
459 selectedRows.Add( row );
460 }
461
462 if( selectedRows.empty() && m_modelsGrid->GetGridCursorRow() >= 0 )
463 selectedRows.Add( m_modelsGrid->GetGridCursorRow() );
464
465 if( selectedRows.empty() )
466 {
467 wxBell();
468 return;
469 }
470
471 std::sort( selectedRows.begin(), selectedRows.end() );
472
473 int nextSelection = selectedRows.front();
474 int lastRow = -1;
475
476 // Don't allow selection until we call select3DModel(), below. Otherwise wxWidgets
477 // has a tendency to get its knickers in a knot....
478 m_inSelect = true;
479
480 m_modelsGrid->ClearSelection();
481
482 for( int ii = selectedRows.size() - 1; ii >= 0; --ii )
483 {
484 int row = selectedRows[ii];
485
486 if( row == lastRow )
487 continue;
488
489 lastRow = row;
490
491 if( row < 0 || row >= (int) m_shapes3D_list.size() )
492 continue;
493
494 // Not all files are embedded but this will ignore the ones that are not
495 m_filesPanel->RemoveEmbeddedFile( m_shapes3D_list[row].m_Filename );
496 m_shapes3D_list.erase( m_shapes3D_list.begin() + row );
497 m_modelsGrid->DeleteRows( row );
498 }
499
500 if( m_modelsGrid->GetNumberRows() > 0 )
501 nextSelection = std::min( nextSelection, m_modelsGrid->GetNumberRows() - 1 );
502 else
503 nextSelection = 0;
504
505 select3DModel( nextSelection ); // will clamp index within bounds
506 m_previewPane->UpdateDummyFootprint();
507 m_inSelect = false;
508
509 onModify();
510}
511
512
514{
515 if( !m_modelsGrid->CommitPendingChanges() )
516 return;
517
518 int selected = m_modelsGrid->GetGridCursorRow();
519
520 PROJECT& prj = m_frame->Prj();
524
525 wxString initialpath = prj.GetRString( PROJECT::VIEWER_3D_PATH );
526 wxString sidx = prj.GetRString( PROJECT::VIEWER_3D_FILTER_INDEX );
527 int filter = 0;
528
529 // If the PROJECT::VIEWER_3D_PATH hasn't been set yet, use the 3DMODEL_DIR environment
530 // variable and fall back to the project path if necessary.
531 if( initialpath.IsEmpty() )
532 {
533 if( !wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &initialpath )
534 || initialpath.IsEmpty() )
535 {
536 initialpath = prj.GetProjectPath();
537 }
538 }
539
540 if( !sidx.empty() )
541 {
542 long tmp;
543 sidx.ToLong( &tmp );
544
545 if( tmp > 0 && tmp <= INT_MAX )
546 filter = (int) tmp;
547 }
548
549 DIALOG_SELECT_3DMODEL dm( m_parentDialog, cache, &model, initialpath, filter );
550
551 // Use QuasiModal so that Configure3DPaths (and its help window) will work
552 int retval = dm.ShowQuasiModal();
553
554 if( retval != wxID_OK || model.m_Filename.empty() )
555 {
556 if( selected >= 0 )
557 {
558 select3DModel( selected );
559 updateValidateStatus( selected );
560 }
561
562 return;
563 }
564
565 if( dm.IsEmbedded3DModel() )
566 {
567 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
568 wxString footprintBasePath = wxEmptyString;
569
570 std::optional<LIBRARY_TABLE_ROW*> fpRow =
571 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
572 if( fpRow )
573 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
574
575 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
576 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
577 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
578
579 wxString fullPath = res->ResolvePath( model.m_Filename, footprintBasePath, std::move( embeddedFilesStack ) );
580 wxFileName fname( fullPath );
581
582 EMBEDDED_FILES::EMBEDDED_FILE* result = m_filesPanel->AddEmbeddedFile( fname.GetFullPath() ); ;
583
584 if( !result )
585 {
586
587 wxString msg = wxString::Format( _( "Error adding 3D model" ) );
588 wxMessageBox( msg, _( "Error" ), wxICON_ERROR | wxOK, this );
589 return;
590 }
591
592 model.m_Filename = result->GetLink();
593 }
594
595 prj.SetRString( PROJECT::VIEWER_3D_PATH, initialpath );
596 sidx = wxString::Format( wxT( "%i" ), filter );
598
599 wxString alias;
600 wxString shortPath;
601 wxString filename = model.m_Filename;
602
603 if( res && res->SplitAlias( filename, alias, shortPath ) )
604 filename = alias + wxT( ":" ) + shortPath;
605
606#ifdef __WINDOWS__
607 // In KiCad files, filenames and paths are stored using Unix notation
608 model.m_Filename.Replace( wxT( "\\" ), wxT( "/" ) );
609#endif
610
611 model.m_Show = true;
612 m_shapes3D_list.push_back( model );
613
614 int idx = m_modelsGrid->GetNumberRows();
615 m_modelsGrid->AppendRows( 1 );
616 m_modelsGrid->SetCellValue( idx, COL_FILENAME, filename );
617 m_modelsGrid->SetCellValue( idx, COL_SHOWN, wxT( "1" ) );
618
619 select3DModel( idx );
621
622 m_previewPane->UpdateDummyFootprint();
623 onModify();
624}
625
626
628{
629 m_modelsGrid->OnAddRow(
630 [&]() -> std::pair<int, int>
631 {
633
634 model.m_Show = true;
635 m_shapes3D_list.push_back( model );
636
637 int row = m_modelsGrid->GetNumberRows();
638 m_modelsGrid->AppendRows( 1 );
639 m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) );
640 m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" );
641
642 select3DModel( row );
644 onModify();
645
646 return { row, COL_FILENAME };
647 } );
648}
649
650
652{
653 int icon = 0;
654 wxString errStr;
655 wxString filename = m_modelsGrid->GetCellValue( aRow, COL_FILENAME );
656
657 if( wxGridCellEditor* cellEditor = m_modelsGrid->GetCellEditor( aRow, COL_FILENAME ) )
658 {
659 if( cellEditor->IsCreated() && cellEditor->GetWindow()->IsShown() )
660 filename = cellEditor->GetValue();
661
662 cellEditor->DecRef();
663 }
664
665 switch( validateModelExists( filename ) )
666 {
668 icon = 0;
669 errStr = "";
670 break;
671
673 icon = wxICON_WARNING;
674 errStr = _( "No filename entered" );
675 break;
676
678 icon = wxICON_ERROR;
679 errStr = _( "Illegal filename" );
680 break;
681
683 icon = wxICON_ERROR;
684 errStr = _( "File not found" );
685 break;
686
688 icon = wxICON_ERROR;
689 errStr = _( "Unable to open file" );
690 break;
691
692 default:
693 icon = wxICON_ERROR;
694 errStr = _( "Unknown error" );
695 break;
696 }
697
698 m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr );
699 m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM, new GRID_CELL_STATUS_ICON_RENDERER( icon ) );
700}
701
702
704{
705 if( aFilename.empty() )
707
708 bool hasAlias = false;
710
711 if( !resolv )
713
714 if( !resolv->ValidateFileName( aFilename, hasAlias ) )
716
717 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
718 wxString footprintBasePath = wxEmptyString;
719
720 std::optional<LIBRARY_TABLE_ROW*> fpRow =
721 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
722 if( fpRow )
723 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
724
725 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
726 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
727 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
728
729 wxString fullPath = resolv->ResolvePath( aFilename, footprintBasePath, std::move( embeddedFilesStack ) );
730
731 if( fullPath.IsEmpty() )
733
734 if( !wxFileName::IsFileReadable( fullPath ) )
736
738}
739
740
741void PANEL_FP_PROPERTIES_3D_MODEL::Cfg3DPath( wxCommandEvent& event )
742{
743 DIALOG_CONFIGURE_PATHS dlg( this );
744
745 if( dlg.ShowQuasiModal() == wxID_OK )
746 m_previewPane->UpdateDummyFootprint();
747}
748
749
750void PANEL_FP_PROPERTIES_3D_MODEL::OnUpdateUI( wxUpdateUIEvent& event )
751{
752 m_button3DShapeRemove->Enable( m_modelsGrid->GetNumberRows() > 0 );
753}
754
755
757{
758 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
759 dlg->OnModify();
760}
761
762
764{
765 postCustomPanelShownEventWithPredicate( static_cast<int>( aEvent.IsShown() ) );
766 aEvent.Skip();
767}
768
769
771{
772 postCustomPanelShownEventWithPredicate( aEvent.GetActive() && m_previewPane->IsShownOnScreen() );
773 aEvent.Skip();
774}
775
776
778{
779 wxCommandEvent event( wxCUSTOM_PANEL_SHOWN_EVENT );
780 event.SetEventObject( m_previewPane );
781 event.SetInt( static_cast<int>( predicate ) );
782 m_previewPane->ProcessWindowEvent( event );
783}
784
785
792
793
795{
797 onModify();
798 event.Skip();
799}
800
801
803{
806 onModify();
807 event.Skip();
808}
809
810
812{
813 bool enabled = m_enableExtrusionCheckbox->GetValue();
814 m_showExtrusionCheckbox->Enable( enabled );
815 m_componentHeightCtrl->Enable( enabled );
816 m_standoffHeightCtrl->Enable( enabled );
817 m_extrusionLayerChoice->Enable( enabled );
818 m_extrusionColorSwatch->Enable( enabled );
819 m_extrusionMaterialChoice->Enable( enabled );
820 m_buttonExportExtruded->Enable( enabled );
821
822 if( enabled )
823 {
824 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
825
826 if( dummyFp )
827 m_previewPane->SetExtrusionTransformMode( &dummyFp->EnsureExtrudedBody() );
828 }
829 else
830 {
831 m_previewPane->SetExtrusionTransformMode( nullptr );
832 }
833}
834
835
837{
838 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
839
840 if( !dummyFp || m_extrusionLayers.empty() )
841 return;
842
843 if( m_enableExtrusionCheckbox->GetValue() && m_showExtrusionCheckbox->GetValue() )
844 {
845 double compHeight = 0.0;
846 double standoff = 0.0;
847 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
848 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
849
850 int sel = m_extrusionLayerChoice->GetSelection();
851
852 EXTRUDED_3D_BODY& body = dummyFp->EnsureExtrudedBody();
853 body.m_height = pcbIUScale.mmToIU( compHeight );
854 body.m_standoff = pcbIUScale.mmToIU( standoff );
855 body.m_layer = m_extrusionLayers[sel];
857 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
858 }
859 else
860 {
861 dummyFp->ClearExtrudedBody();
862 }
863
864 m_previewPane->UpdateDummyFootprint( true );
865}
866
867
869{
871 {
872 EXTRUSION_MATERIAL mat = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
873 m_extrusionColorSwatch->SetSwatchColor( EXTRUDED_3D_BODY::GetDefaultColor( mat ), false );
874 }
875
877 onModify();
878 event.Skip();
879}
880
881
883{
884 double height = 0.0;
885 double standoff = 0.0;
886 m_componentHeightCtrl->GetValue().ToDouble( &height );
887 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
888
889 if( height <= 0.0 )
890 {
891 wxMessageBox( _( "Component height must be greater than zero." ), _( "Export Extruded Body" ),
892 wxOK | wxICON_WARNING, this );
893 return;
894 }
895
896 int layerSel = m_extrusionLayerChoice->GetSelection();
897
898 SHAPE_POLY_SET outline;
899 bool gotOutline =
900 GetExtrusionOutline( m_footprint, outline, m_extrusionLayers[layerSel] ) && outline.OutlineCount() > 0;
901
902 if( !gotOutline )
903 {
904 wxMessageBox( _( "No extrusion outline could be generated for this footprint." ), _( "Export Extruded Body" ),
905 wxOK | wxICON_WARNING, this );
906 return;
907 }
908
909 wxString defaultName = m_footprint->GetReference() + wxT( "_extruded" );
910
911 wxFileDialog dlg( this, _( "Export Extruded 3D Body" ), wxEmptyString, defaultName,
912 _( "STEP files" ) + wxT( " (*.step)|*.step|" ) + _( "GLB files" ) + wxT( " (*.glb)|*.glb|" )
913 + _( "STL files" ) + wxT( " (*.stl)|*.stl|" ) + _( "BREP files" )
914 + wxT( " (*.brep)|*.brep" ),
915 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
916
917 if( dlg.ShowModal() == wxID_CANCEL )
918 return;
919
920 wxString path = dlg.GetPath();
921 int filterIdx = dlg.GetFilterIndex();
922
923 bool bottom = m_footprint->IsFlipped();
924 VECTOR2D origin( m_footprint->GetPosition().x, m_footprint->GetPosition().y );
925
926 EXTRUSION_MATERIAL material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
927
928 KIGFX::COLOR4D c = m_extrusionColorSwatch->GetSwatchColor();
929
931 c = EXTRUDED_3D_BODY::GetDefaultColor( material );
932
933 uint32_t colorKey = EXTRUDED_3D_BODY::PackColorKey( c );
934
936 STEP_PCB_MODEL model( wxT( "extruded_body" ), &reporter );
937
938 if( !model.AddExtrudedBody( outline, bottom, standoff, height, origin, colorKey, material,
939 m_footprint->GetReference() ) )
940 {
941 wxMessageBox( _( "Failed to create extruded body geometry." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR,
942 this );
943 return;
944 }
945
946 if( standoff > 0.0 )
947 model.AddExtrudedPins( m_footprint, bottom, standoff, origin );
948
949 SHAPE_POLY_SET boardOutline( outline );
950 model.CreatePCB( boardOutline, origin, false );
951
952 bool ok = false;
953
954 switch( filterIdx )
955 {
956 case 0: ok = model.WriteSTEP( path, true, false ); break;
957 case 1: ok = model.WriteGLTF( path ); break;
958 case 2: ok = model.WriteSTL( path ); break;
959 case 3: ok = model.WriteBREP( path ); break;
960 }
961
962 if( !ok )
963 {
964 wxMessageBox( _( "Failed to write file." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR, this );
965 }
966}
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:121
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:65
KIGFX::COLOR4D m_color
Definition footprint.h:108
VECTOR3D m_offset
Definition footprint.h:114
PCB_LAYER_ID m_layer
Definition footprint.h:107
static KIGFX::COLOR4D GetDefaultColor(EXTRUSION_MATERIAL aMaterial)
Definition footprint.h:116
VECTOR3D m_rotation
Definition footprint.h:113
static uint32_t PackColorKey(const KIGFX::COLOR4D &aColor)
Definition footprint.h:135
VECTOR3D m_scale
Definition footprint.h:112
EXTRUSION_MATERIAL m_material
Definition footprint.h:109
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:396
bool HasExtrudedBody() const
Definition footprint.h:395
void ClearExtrudedBody()
Definition footprint.h:400
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:57
void SetTooltipEnable(int aCol, bool aEnable=true)
Enable the tooltip for a column.
Definition grid_tricks.h:71
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
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:214
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)
std::vector< FP_3DMODEL > m_shapes3D_list
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:62
@ VIEWER_3D_FILTER_INDEX
Definition project.h:222
@ VIEWER_3D_PATH
Definition project.h:221
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
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:355
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:366
Cache for storing the 3D shapes.
Definition 3d_cache.h:53
void FlushCache(bool closePlugins=true)
Free all data in the cache and by default closes all plugins.
Definition 3d_cache.cpp:553
FILENAME_RESOLVER * GetResolver() noexcept
Definition 3d_cache.cpp:541
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:93
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ UNSELECTED_LAYER
Definition layer_ids.h:58
@ F_Fab
Definition layer_ids.h:115
@ F_SilkS
Definition layer_ids.h:96
@ UNDEFINED_LAYER
Definition layer_ids.h:57
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
IbisParser parser & reporter
KIBIS_MODEL * model
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< double > VECTOR2D
Definition vector2d.h:682