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 return true;
246}
247
248
250{
251 if( !m_modelsGrid->CommitPendingChanges() )
252 return false;
253
254 if( m_enableExtrusionCheckbox->GetValue() )
255 {
256 double compHeight = 0.0;
257 double standoff = 0.0;
258 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
259 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
260
261 if( compHeight <= 0.0 )
262 {
263 wxMessageBox( _( "Component height must be greater than zero." ), _( "Extruded 3D Body" ),
264 wxOK | wxICON_WARNING, this );
265 return false;
266 }
267
268 if( standoff < 0.0 )
269 standoff = 0.0;
270
271 if( standoff >= compHeight )
272 {
273 wxMessageBox( _( "Standoff height must be less than the overall height." ), _( "Extruded 3D Body" ),
274 wxOK | wxICON_WARNING, this );
275 return false;
276 }
277
278 int sel = m_extrusionLayerChoice->GetSelection();
279
280 EXTRUDED_3D_BODY& body = m_footprint->EnsureExtrudedBody();
281 body.m_height = pcbIUScale.mmToIU( compHeight );
282 body.m_standoff = pcbIUScale.mmToIU( standoff );
283 body.m_layer = m_extrusionLayers[sel];
285 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
286
287 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
288
289 if( dummyFp && dummyFp->HasExtrudedBody() )
290 {
291 const EXTRUDED_3D_BODY* dummyBody = dummyFp->GetExtrudedBody();
292 body.m_scale = dummyBody->m_scale;
293 body.m_rotation = dummyBody->m_rotation;
294 body.m_offset = dummyBody->m_offset;
295 }
296
297 body.m_show = m_showExtrusionCheckbox->GetValue();
298 }
299 else
300 {
301 m_footprint->ClearExtrudedBody();
302 }
303
304 return true;
305}
306
307
309{
310 wxString default_path;
311 wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &default_path );
312
313#ifdef __WINDOWS__
314 default_path.Replace( wxT( "/" ), wxT( "\\" ) );
315#endif
316
317 m_shapes3D_list.clear();
318 m_modelsGrid->ClearRows();
319
320 wxString origPath, alias, shortPath;
322
323 for( const FP_3DMODEL& model : m_footprint->Models() )
324 {
325 m_shapes3D_list.push_back( model );
326 origPath = model.m_Filename;
327
328 if( res && res->SplitAlias( origPath, alias, shortPath ) )
329 origPath = alias + wxT( ":" ) + shortPath;
330
331 m_modelsGrid->AppendRows( 1 );
332 int row = m_modelsGrid->GetNumberRows() - 1;
333 m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath );
334 m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
335
336 // Must be after the filename is set
338 }
339
340 select3DModel( 0 );
341
342 m_previewPane->UpdateDummyFootprint();
343 m_modelsGrid->SetGridWidthsDirty();
344
345 Layout();
346}
347
348
350{
351 m_inSelect = true;
352
353 aModelIdx = std::max( 0, aModelIdx );
354 aModelIdx = std::min( aModelIdx, m_modelsGrid->GetNumberRows() - 1 );
355
356 if( m_modelsGrid->GetNumberRows() )
357 {
358 m_modelsGrid->SelectRow( aModelIdx );
359 m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME );
360 }
361
362 m_previewPane->SetSelectedModel( aModelIdx );
363
364 m_inSelect = false;
365}
366
367
369{
370 if( !m_inSelect )
371 select3DModel( aEvent.GetRow() );
372}
373
374
376{
377 if( !aFilename->empty() )
378 {
379 bool hasAlias = false;
381
382 aFilename->Replace( wxT( "\n" ), wxT( "" ) );
383 aFilename->Replace( wxT( "\r" ), wxT( "" ) );
384 aFilename->Replace( wxT( "\t" ), wxT( "" ) );
385
386 res->ValidateFileName( *aFilename, hasAlias );
387
388 // If the user has specified an alias in the name then prepend ':'
389 if( hasAlias )
390 aFilename->insert( 0, wxT( ":" ) );
391
392#ifdef __WINDOWS__
393 // In KiCad files, filenames and paths are stored using Unix notation
394 aFilename->Replace( wxT( "\\" ), wxT( "/" ) );
395#endif
396 }
397}
398
399
401{
402 if( aEvent.GetCol() == COL_FILENAME )
403 updateValidateStatus( aEvent.GetRow() );
404}
405
406
408{
409 if( aEvent.GetCol() == COL_FILENAME )
410 {
411 wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME );
412
413 if( !filename.empty() )
414 {
415 cleanupFilename( &filename );
416
417 // Update the grid with the modified filename
418 m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename );
419 }
420
421 // Save the filename in the 3D shapes table
422 m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename;
423
424 // Update the validation status
425 updateValidateStatus( aEvent.GetRow() );
426 }
427 else if( aEvent.GetCol() == COL_SHOWN )
428 {
429 wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN );
430
431 m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) );
432 }
433
434 m_previewPane->UpdateDummyFootprint();
435 onModify();
436}
437
438
440{
441 if( !m_modelsGrid->CommitPendingChanges() )
442 return;
443
444 if( !m_modelsGrid->GetNumberRows() || m_shapes3D_list.empty() )
445 return;
446
447 wxArrayInt selectedRows = m_modelsGrid->GetSelectedRows();
448 wxGridCellCoordsArray selectedCells = m_modelsGrid->GetSelectedCells();
449 wxGridCellCoordsArray blockTopLeft = m_modelsGrid->GetSelectionBlockTopLeft();
450 wxGridCellCoordsArray blockBottomRight = m_modelsGrid->GetSelectionBlockBottomRight();
451
452 for( unsigned ii = 0; ii < selectedCells.GetCount(); ++ii )
453 selectedRows.Add( selectedCells[ii].GetRow() );
454
455 if( !blockTopLeft.IsEmpty() && !blockBottomRight.IsEmpty() )
456 {
457 for( int row = blockTopLeft[0].GetRow(); row <= blockBottomRight[0].GetRow(); ++row )
458 selectedRows.Add( row );
459 }
460
461 if( selectedRows.empty() && m_modelsGrid->GetGridCursorRow() >= 0 )
462 selectedRows.Add( m_modelsGrid->GetGridCursorRow() );
463
464 if( selectedRows.empty() )
465 {
466 wxBell();
467 return;
468 }
469
470 std::sort( selectedRows.begin(), selectedRows.end() );
471
472 int nextSelection = selectedRows.front();
473 int lastRow = -1;
474
475 // Don't allow selection until we call select3DModel(), below. Otherwise wxWidgets
476 // has a tendency to get its knickers in a knot....
477 m_inSelect = true;
478
479 m_modelsGrid->ClearSelection();
480
481 for( int ii = selectedRows.size() - 1; ii >= 0; --ii )
482 {
483 int row = selectedRows[ii];
484
485 if( row == lastRow )
486 continue;
487
488 lastRow = row;
489
490 if( row < 0 || row >= (int) m_shapes3D_list.size() )
491 continue;
492
493 // Not all files are embedded but this will ignore the ones that are not
494 m_filesPanel->RemoveEmbeddedFile( m_shapes3D_list[row].m_Filename );
495 m_shapes3D_list.erase( m_shapes3D_list.begin() + row );
496 m_modelsGrid->DeleteRows( row );
497 }
498
499 if( m_modelsGrid->GetNumberRows() > 0 )
500 nextSelection = std::min( nextSelection, m_modelsGrid->GetNumberRows() - 1 );
501 else
502 nextSelection = 0;
503
504 select3DModel( nextSelection ); // will clamp index within bounds
505 m_previewPane->UpdateDummyFootprint();
506 m_inSelect = false;
507
508 onModify();
509}
510
511
513{
514 if( !m_modelsGrid->CommitPendingChanges() )
515 return;
516
517 int selected = m_modelsGrid->GetGridCursorRow();
518
519 PROJECT& prj = m_frame->Prj();
523
524 wxString initialpath = prj.GetRString( PROJECT::VIEWER_3D_PATH );
525 wxString sidx = prj.GetRString( PROJECT::VIEWER_3D_FILTER_INDEX );
526 int filter = 0;
527
528 // If the PROJECT::VIEWER_3D_PATH hasn't been set yet, use the 3DMODEL_DIR environment
529 // variable and fall back to the project path if necessary.
530 if( initialpath.IsEmpty() )
531 {
532 if( !wxGetEnv( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ), &initialpath )
533 || initialpath.IsEmpty() )
534 {
535 initialpath = prj.GetProjectPath();
536 }
537 }
538
539 if( !sidx.empty() )
540 {
541 long tmp;
542 sidx.ToLong( &tmp );
543
544 if( tmp > 0 && tmp <= INT_MAX )
545 filter = (int) tmp;
546 }
547
548 DIALOG_SELECT_3DMODEL dm( m_parentDialog, cache, &model, initialpath, filter );
549
550 // Use QuasiModal so that Configure3DPaths (and its help window) will work
551 int retval = dm.ShowQuasiModal();
552
553 if( retval != wxID_OK || model.m_Filename.empty() )
554 {
555 if( selected >= 0 )
556 {
557 select3DModel( selected );
558 updateValidateStatus( selected );
559 }
560
561 return;
562 }
563
564 if( dm.IsEmbedded3DModel() )
565 {
566 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
567 wxString footprintBasePath = wxEmptyString;
568
569 std::optional<LIBRARY_TABLE_ROW*> fpRow =
570 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
571 if( fpRow )
572 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
573
574 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
575 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
576 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
577
578 wxString fullPath = res->ResolvePath( model.m_Filename, footprintBasePath, std::move( embeddedFilesStack ) );
579 wxFileName fname( fullPath );
580
581 EMBEDDED_FILES::EMBEDDED_FILE* result = m_filesPanel->AddEmbeddedFile( fname.GetFullPath() ); ;
582
583 if( !result )
584 {
585
586 wxString msg = wxString::Format( _( "Error adding 3D model" ) );
587 wxMessageBox( msg, _( "Error" ), wxICON_ERROR | wxOK, this );
588 return;
589 }
590
591 model.m_Filename = result->GetLink();
592 }
593
594 prj.SetRString( PROJECT::VIEWER_3D_PATH, initialpath );
595 sidx = wxString::Format( wxT( "%i" ), filter );
597
598 wxString alias;
599 wxString shortPath;
600 wxString filename = model.m_Filename;
601
602 if( res && res->SplitAlias( filename, alias, shortPath ) )
603 filename = alias + wxT( ":" ) + shortPath;
604
605#ifdef __WINDOWS__
606 // In KiCad files, filenames and paths are stored using Unix notation
607 model.m_Filename.Replace( wxT( "\\" ), wxT( "/" ) );
608#endif
609
610 model.m_Show = true;
611 m_shapes3D_list.push_back( model );
612
613 int idx = m_modelsGrid->GetNumberRows();
614 m_modelsGrid->AppendRows( 1 );
615 m_modelsGrid->SetCellValue( idx, COL_FILENAME, filename );
616 m_modelsGrid->SetCellValue( idx, COL_SHOWN, wxT( "1" ) );
617
618 select3DModel( idx );
620
621 m_previewPane->UpdateDummyFootprint();
622 onModify();
623}
624
625
627{
628 m_modelsGrid->OnAddRow(
629 [&]() -> std::pair<int, int>
630 {
632
633 model.m_Show = true;
634 m_shapes3D_list.push_back( model );
635
636 int row = m_modelsGrid->GetNumberRows();
637 m_modelsGrid->AppendRows( 1 );
638 m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) );
639 m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" );
640
641 select3DModel( row );
643 onModify();
644
645 return { row, COL_FILENAME };
646 } );
647}
648
649
651{
652 int icon = 0;
653 wxString errStr;
654 wxString filename = m_modelsGrid->GetCellValue( aRow, COL_FILENAME );
655
656 if( wxGridCellEditor* cellEditor = m_modelsGrid->GetCellEditor( aRow, COL_FILENAME ) )
657 {
658 if( cellEditor->IsCreated() && cellEditor->GetWindow()->IsShown() )
659 filename = cellEditor->GetValue();
660
661 cellEditor->DecRef();
662 }
663
664 switch( validateModelExists( filename ) )
665 {
667 icon = 0;
668 errStr = "";
669 break;
670
672 icon = wxICON_WARNING;
673 errStr = _( "No filename entered" );
674 break;
675
677 icon = wxICON_ERROR;
678 errStr = _( "Illegal filename" );
679 break;
680
682 icon = wxICON_ERROR;
683 errStr = _( "File not found" );
684 break;
685
687 icon = wxICON_ERROR;
688 errStr = _( "Unable to open file" );
689 break;
690
691 default:
692 icon = wxICON_ERROR;
693 errStr = _( "Unknown error" );
694 break;
695 }
696
697 m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr );
698 m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM, new GRID_CELL_STATUS_ICON_RENDERER( icon ) );
699}
700
701
703{
704 if( aFilename.empty() )
706
707 bool hasAlias = false;
709
710 if( !resolv )
712
713 if( !resolv->ValidateFileName( aFilename, hasAlias ) )
715
716 wxString libraryName = m_footprint->GetFPID().GetLibNickname();
717 wxString footprintBasePath = wxEmptyString;
718
719 std::optional<LIBRARY_TABLE_ROW*> fpRow =
720 PROJECT_PCB::FootprintLibAdapter( &m_frame->Prj() )->GetRow( libraryName );
721 if( fpRow )
722 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
723
724 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
725 embeddedFilesStack.push_back( m_filesPanel->GetLocalFiles() );
726 embeddedFilesStack.push_back( m_frame->GetBoard()->GetEmbeddedFiles() );
727
728 wxString fullPath = resolv->ResolvePath( aFilename, footprintBasePath, std::move( embeddedFilesStack ) );
729
730 if( fullPath.IsEmpty() )
732
733 if( !wxFileName::IsFileReadable( fullPath ) )
735
737}
738
739
740void PANEL_FP_PROPERTIES_3D_MODEL::Cfg3DPath( wxCommandEvent& event )
741{
742 DIALOG_CONFIGURE_PATHS dlg( this );
743
744 if( dlg.ShowQuasiModal() == wxID_OK )
745 m_previewPane->UpdateDummyFootprint();
746}
747
748
749void PANEL_FP_PROPERTIES_3D_MODEL::OnUpdateUI( wxUpdateUIEvent& event )
750{
751 m_button3DShapeRemove->Enable( m_modelsGrid->GetNumberRows() > 0 );
752}
753
754
756{
757 if( DIALOG_SHIM* dlg = dynamic_cast<DIALOG_SHIM*>( wxGetTopLevelParent( this ) ) )
758 dlg->OnModify();
759}
760
761
763{
764 postCustomPanelShownEventWithPredicate( static_cast<int>( aEvent.IsShown() ) );
765 aEvent.Skip();
766}
767
768
770{
771 postCustomPanelShownEventWithPredicate( aEvent.GetActive() && m_previewPane->IsShownOnScreen() );
772 aEvent.Skip();
773}
774
775
777{
778 wxCommandEvent event( wxCUSTOM_PANEL_SHOWN_EVENT );
779 event.SetEventObject( m_previewPane );
780 event.SetInt( static_cast<int>( predicate ) );
781 m_previewPane->ProcessWindowEvent( event );
782}
783
784
791
792
794{
796 onModify();
797 event.Skip();
798}
799
800
802{
805 onModify();
806 event.Skip();
807}
808
809
811{
812 bool enabled = m_enableExtrusionCheckbox->GetValue();
813 m_showExtrusionCheckbox->Enable( enabled );
814 m_componentHeightCtrl->Enable( enabled );
815 m_standoffHeightCtrl->Enable( enabled );
816 m_extrusionLayerChoice->Enable( enabled );
817 m_extrusionColorSwatch->Enable( enabled );
818 m_extrusionMaterialChoice->Enable( enabled );
819 m_buttonExportExtruded->Enable( enabled );
820
821 if( enabled )
822 {
823 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
824
825 if( dummyFp )
826 m_previewPane->SetExtrusionTransformMode( &dummyFp->EnsureExtrudedBody() );
827 }
828 else
829 {
830 m_previewPane->SetExtrusionTransformMode( nullptr );
831 }
832}
833
834
836{
837 FOOTPRINT* dummyFp = m_previewPane->GetDummyFootprint();
838
839 if( !dummyFp || m_extrusionLayers.empty() )
840 return;
841
842 if( m_enableExtrusionCheckbox->GetValue() && m_showExtrusionCheckbox->GetValue() )
843 {
844 double compHeight = 0.0;
845 double standoff = 0.0;
846 m_componentHeightCtrl->GetValue().ToDouble( &compHeight );
847 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
848
849 int sel = m_extrusionLayerChoice->GetSelection();
850
851 EXTRUDED_3D_BODY& body = dummyFp->EnsureExtrudedBody();
852 body.m_height = pcbIUScale.mmToIU( compHeight );
853 body.m_standoff = pcbIUScale.mmToIU( standoff );
854 body.m_layer = m_extrusionLayers[sel];
856 body.m_material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
857 }
858 else
859 {
860 dummyFp->ClearExtrudedBody();
861 }
862
863 m_previewPane->UpdateDummyFootprint( true );
864}
865
866
868{
870 {
871 EXTRUSION_MATERIAL mat = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
872 m_extrusionColorSwatch->SetSwatchColor( EXTRUDED_3D_BODY::GetDefaultColor( mat ), false );
873 }
874
876 onModify();
877 event.Skip();
878}
879
880
882{
883 double height = 0.0;
884 double standoff = 0.0;
885 m_componentHeightCtrl->GetValue().ToDouble( &height );
886 m_standoffHeightCtrl->GetValue().ToDouble( &standoff );
887
888 if( height <= 0.0 )
889 {
890 wxMessageBox( _( "Component height must be greater than zero." ), _( "Export Extruded Body" ),
891 wxOK | wxICON_WARNING, this );
892 return;
893 }
894
895 int layerSel = m_extrusionLayerChoice->GetSelection();
896
897 SHAPE_POLY_SET outline;
898 bool gotOutline =
899 GetExtrusionOutline( m_footprint, outline, m_extrusionLayers[layerSel] ) && outline.OutlineCount() > 0;
900
901 if( !gotOutline )
902 {
903 wxMessageBox( _( "No extrusion outline could be generated for this footprint." ), _( "Export Extruded Body" ),
904 wxOK | wxICON_WARNING, this );
905 return;
906 }
907
908 wxString defaultName = m_footprint->GetReference() + wxT( "_extruded" );
909
910 wxFileDialog dlg( this, _( "Export Extruded 3D Body" ), wxEmptyString, defaultName,
911 _( "STEP files" ) + wxT( " (*.step)|*.step|" ) + _( "GLB files" ) + wxT( " (*.glb)|*.glb|" )
912 + _( "STL files" ) + wxT( " (*.stl)|*.stl|" ) + _( "BREP files" )
913 + wxT( " (*.brep)|*.brep" ),
914 wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
915
916 if( dlg.ShowModal() == wxID_CANCEL )
917 return;
918
919 wxString path = dlg.GetPath();
920 int filterIdx = dlg.GetFilterIndex();
921
922 bool bottom = m_footprint->IsFlipped();
923 VECTOR2D origin( m_footprint->GetPosition().x, m_footprint->GetPosition().y );
924
925 EXTRUSION_MATERIAL material = static_cast<EXTRUSION_MATERIAL>( m_extrusionMaterialChoice->GetSelection() );
926
927 KIGFX::COLOR4D c = m_extrusionColorSwatch->GetSwatchColor();
928
930 c = EXTRUDED_3D_BODY::GetDefaultColor( material );
931
932 uint32_t colorKey = EXTRUDED_3D_BODY::PackColorKey( c );
933
934 NULL_REPORTER reporter;
935 STEP_PCB_MODEL model( wxT( "extruded_body" ), &reporter );
936
937 if( !model.AddExtrudedBody( outline, bottom, standoff, height, origin, colorKey, material,
938 m_footprint->GetReference() ) )
939 {
940 wxMessageBox( _( "Failed to create extruded body geometry." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR,
941 this );
942 return;
943 }
944
945 if( standoff > 0.0 )
946 model.AddExtrudedPins( m_footprint, bottom, standoff, origin );
947
948 SHAPE_POLY_SET boardOutline( outline );
949 model.CreatePCB( boardOutline, origin, false );
950
951 bool ok = false;
952
953 switch( filterIdx )
954 {
955 case 0: ok = model.WriteSTEP( path, true, false ); break;
956 case 1: ok = model.WriteGLTF( path ); break;
957 case 2: ok = model.WriteSTL( path ); break;
958 case 3: ok = model.WriteBREP( path ); break;
959 }
960
961 if( !ok )
962 {
963 wxMessageBox( _( "Failed to write file." ), _( "Export Extruded Body" ), wxOK | wxICON_ERROR, this );
964 }
965}
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