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>
37#include <pcb_3d_body.h>
41#include "filename_resolver.h"
42#include <pgm_base.h>
43#include <kiplatform/ui.h>
49#include <project_pcb.h>
54
55#include <reporter.h>
56
57#include <wx/defs.h>
58#include <wx/filedlg.h>
59#include <wx/msgdlg.h>
60#include <wx/notebook.h>
61
68
69
70wxDEFINE_EVENT( wxCUSTOM_PANEL_SHOWN_EVENT, wxCommandEvent );
71
73 FOOTPRINT* aFootprint,
74 DIALOG_SHIM* aDialogParent,
75 PANEL_EMBEDDED_FILES* aFilesPanel,
76 wxWindow* aParent ) :
78 m_parentDialog( aDialogParent ),
79 m_frame( aFrame ),
80 m_footprint( aFootprint ),
81 m_filesPanel( aFilesPanel ),
82 m_inSelect( false )
83{
84 m_splitter1->SetSashPosition( FromDIP( m_splitter1->GetSashPosition() ) );
85 m_splitter1->SetMinimumPaneSize( FromDIP( m_splitter1->GetMinimumPaneSize() ) );
86
87 GRID_TRICKS* trick = new GRID_TRICKS( m_modelsGrid, [this]( wxCommandEvent& aEvent )
88 {
89 OnAdd3DRow( aEvent );
90 } );
92 m_modelsGrid->PushEventHandler( trick );
93 m_modelsGrid->SetupColumnAutosizer( COL_FILENAME );
94
95 // Get the last 3D directory
97
98 if( cfg && cfg->m_LastFootprint3dDir.IsEmpty() )
99 {
100 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ),
101 &cfg->m_LastFootprint3dDir );
102 }
103
104 // Icon showing warning/error information
105 wxGridCellAttr* attr = new wxGridCellAttr;
106 attr->SetReadOnly();
107 m_modelsGrid->SetColAttr( COL_PROBLEM, attr );
108
109 // Filename
110 attr = new wxGridCellAttr;
111
112 attr->SetEditor( new GRID_CELL_RUN_FUNCTION_EDITOR(
114 [this]( int aRow, int )
115 {
116 if( aRow < 0 || aRow >= static_cast<int>( m_shapes3D_list.size() ) )
117 return;
118
120
121 if( pick3DModel( m_modelsGrid->GetCellValue( aRow, COL_FILENAME ), model ) )
122 {
123 m_modelsGrid->CallAfter(
124 [this, aRow, model]
125 {
126 m_modelsGrid->DisableCellEditControl();
127 set3DModelRow( aRow, model );
128 } );
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
145
146 m_previewPane->SetEmbeddedFilesDelegate( m_filesPanel->GetLocalFiles() );
147
148 m_LowerSizer3D->Add( m_previewPane, 1, wxEXPAND, 5 );
149
150 // Configure button logos
156
157 m_modelsGrid->Bind( wxEVT_GRID_CELL_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::on3DModelCellChanging, this );
158 Bind( wxEVT_SHOW, &PANEL_FP_PROPERTIES_3D_MODEL::onShowEvent, this );
159 Bind( wxEVT_SIZE, &PANEL_FP_PROPERTIES_3D_MODEL::onSize, this );
160 m_splitter1->Bind( wxEVT_SPLITTER_SASH_POS_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::onSashPositionChanging, this );
161 aParent->Bind( wxEVT_NOTEBOOK_PAGE_CHANGED, &PANEL_FP_PROPERTIES_3D_MODEL::onNotebookPageChanged, this );
163
164 // Bind extrusion control events to update the 3D preview
171}
172
173
175{
176 // Delete the GRID_TRICKS.
177 m_modelsGrid->PopEventHandler( true );
178
179 m_modelsGrid->Unbind( wxEVT_GRID_CELL_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::on3DModelCellChanging, this );
180 // Unbind OnShowEvent to prevent unnecessary event handling.
181 Unbind( wxEVT_SHOW, &PANEL_FP_PROPERTIES_3D_MODEL::onShowEvent, this );
182 Unbind( wxEVT_SIZE, &PANEL_FP_PROPERTIES_3D_MODEL::onSize, this );
183 m_splitter1->Unbind( wxEVT_SPLITTER_SASH_POS_CHANGING, &PANEL_FP_PROPERTIES_3D_MODEL::onSashPositionChanging, this );
184 GetParent()->Unbind( wxEVT_NOTEBOOK_PAGE_CHANGED, &PANEL_FP_PROPERTIES_3D_MODEL::onNotebookPageChanged, this );
185
190 this );
193
194 // free the memory used by all models, otherwise models which were
195 // browsed but not used would consume memory
197
198 delete m_previewPane;
199}
200
201
203{
205
206 bool hasExtrusion = m_footprint->HasBody();
207 m_enableExtrusionCheckbox->SetValue( hasExtrusion );
208
209 const EXTRUDED_3D_BODY* body = m_footprint->GetBody() ? m_footprint->GetBody()->GetExtruded() : nullptr;
210 m_showExtrusionCheckbox->SetValue( body ? body->m_show : true );
211 m_componentHeightCtrl->SetValue(
212 wxString::Format( wxT( "%.4f" ), body ? pcbIUScale.IUTomm( body->m_height ) : 0.0 ) );
213 m_standoffHeightCtrl->SetValue(
214 wxString::Format( wxT( "%.4f" ), body ? pcbIUScale.IUTomm( body->m_standoff ) : 0.0 ) );
215
217
218 m_extrusionLayerChoice->Clear();
219 m_extrusionLayerChoice->Append( _( "Auto" ) );
220 m_extrusionLayerChoice->Append( _( "Courtyard layer" ) );
221 m_extrusionLayerChoice->Append( _( "Fabrication layer" ) );
222 m_extrusionLayerChoice->Append( _( "Silkscreen layer" ) );
223 m_extrusionLayerChoice->Append( _( "Pin bounding box" ) );
224
225 PCB_LAYER_ID layer = body ? body->m_layer : UNDEFINED_LAYER;
226 int selection = 0; // Auto
227
228 for( size_t i = 0; i < m_extrusionLayers.size(); i++ )
229 {
230 if( m_extrusionLayers[i] == layer )
231 {
232 selection = static_cast<int>( i );
233 break;
234 }
235 }
236
237 m_extrusionLayerChoice->SetSelection( selection );
238
241
242 if( color == KIGFX::COLOR4D::UNSPECIFIED )
244
245 m_extrusionColorSwatch->SetSwatchColor( color, false );
246
247 m_extrusionMaterialChoice->SetSelection(
248 static_cast<int>( body ? body->m_material : EXTRUSION_MATERIAL::PLASTIC ) );
249
252
253 m_upperPanel->Layout();
254
255 Layout();
256
257 if( GetSizer() )
258 GetSizer()->Fit( this );
259
260 return true;
261}
262
263
265{
266 if( !m_modelsGrid->CommitPendingChanges() )
267 return false;
268
269 return m_filesPanel->CommitPendingChanges();
270}
271
272
274{
275 if( m_enableExtrusionCheckbox->GetValue() )
276 {
277 double compHeight;
278 double standoff;
279
280 if( !readExtrusionHeights( _( "Extruded 3D Body" ), compHeight, standoff ) )
281 return false;
282
283 if( compHeight <= 0.0 )
284 {
285 wxMessageBox( _( "Component height must be greater than zero." ), _( "Extruded 3D Body" ),
286 wxOK | wxICON_WARNING, this );
287 return false;
288 }
289
290 if( standoff < 0.0 )
291 standoff = 0.0;
292
293 if( standoff >= compHeight )
294 {
295 wxMessageBox( _( "Standoff height must be less than the overall height." ), _( "Extruded 3D Body" ),
296 wxOK | wxICON_WARNING, this );
297 return false;
298 }
299 }
300
301 return true;
302}
303
304
306{
307 if( m_enableExtrusionCheckbox->GetValue() )
308 {
309 double compHeight;
310 double standoff;
311
312 (void) readExtrusionHeights( _( "Extruded 3D Body" ), compHeight, standoff );
313
314 int sel = m_extrusionLayerChoice->GetSelection();
315
316 EXTRUDED_3D_BODY& body = *m_footprint->EnsureBody().MutableExtruded();
317 body.m_height = pcbIUScale.mmToIU( compHeight );
318 body.m_standoff = pcbIUScale.mmToIU( standoff );
319 body.m_layer = m_extrusionLayers[sel];
321 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
322
323 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
324
325 if( dummyFp && dummyFp->HasBody() )
326 {
327 const EXTRUDED_3D_BODY* dummyBody = dummyFp->GetBody()->GetExtruded();
328 body.m_scale = dummyBody->m_scale;
329 body.m_rotation = dummyBody->m_rotation;
330 body.m_offset = dummyBody->m_offset;
331 }
332
333 body.m_show = m_showExtrusionCheckbox->GetValue();
334 }
335 else
336 {
337 m_footprint->ClearBody();
338 }
339
340 return true;
341}
342
343
345{
347 m_previewPane->SetEmbeddedFilesDelegate( m_filesPanel->GetLocalFiles() );
348}
349
350
352{
353 wxString default_path;
354 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &default_path );
355
356#ifdef __WINDOWS__
357 default_path.Replace( wxT( "/" ), wxT( "\\" ) );
358#endif
359
360 m_shapes3D_list.clear();
361 m_modelsGrid->ClearRows();
362
363 wxString origPath, alias, shortPath;
365
366 for( const FP_3DMODEL& model : m_footprint->Models() )
367 {
368 m_shapes3D_list.push_back( model );
369 origPath = model.m_Filename;
370
371 if( res && res->SplitAlias( origPath, alias, shortPath ) )
372 origPath = alias + wxT( ":" ) + shortPath;
373
374 m_modelsGrid->AppendRows( 1 );
375 int row = m_modelsGrid->GetNumberRows() - 1;
376 m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath );
377 m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
378
379 // Must be after the filename is set
381 }
382
383 select3DModel( 0 );
384
385 m_previewPane->UpdateDummyFootprint();
386 m_modelsGrid->SetGridWidthsDirty();
387
388 Layout();
389}
390
391
393{
394 m_inSelect = true;
395
396 aModelIdx = std::max( 0, aModelIdx );
397 aModelIdx = std::min( aModelIdx, m_modelsGrid->GetNumberRows() - 1 );
398
399 if( m_modelsGrid->GetNumberRows() )
400 {
401 m_modelsGrid->SelectRow( aModelIdx );
402 m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME );
403 }
404
405 m_previewPane->SetSelectedModel( aModelIdx );
406
407 m_inSelect = false;
408}
409
410
412{
413 if( !m_inSelect )
414 select3DModel( aEvent.GetRow() );
415}
416
417
419{
420 if( !aFilename->empty() )
421 {
422 bool hasAlias = false;
424
425 aFilename->Replace( wxT( "\n" ), wxT( "" ) );
426 aFilename->Replace( wxT( "\r" ), wxT( "" ) );
427 aFilename->Replace( wxT( "\t" ), wxT( "" ) );
428
429 res->ValidateFileName( *aFilename, hasAlias );
430
431 // If the user has specified an alias in the name then prepend ':'
432 if( hasAlias )
433 aFilename->insert( 0, wxT( ":" ) );
434
435#ifdef __WINDOWS__
436 // In KiCad files, filenames and paths are stored using Unix notation
437 aFilename->Replace( wxT( "\\" ), wxT( "/" ) );
438#endif
439 }
440}
441
442
444{
445 if( aEvent.GetCol() == COL_FILENAME )
446 updateValidateStatus( aEvent.GetRow() );
447}
448
449
451{
452 if( aEvent.GetCol() == COL_FILENAME )
453 {
454 wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME );
455
456 if( !filename.empty() )
457 {
458 cleanupFilename( &filename );
459
460 // Update the grid with the modified filename
461 m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename );
462 }
463
464 // Save the filename in the 3D shapes table
465 m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename;
466
467 // Update the validation status
468 updateValidateStatus( aEvent.GetRow() );
469 }
470 else if( aEvent.GetCol() == COL_SHOWN )
471 {
472 wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN );
473
474 m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) );
475 }
476
477 m_previewPane->UpdateDummyFootprint();
478 onModify();
479}
480
481
483{
484 if( !m_modelsGrid->CommitPendingChanges() )
485 return;
486
487 if( !m_modelsGrid->GetNumberRows() || m_shapes3D_list.empty() )
488 return;
489
490 wxArrayInt selectedRows = m_modelsGrid->GetSelectedRows();
491 wxGridCellCoordsArray selectedCells = m_modelsGrid->GetSelectedCells();
492 wxGridCellCoordsArray blockTopLeft = m_modelsGrid->GetSelectionBlockTopLeft();
493 wxGridCellCoordsArray blockBottomRight = m_modelsGrid->GetSelectionBlockBottomRight();
494
495 for( unsigned ii = 0; ii < selectedCells.GetCount(); ++ii )
496 selectedRows.Add( selectedCells[ii].GetRow() );
497
498 if( !blockTopLeft.IsEmpty() && !blockBottomRight.IsEmpty() )
499 {
500 for( int row = blockTopLeft[0].GetRow(); row <= blockBottomRight[0].GetRow(); ++row )
501 selectedRows.Add( row );
502 }
503
504 if( selectedRows.empty() && m_modelsGrid->GetGridCursorRow() >= 0 )
505 selectedRows.Add( m_modelsGrid->GetGridCursorRow() );
506
507 if( selectedRows.empty() )
508 {
509 wxBell();
510 return;
511 }
512
513 std::sort( selectedRows.begin(), selectedRows.end() );
514
515 int nextSelection = selectedRows.front();
516 int lastRow = -1;
517
518 // Don't allow selection until we call select3DModel(), below. Otherwise wxWidgets
519 // has a tendency to get its knickers in a knot....
520 m_inSelect = true;
521
522 m_modelsGrid->ClearSelection();
523
524 for( int ii = selectedRows.size() - 1; ii >= 0; --ii )
525 {
526 int row = selectedRows[ii];
527
528 if( row == lastRow )
529 continue;
530
531 lastRow = row;
532
533 if( row < 0 || row >= (int) m_shapes3D_list.size() )
534 continue;
535
536 // Not all files are embedded but this will ignore the ones that are not
537 m_filesPanel->RemoveEmbeddedFile( m_shapes3D_list[row].m_Filename );
538 m_shapes3D_list.erase( m_shapes3D_list.begin() + row );
539 m_modelsGrid->DeleteRows( row );
540 }
541
542 if( m_modelsGrid->GetNumberRows() > 0 )
543 nextSelection = std::min( nextSelection, m_modelsGrid->GetNumberRows() - 1 );
544 else
545 nextSelection = 0;
546
547 select3DModel( nextSelection ); // will clamp index within bounds
548 m_previewPane->UpdateDummyFootprint();
549 m_inSelect = false;
550
551 onModify();
552}
553
554
555bool PANEL_FP_PROPERTIES_3D_MODEL::pick3DModel( const wxString& aCurrentValue, FP_3DMODEL& aModel )
556{
557 PROJECT& project = m_frame->Prj();
559 FILENAME_RESOLVER* resolver = cache->GetResolver();
560 wxString directory;
561 wxString filename;
562
563 const wxString embeddedPrefix = wxString::FromUTF8( FILEEXT::KiCadUriPrefix ) + wxS( "://" );
564
565 if( !aCurrentValue.IsEmpty() && !aCurrentValue.StartsWith( embeddedPrefix ) )
566 {
567 wxString libraryBasePath;
568
569 if( std::optional<LIBRARY_TABLE_ROW*> row =
570 PROJECT_PCB::FootprintLibAdapter( &project )->GetRow( m_footprint->GetFPID().GetLibNickname() ) )
571 {
572 libraryBasePath = LIBRARY_MANAGER::GetFullURI( *row, true );
573 }
574
575 wxString resolved = resolver->ResolvePath( aCurrentValue, libraryBasePath,
576 { m_filesPanel->GetLocalFiles(), m_frame->GetBoard()->GetEmbeddedFiles() } );
577 wxFileName currentFile( resolved );
578
579 if( currentFile.FileExists() )
580 {
581 directory = currentFile.GetPath();
582 filename = currentFile.GetFullName();
583 }
584 }
585
586 if( directory.IsEmpty() && m_configuredPathChoice->GetSelection() > 0 )
587 directory = m_configuredPathChoice->GetStringSelection();
588
589 if( directory.IsEmpty() )
591
592 PCBNEW_SETTINGS* settings = GetAppSettings<PCBNEW_SETTINGS>( "pcbnew" );
593
594 if( directory.IsEmpty() && settings )
596
597 if( directory.IsEmpty() )
598 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &directory );
599
600 if( directory.IsEmpty() )
601 directory = project.GetProjectPath();
602
603 wxString wildcard;
604 wxString extensions;
605 int filterCount = 0;
606
607 if( const std::list<wxString>* filters = cache->GetFileFilters() )
608 {
609 for( const wxString& filter : *filters )
610 {
611 ++filterCount;
612
613 if( !wildcard.IsEmpty() )
614 wildcard += '|';
615
616 wildcard += filter;
617 wxString extension = filter.AfterLast( '|' );
618
619 if( extension != wxS( "*.*" ) )
620 {
621 if( !extensions.IsEmpty() )
622 extensions += ';';
623
624 extensions += extension;
625 }
626 }
627 }
628
629 if( !extensions.IsEmpty() )
630 {
631 wxString fullFilter = wxString::Format( _( "All supported files (%s)" ), extensions );
632 fullFilter << '|' << extensions << '|' << wildcard;
633 wildcard = fullFilter;
634 ++filterCount;
635 }
636
637 if( wildcard.IsEmpty() )
638 wildcard = wxFileSelectorDefaultWildcardStr;
639
640 COMMON_SETTINGS* commonSettings = Pgm().GetCommonSettings();
642 options.parent = m_parentDialog;
643 options.title = _( "Select 3D Model" );
644 options.directory = directory;
645 options.filename = filename;
646 options.wildcard = wildcard;
647 options.style = wxFD_OPEN | wxFD_FILE_MUST_EXIST;
648 options.embedFile = commonSettings && commonSettings->m_EmbedFileDefaults.model_3d;
649
650 long filterIndex = 0;
651 project.GetRString( PROJECT::VIEWER_3D_FILTER_INDEX ).ToLong( &filterIndex );
652 options.filterIndex = filterCount > 0
653 ? static_cast<int>( std::clamp( filterIndex, 0L,
654 static_cast<long>( filterCount - 1 ) ) )
655 : 0;
656
658
659 struct MODAL_SCOPE
660 {
661 explicit MODAL_SCOPE( DIALOG_SHIM* aDialog ) : dialog( aDialog ) { dialog->PrepareForModalSubDialog(); }
662 ~MODAL_SCOPE() { dialog->CleanupAfterModalSubDialog(); }
663 DIALOG_SHIM* dialog;
664 };
665
666 {
667 MODAL_SCOPE modalScope( m_parentDialog );
668 selected = ShowNativeModelFilePicker( options );
669 }
670
671 if( !selected.accepted )
672 return false;
673
674 if( commonSettings )
675 commonSettings->m_EmbedFileDefaults.model_3d = selected.embedFile;
676
677 wxFileName selectedFile( selected.path );
678 selectedFile.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
679 project.SetRString( PROJECT::VIEWER_3D_PATH, selectedFile.GetPath() );
680 project.SetRString( PROJECT::VIEWER_3D_FILTER_INDEX, wxString::Format( wxS( "%d" ), selected.filterIndex ) );
681
682 if( settings )
683 settings->m_LastFootprint3dDir = selectedFile.GetPath();
684
685 if( selected.embedFile )
686 {
687 if( EMBEDDED_FILES::EMBEDDED_FILE* embedded = m_filesPanel->AddEmbeddedFile( selected.path ) )
688 aModel.m_Filename = embedded->GetLink();
689 else
690 {
691 wxMessageBox( _( "Error adding 3D model" ), _( "Error" ), wxICON_ERROR | wxOK, this );
692 return false;
693 }
694 }
695 else
696 {
697 aModel.m_Filename = resolver->ShortenPath( selectedFile.GetFullPath() );
698 }
699
700 return true;
701}
702
703
705{
706 wxCHECK_RET( aRow >= 0 && aRow < static_cast<int>( m_shapes3D_list.size() ),
707 wxS( "invalid 3D model row" ) );
708
709 m_shapes3D_list[aRow] = aModel;
710 wxString displayName = aModel.m_Filename;
711 wxString alias;
712 wxString shortPath;
713
714 if( FILENAME_RESOLVER* resolver = PROJECT_PCB::Get3DCacheManager( &m_frame->Prj() )->GetResolver();
715 resolver && resolver->SplitAlias( displayName, alias, shortPath ) )
716 {
717 displayName = alias + wxS( ":" ) + shortPath;
718 }
719
720 m_modelsGrid->SetCellValue( aRow, COL_FILENAME, displayName );
721 m_modelsGrid->SetCellValue( aRow, COL_SHOWN, aModel.m_Show ? wxS( "1" ) : wxS( "0" ) );
722 select3DModel( aRow );
723 updateValidateStatus( aRow );
724 m_previewPane->UpdateDummyFootprint();
725 onModify();
726}
727
728
730{
731 const wxString previous = m_configuredPathChoice->GetStringSelection();
732 m_configuredPathChoice->Clear();
733 m_configuredPathChoice->Append( _( "Automatic" ) );
734
736
737 if( const std::list<SEARCH_PATH>* paths = resolver->GetPaths() )
738 {
739 for( const SEARCH_PATH& path : *paths )
740 {
741 if( !path.m_Pathexp.IsEmpty() && m_configuredPathChoice->FindString( path.m_Pathexp ) == wxNOT_FOUND )
742 m_configuredPathChoice->Append( path.m_Pathexp );
743 }
744 }
745
746 const int previousIndex = m_configuredPathChoice->FindString( previous );
747 m_configuredPathChoice->SetSelection( previousIndex == wxNOT_FOUND ? 0 : previousIndex );
748}
749
750
752{
753 if( !m_modelsGrid->CommitPendingChanges() )
754 return;
755
756 const int selected = m_modelsGrid->GetGridCursorRow();
758
759 if( !pick3DModel( wxEmptyString, model ) )
760 {
761 if( selected >= 0 )
762 {
763 select3DModel( selected );
764 updateValidateStatus( selected );
765 }
766
767 return;
768 }
769
770 const int row = m_modelsGrid->GetNumberRows();
771 model.m_Show = true;
772 m_shapes3D_list.push_back( model );
773 m_modelsGrid->AppendRows( 1 );
774 set3DModelRow( row, model );
775}
776
777
779{
780 m_modelsGrid->OnAddRow(
781 [&]() -> std::pair<int, int>
782 {
784
785 model.m_Show = true;
786 m_shapes3D_list.push_back( model );
787
788 int row = m_modelsGrid->GetNumberRows();
789 m_modelsGrid->AppendRows( 1 );
790 m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) );
791 m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" );
792
793 select3DModel( row );
795 onModify();
796
797 return { row, COL_FILENAME };
798 } );
799}
800
801
803{
804 int icon = 0;
805 wxString errStr;
806 wxString filename = m_modelsGrid->GetCellValue( aRow, COL_FILENAME );
807
808 if( wxGridCellEditor* cellEditor = m_modelsGrid->GetCellEditor( aRow, COL_FILENAME ) )
809 {
810 if( cellEditor->IsCreated() && cellEditor->GetWindow()->IsShown() )
811 filename = cellEditor->GetValue();
812
813 cellEditor->DecRef();
814 }
815
816 switch( validateModelExists( filename ) )
817 {
819 icon = 0;
820 errStr = "";
821 break;
822
824 icon = wxICON_WARNING;
825 errStr = _( "No filename entered" );
826 break;
827
829 icon = wxICON_ERROR;
830 errStr = _( "Illegal filename" );
831 break;
832
834 icon = wxICON_ERROR;
835 errStr = _( "File not found" );
836 break;
837
839 icon = wxICON_ERROR;
840 errStr = _( "Unable to open file" );
841 break;
842
843 default:
844 icon = wxICON_ERROR;
845 errStr = _( "Unknown error" );
846 break;
847 }
848
849 m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr );
850 m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM, new GRID_CELL_STATUS_ICON_RENDERER( icon ) );
851}
852
853
855{
856 if( aFilename.empty() )
858
859 bool hasAlias = false;
861
862 if( !resolv )
864
865 if( !resolv->ValidateFileName( aFilename, hasAlias ) )
867
868 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
869 wxString footprintBasePath = wxEmptyString;
870
871 std::optional<LIBRARY_TABLE_ROW*> fpRow =
872 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
873 if( fpRow )
874 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
875
876 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
877 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
878 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
879
880 wxString fullPath = resolv->ResolvePath( aFilename, footprintBasePath, std::move( embeddedFilesStack ) );
881
882 if( fullPath.IsEmpty() )
884
885 if( !wxFileName::IsFileReadable( fullPath ) )
887
889}
890
891
892void PANEL_FP_PROPERTIES_3D_MODEL::Cfg3DPath( wxCommandEvent& event )
893{
894 DIALOG_CONFIGURE_PATHS dlg( this );
895
896 if( dlg.ShowQuasiModal() == wxID_OK )
897 {
899 m_previewPane->UpdateDummyFootprint();
900 }
901}
902
903
904void PANEL_FP_PROPERTIES_3D_MODEL::OnUpdateUI( wxUpdateUIEvent& event )
905{
906 m_button3DShapeRemove->Enable( m_modelsGrid->GetNumberRows() > 0 );
907}
908
909
911{
912 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
913 dlg->OnModify();
914}
915
916
918{
919 postCustomPanelShownEventWithPredicate( static_cast<int>( aEvent.IsShown() ) );
920 aEvent.Skip();
921
922 if( aEvent.IsShown() )
923 CallAfter( [this] { setDefaultSashPosition(); } );
924}
925
926
927void PANEL_FP_PROPERTIES_3D_MODEL::onSize( wxSizeEvent& aEvent )
928{
929 aEvent.Skip();
931}
932
933
935{
937 aEvent.Skip();
938}
939
940
942{
943 if( !m_splitter1->IsSplit() || m_userSetSashPosition )
944 return;
945
946 const int page = GetSize().y;
947
948 if( page <= 0 )
949 return;
950
951 const int rowHeight = m_modelsGrid->GetDefaultRowSize();
952 const int chrome = m_modelsGrid->GetColLabelSize() + 4;
953 const int minGrid = 3 * rowHeight + chrome;
954 const int maxGrid = std::max( minGrid, page / 3 );
955 const int grid = std::clamp( m_modelsGrid->GetNumberRows() * rowHeight + chrome, minGrid, maxGrid );
956
957 m_modelsGrid->SetMinSize( wxSize( -1, grid ) );
958 const int content = m_upperPanel->GetSizer()->CalcMin().y;
959 const int minPane = m_splitter1->GetMinimumPaneSize();
960
961 // Prevent collapsing part of the page when the window height is too small
962 m_splitter1->SetSashPosition( std::max( std::min( content, page - minPane ), minPane ) );
963}
964
965
967{
968 postCustomPanelShownEventWithPredicate( aEvent.GetActive() && m_previewPane->IsShownOnScreen() );
969 aEvent.Skip();
970}
971
972
974{
975 wxNotebook* notebook = dynamic_cast<wxNotebook*>( aEvent.GetEventObject() );
976
977 if( notebook && notebook == GetParent() )
978 {
979 // Native notebooks do not consistently emit show events when hiding their pages.
980 const int selected = aEvent.GetSelection();
981 bool isSelected = selected >= 0
982 && selected < static_cast<int>( notebook->GetPageCount() )
983 && notebook->GetPage( selected ) == this;
984
986
987 if( isSelected )
988 CallAfter( [this] { setDefaultSashPosition(); } );
989 }
990
991 aEvent.Skip();
992}
993
994
996{
997 wxCommandEvent event( wxCUSTOM_PANEL_SHOWN_EVENT );
998 event.SetEventObject( m_previewPane );
999 event.SetInt( static_cast<int>( predicate ) );
1000 m_previewPane->ProcessWindowEvent( event );
1001}
1002
1003
1005{
1008 onModify();
1009}
1010
1011
1013{
1015 onModify();
1016 event.Skip();
1017}
1018
1019
1021{
1024 onModify();
1025 event.Skip();
1026}
1027
1028
1030{
1031 bool enabled = m_enableExtrusionCheckbox->GetValue();
1032 m_showExtrusionCheckbox->Enable( enabled );
1033 m_componentHeightCtrl->Enable( enabled );
1034 m_standoffHeightCtrl->Enable( enabled );
1035 m_extrusionLayerChoice->Enable( enabled );
1036 m_extrusionColorSwatch->Enable( enabled );
1037 m_extrusionMaterialChoice->Enable( enabled );
1038 m_buttonExportExtruded->Enable( enabled );
1039
1040 if( enabled )
1041 {
1042 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
1043
1044 if( dummyFp )
1045 m_previewPane->SetExtrusionTransformMode( &dummyFp->EnsureBody() );
1046 }
1047 else
1048 {
1049 m_previewPane->SetExtrusionTransformMode( nullptr );
1050 }
1051}
1052
1053
1054bool PANEL_FP_PROPERTIES_3D_MODEL::readExtrusionHeights( const wxString& aTitle, double& aHeight, double& aStandoff )
1055{
1056 wxString heightText = m_componentHeightCtrl->GetValue();
1057 wxString standoffText = m_standoffHeightCtrl->GetValue();
1058
1059 // An empty field reads as zero.
1060 aHeight = 0.0;
1061 aStandoff = 0.0;
1062
1063 if( !heightText.IsEmpty() && !heightText.ToDouble( &aHeight ) )
1064 {
1065 wxMessageBox( _( "Overall height is not a valid number." ), aTitle, wxOK | wxICON_WARNING, this );
1066 return false;
1067 }
1068
1069 if( !standoffText.IsEmpty() && !standoffText.ToDouble( &aStandoff ) )
1070 {
1071 wxMessageBox( _( "Standoff height is not a valid number." ), aTitle, wxOK | wxICON_WARNING, this );
1072 return false;
1073 }
1074
1075 return true;
1076}
1077
1078
1080{
1081 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
1082
1083 if( !dummyFp || m_extrusionLayers.empty() )
1084 return;
1085
1086 double compHeight = 0.0;
1087 double standoff = 0.0;
1088 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
1089 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
1090
1091 int sel = m_extrusionLayerChoice->GetSelection();
1092
1093 EXTRUDED_3D_BODY& body = *dummyFp->EnsureBody().MutableExtruded();
1094 body.m_height = pcbIUScale.mmToIU( compHeight );
1095 body.m_standoff = pcbIUScale.mmToIU( standoff );
1096 body.m_layer = m_extrusionLayers[sel];
1098 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
1099 body.m_show = m_enableExtrusionCheckbox->GetValue() && m_showExtrusionCheckbox->GetValue();
1100
1101 m_previewPane->UpdateDummyFootprint( true );
1102}
1103
1104
1106{
1108 {
1109 EXTRUSION_MATERIAL mat = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
1110 m_extrusionColorSwatch->SetSwatchColor( EXTRUDED_3D_BODY::GetDefaultColor( mat ), false );
1111 }
1112
1114 onModify();
1115 event.Skip();
1116}
1117
1118
1120{
1121 double height;
1122 double standoff;
1123
1124 if( !readExtrusionHeights( _( "Export Extruded Body" ), height, standoff ) )
1125 return;
1126
1127 if( height <= 0.0 )
1128 {
1129 wxMessageBox( _( "Component height must be greater than zero." ), _( "Export Extruded Body" ),
1130 wxOK | wxICON_WARNING, this );
1131 return;
1132 }
1133
1134 int layerSel = m_extrusionLayerChoice->GetSelection();
1135
1136 SHAPE_POLY_SET outline;
1137 bool gotOutline =
1138 GetExtrusionOutline( m_footprint, outline, m_extrusionLayers[layerSel] ) && outline.OutlineCount() > 0;
1139
1140 if( !gotOutline )
1141 {
1142 wxMessageBox( _( "No extrusion outline could be generated for this footprint." ), _( "Export Extruded Body" ),
1143 wxOK | wxICON_WARNING, this );
1144 return;
1145 }
1146
1147 wxString defaultName = m_footprint->GetReference() + wxT( "_extruded" );
1148
1149 wxFileDialog dlg( this, _( "Export Extruded 3D Body" ), wxEmptyString, defaultName,
1150 _( "STEP files" ) + wxT( " (*.step)|*.step|" ) + _( "GLB files" ) + wxT( " (*.glb)|*.glb|" )
1151 + _( "STL files" ) + wxT( " (*.stl)|*.stl|" ) + _( "BREP files" )
1152 + wxT( " (*.brep)|*.brep" ),
1153 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1154
1155 if( dlg.ShowModal() == wxID_CANCEL )
1156 return;
1157
1158 wxString path = dlg.GetPath();
1159 int filterIdx = dlg.GetFilterIndex();
1160
1161 bool bottom = m_footprint->IsFlipped();
1162 VECTOR2D origin( m_footprint->GetPosition().x, m_footprint->GetPosition().y );
1163
1164 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
1165 const EXTRUDED_3D_BODY* body = dummyFp && dummyFp->HasBody() ? dummyFp->GetBody()->GetExtruded() : nullptr;
1166
1167 double bodyStandoff = standoff;
1168
1169 if( body )
1170 {
1171 ApplyExtrusionTransform( outline, body, m_footprint->GetPosition() );
1172
1173 double bodyThickness = ( height - standoff ) * body->m_scale.z;
1174 bodyStandoff = standoff + body->m_offset.z;
1175 height = bodyStandoff + bodyThickness;
1176 }
1177
1178 EXTRUSION_MATERIAL material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
1179
1180 KIGFX::COLOR4D c = m_extrusionColorSwatch->GetSwatchColor();
1181
1183 c = EXTRUDED_3D_BODY::GetDefaultColor( material );
1184
1185 uint32_t colorKey = EXTRUDED_3D_BODY::PackColorKey( c );
1186
1188 STEP_PCB_MODEL model( wxT( "extruded_body" ), &reporter );
1189
1190 if( !model.AddExtrudedBody( outline, bottom, bodyStandoff, height, origin, colorKey, material,
1191 m_footprint->GetReference() ) )
1192 {
1193 wxMessageBox( _( "Failed to create extruded body geometry." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR,
1194 this );
1195 return;
1196 }
1197
1198 if( standoff > 0.0 )
1199 model.AddExtrudedPins( m_footprint, body, bottom, bodyStandoff, origin );
1200
1201 SHAPE_POLY_SET boardOutline( outline );
1202 model.CreatePCB( boardOutline, origin, false );
1203
1204 bool ok = false;
1205
1206 switch( filterIdx )
1207 {
1208 case 0: ok = model.WriteSTEP( path, true, false ); break;
1209 case 1: ok = model.WriteGLTF( path ); break;
1210 case 2: ok = model.WriteSTL( path ); break;
1211 case 3: ok = model.WriteBREP( path ); break;
1212 }
1213
1214 if( !ok )
1215 {
1216 wxMessageBox( _( "Failed to write file." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR, this );
1217 }
1218}
void ApplyExtrusionTransform(SHAPE_POLY_SET &aOutline, const EXTRUDED_3D_BODY *aBody, const VECTOR2I &aFpPos)
Apply 2D extrusion transforms (rotation, scale, offset) to an outline.
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:128
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
EMBED_FILE_DEFAULTS m_EmbedFileDefaults
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition dialog_shim.h:80
KIGFX::COLOR4D m_color
Definition pcb_3d_body.h:46
VECTOR3D m_offset
Definition pcb_3d_body.h:52
PCB_LAYER_ID m_layer
Definition pcb_3d_body.h:45
static KIGFX::COLOR4D GetDefaultColor(EXTRUSION_MATERIAL aMaterial)
Definition pcb_3d_body.h:54
VECTOR3D m_rotation
Definition pcb_3d_body.h:51
static uint32_t PackColorKey(const KIGFX::COLOR4D &aColor)
Definition pcb_3d_body.h:73
EXTRUSION_MATERIAL m_material
Definition pcb_3d_body.h:47
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 std::list< SEARCH_PATH > * GetPaths() const
Return a pointer to the internal path list; the items in:load.
wxString ShortenPath(const wxString &aFullPathName)
Produce a relative path based on the existing search directories or returns the same path if the path...
PCB_3D_BODY & EnsureBody()
const PCB_3D_BODY * GetBody() const
Definition footprint.h:427
bool HasBody() const
Convenience accessors for now while we restrict to only one body per footprint.
Definition footprint.h:426
wxString m_Filename
The 3D shape filename in 3D library.
Definition footprint.h:136
bool m_Show
Include model in rendering.
Definition footprint.h:137
A cell editor which runs a provided function when the grid cell button is clicked.
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:400
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:267
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
bool pick3DModel(const wxString &aCurrentValue, FP_3DMODEL &aModel)
void onNotebookPageChanged(wxBookCtrlEvent &aEvent)
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
void setDefaultSashPosition()
Split the page so the model list shows its rows, never fewer than three and never more than a third o...
void onSashPositionChanging(wxSplitterEvent &aEvent)
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)
bool readExtrusionHeights(const wxString &aTitle, double &aHeight, double &aStandoff)
void set3DModelRow(int aRow, const FP_3DMODEL &aModel)
void onExtrusionMaterialChanged(wxCommandEvent &event)
void On3DModelSelected(wxGridEvent &) override
wxString m_LastFootprint3dDir
const EXTRUDED_3D_BODY * GetExtruded() const
EXTRUDED_3D_BODY * MutableExtruded()
Common, abstract interface for edit frames.
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:562
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:63
@ VIEWER_3D_FILTER_INDEX
Definition project.h:239
@ VIEWER_3D_PATH
Definition project.h:238
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:560
std::list< wxString > const * GetFileFilters() const
Return the list of file filters retrieved from the plugins.
Definition 3d_cache.cpp:554
FILENAME_RESOLVER * GetResolver() noexcept
Definition 3d_cache.cpp:548
Represent a set of closed polygons.
int OutlineCount() const
Return the number of outlines in the set.
static wxString displayName(const DATABASE_LIB_TABLE &aTable)
#define _(s)
Declaration of the eda_3d_viewer class.
Functions related to environment variables, including help functions.
static const std::string KiCadUriPrefix
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:78
void EllipsizeChoiceBox(wxChoice *aChoice)
Configure a wxChoice control to ellipsize the shown text in the button with the ellipses placed at th...
Definition wxgtk/ui.cpp:407
NATIVE_MODEL_FILE_PICKER_RESULT ShowNativeModelFilePicker(const NATIVE_MODEL_FILE_PICKER_OPTIONS &aOptions)
wxDEFINE_EVENT(wxCUSTOM_PANEL_SHOWN_EVENT, wxCommandEvent)
EXTRUSION_MATERIAL
Definition pcb_3d_body.h:31
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
static const std::vector< std::string > filters
Definition plugin.cpp:64
static const std::vector< std::string > extensions
Definition plugin.cpp:63
T * GetAppSettings(const char *aFilename)
std::string path
IbisParser parser & reporter
KIBIS_MODEL * model
VECTOR3I res
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35