← Back to team overview

kicad-developers team mailing list archive

[FEATURE] Array 3D models

 

To provide an option to reduce the size of the 3D model library, I have
implemented an "array" feature for 3D models. A module (footprint) can
reference a single model multiple times, with a dimensional offset between
each copy.

(Note - just the PinHeader models are currently over 1GB! This feature lets
you use a single 3D model for all pin headers or similar repetitive
footprints within a certain series).

Features:

1. Specify repeat count and repeat step in x/y/z axes
2. Save / load implemented. (If no repeat option used, no extra output is
generated - old files are not touched)
3. Render in 3D viewer
4. Render in raytracing viewer
5. Export to VRML (multiple references to single file)
6. Export to STEP

Notes:

a. An exported STEP file will now be (possibly) much smaller as it
references a single small object multiple times
b. There were a couple of bugs I found where model offset units were
incorrectly translated between INCHES and MM

A couple of screenshots:

https://imgur.com/a/EOwPh


Testing:

Wayne verified that the file units for 3D model data are in mm - I *think*
this means that there was previously a bug regarding 3D model offset, where
the scaling factor in the file was interpreted as inches when exporting
(e.g. to STEP)

I believe I have fixed this bug - confirmation would be great.

Cheers,
Oliver
From 1e938c8740d1767c583e9292620f4516aab4a51e Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Mon, 30 Oct 2017 08:56:36 +1100
Subject: [PATCH 1/9] Render arrayed models in 3D viewer

---
 3d-viewer/3d_cache/3d_info.h                       |   6 +
 3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp  |  23 +--
 3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp  | 182 +++++++++++----------
 3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h    |   2 +-
 .../3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp |  81 ++++++---
 5 files changed, 170 insertions(+), 124 deletions(-)

diff --git a/3d-viewer/3d_cache/3d_info.h b/3d-viewer/3d_cache/3d_info.h
index 7204934..9c5cf77 100644
--- a/3d-viewer/3d_cache/3d_info.h
+++ b/3d-viewer/3d_cache/3d_info.h
@@ -47,6 +47,12 @@ struct S3D_INFO
     SGPOINT m_Rotation;     ///< an X,Y,Z rotation (unit = degrees) for the 3D shape
     SGPOINT m_Offset;       ///< an offset (unit = inch) for the 3D shape
     wxString m_Filename;    ///< The 3D shape filename in 3D library
+
+    unsigned int m_RepeatX; ///< Array count along x axis
+    unsigned int m_RepeatY; ///< Array count along y axis
+
+    double m_StepX;         ///< Array step size along x axis
+    double m_StepY;         ///< Array step size along y axis
 };
 
 #endif // INFO_3D_H
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp
index 12f012d..6aa8f87 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Dec  4 2016)
+// C++ code generated with wxFormBuilder (version Mar 22 2017)
 // http://www.wxformbuilder.org/
 //
 // PLEASE DO "NOT" EDIT THIS FILE!
@@ -40,7 +40,7 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	fgSizerScale->Add( m_staticText2, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
 	
 	yscale = new wxTextCtrl( vbScale->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
-	fgSizerScale->Add( yscale, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	fgSizerScale->Add( yscale, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP, 5 );
 	
 	m_spinYscale = new wxSpinButton( vbScale->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
 	fgSizerScale->Add( m_spinYscale, 0, wxALIGN_CENTER_VERTICAL, 5 );
@@ -53,13 +53,13 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	fgSizerScale->Add( zscale, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT, 5 );
 	
 	m_spinZscale = new wxSpinButton( vbScale->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
-	fgSizerScale->Add( m_spinZscale, 0, wxALIGN_CENTER_VERTICAL, 5 );
+	fgSizerScale->Add( m_spinZscale, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM, 5 );
 	
 	
 	vbScale->Add( fgSizerScale, 1, wxEXPAND, 5 );
 	
 	
-	bSizerLeft->Add( vbScale, 0, wxEXPAND, 5 );
+	bSizerLeft->Add( vbScale, 0, wxEXPAND|wxRIGHT|wxTOP, 5 );
 	
 	wxStaticBoxSizer* vbRotate;
 	vbRotate = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Rotation (degrees)") ), wxVERTICAL );
@@ -100,7 +100,7 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	#else
 	yrot->SetMaxLength( 9 );
 	#endif
-	fgSizerRotate->Add( yrot, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	fgSizerRotate->Add( yrot, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP, 5 );
 	
 	m_spinYrot = new wxSpinButton( vbRotate->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
 	fgSizerRotate->Add( m_spinYrot, 0, wxALIGN_CENTER_VERTICAL, 5 );
@@ -121,13 +121,13 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	fgSizerRotate->Add( zrot, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT, 5 );
 	
 	m_spinZrot = new wxSpinButton( vbRotate->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
-	fgSizerRotate->Add( m_spinZrot, 0, wxALIGN_CENTER_VERTICAL, 5 );
+	fgSizerRotate->Add( m_spinZrot, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM, 5 );
 	
 	
 	vbRotate->Add( fgSizerRotate, 1, wxEXPAND, 5 );
 	
 	
-	bSizerLeft->Add( vbRotate, 0, wxEXPAND, 5 );
+	bSizerLeft->Add( vbRotate, 0, wxEXPAND|wxRIGHT|wxTOP, 5 );
 	
 	wxStaticBoxSizer* vbOffset;
 	vbOffset = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Offset") ), wxVERTICAL );
@@ -152,7 +152,7 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	fgSizerOffset->Add( m_staticText22, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
 	
 	yoff = new wxTextCtrl( vbOffset->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
-	fgSizerOffset->Add( yoff, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	fgSizerOffset->Add( yoff, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP, 5 );
 	
 	m_spinYoffset = new wxSpinButton( vbOffset->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
 	fgSizerOffset->Add( m_spinYoffset, 0, wxALIGN_CENTER_VERTICAL, 5 );
@@ -165,13 +165,13 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	fgSizerOffset->Add( zoff, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT, 5 );
 	
 	m_spinZoffset = new wxSpinButton( vbOffset->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
-	fgSizerOffset->Add( m_spinZoffset, 0, wxALIGN_CENTER_VERTICAL, 5 );
+	fgSizerOffset->Add( m_spinZoffset, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM, 5 );
 	
 	
 	vbOffset->Add( fgSizerOffset, 1, wxEXPAND, 5 );
 	
 	
-	bSizerLeft->Add( vbOffset, 0, wxEXPAND, 5 );
+	bSizerLeft->Add( vbOffset, 0, wxEXPAND|wxRIGHT|wxTOP, 5 );
 	
 	
 	bSizermain->Add( bSizerLeft, 0, wxEXPAND, 5 );
@@ -184,6 +184,9 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	
 	bSizerRight->Add( m_SizerPanelView, 1, wxEXPAND, 5 );
 	
+	
+	bSizerRight->Add( 0, 0, 1, wxEXPAND, 5 );
+	
 	wxBoxSizer* bSizer3DButtons;
 	bSizer3DButtons = new wxBoxSizer( wxHORIZONTAL );
 	
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp
index 64d70a3..9c61058 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp
@@ -94,7 +94,7 @@
                         <property name="permission">none</property>
                         <object class="sizeritem" expanded="1">
                             <property name="border">5</property>
-                            <property name="flag">wxEXPAND</property>
+                            <property name="flag">wxEXPAND|wxRIGHT|wxTOP</property>
                             <property name="proportion">0</property>
                             <object class="wxStaticBoxSizer" expanded="1">
                                 <property name="id">wxID_ANY</property>
@@ -121,11 +121,11 @@
                                         <property name="permission">none</property>
                                         <property name="rows">0</property>
                                         <property name="vgap">0</property>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -204,11 +204,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -295,11 +295,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -379,11 +379,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -462,11 +462,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
-                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -553,11 +553,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -637,11 +637,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -720,11 +720,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -811,11 +811,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
-                                            <property name="flag">wxALIGN_CENTER_VERTICAL</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -901,7 +901,7 @@
                         </object>
                         <object class="sizeritem" expanded="1">
                             <property name="border">5</property>
-                            <property name="flag">wxEXPAND</property>
+                            <property name="flag">wxEXPAND|wxRIGHT|wxTOP</property>
                             <property name="proportion">0</property>
                             <object class="wxStaticBoxSizer" expanded="1">
                                 <property name="id">wxID_ANY</property>
@@ -928,11 +928,11 @@
                                         <property name="permission">none</property>
                                         <property name="rows">0</property>
                                         <property name="vgap">0</property>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1011,11 +1011,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1102,11 +1102,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1186,11 +1186,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1269,11 +1269,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
-                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1360,11 +1360,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1444,11 +1444,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1527,11 +1527,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1618,11 +1618,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
-                                            <property name="flag">wxALIGN_CENTER_VERTICAL</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1708,7 +1708,7 @@
                         </object>
                         <object class="sizeritem" expanded="1">
                             <property name="border">5</property>
-                            <property name="flag">wxEXPAND</property>
+                            <property name="flag">wxEXPAND|wxRIGHT|wxTOP</property>
                             <property name="proportion">0</property>
                             <object class="wxStaticBoxSizer" expanded="1">
                                 <property name="id">wxID_ANY</property>
@@ -1735,11 +1735,11 @@
                                         <property name="permission">none</property>
                                         <property name="rows">0</property>
                                         <property name="vgap">0</property>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1818,11 +1818,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1909,11 +1909,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -1993,11 +1993,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2076,11 +2076,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
-                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2167,11 +2167,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2251,11 +2251,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxStaticText" expanded="1">
+                                            <object class="wxStaticText" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2334,11 +2334,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxTextCtrl" expanded="1">
+                                            <object class="wxTextCtrl" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2425,11 +2425,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
-                                            <property name="flag">wxALIGN_CENTER_VERTICAL</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxSpinButton" expanded="1">
+                                            <object class="wxSpinButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2538,27 +2538,37 @@
                         <object class="sizeritem" expanded="1">
                             <property name="border">5</property>
                             <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="spacer" expanded="1">
+                                <property name="height">0</property>
+                                <property name="permission">protected</property>
+                                <property name="width">0</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
                             <property name="proportion">0</property>
-                            <object class="wxBoxSizer" expanded="1">
+                            <object class="wxBoxSizer" expanded="0">
                                 <property name="minimum_size"></property>
                                 <property name="name">bSizer3DButtons</property>
                                 <property name="orient">wxHORIZONTAL</property>
                                 <property name="permission">none</property>
-                                <object class="sizeritem" expanded="1">
+                                <object class="sizeritem" expanded="0">
                                     <property name="border">5</property>
                                     <property name="flag">wxEXPAND</property>
                                     <property name="proportion">1</property>
-                                    <object class="spacer" expanded="1">
+                                    <object class="spacer" expanded="0">
                                         <property name="height">0</property>
                                         <property name="permission">protected</property>
                                         <property name="width">0</property>
                                     </object>
                                 </object>
-                                <object class="sizeritem" expanded="1">
+                                <object class="sizeritem" expanded="0">
                                     <property name="border">5</property>
                                     <property name="flag">wxALIGN_CENTER_HORIZONTAL|wxEXPAND</property>
                                     <property name="proportion">6</property>
-                                    <object class="wxFlexGridSizer" expanded="1">
+                                    <object class="wxFlexGridSizer" expanded="0">
                                         <property name="cols">4</property>
                                         <property name="flexible_direction">wxBOTH</property>
                                         <property name="growablecols">0,1,2,3</property>
@@ -2570,11 +2580,11 @@
                                         <property name="permission">protected</property>
                                         <property name="rows">0</property>
                                         <property name="vgap">0</property>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2663,11 +2673,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2756,11 +2766,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2849,11 +2859,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -2942,11 +2952,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -3035,11 +3045,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -3128,11 +3138,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -3221,11 +3231,11 @@
                                                 <event name="OnUpdateUI"></event>
                                             </object>
                                         </object>
-                                        <object class="sizeritem" expanded="1">
+                                        <object class="sizeritem" expanded="0">
                                             <property name="border">5</property>
                                             <property name="flag">wxALL|wxEXPAND</property>
                                             <property name="proportion">0</property>
-                                            <object class="wxBitmapButton" expanded="1">
+                                            <object class="wxBitmapButton" expanded="0">
                                                 <property name="BottomDockable">1</property>
                                                 <property name="LeftDockable">1</property>
                                                 <property name="RightDockable">1</property>
@@ -3316,11 +3326,11 @@
                                         </object>
                                     </object>
                                 </object>
-                                <object class="sizeritem" expanded="1">
+                                <object class="sizeritem" expanded="0">
                                     <property name="border">5</property>
                                     <property name="flag">wxEXPAND</property>
                                     <property name="proportion">1</property>
-                                    <object class="spacer" expanded="1">
+                                    <object class="spacer" expanded="0">
                                         <property name="height">0</property>
                                         <property name="permission">protected</property>
                                         <property name="width">0</property>
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h
index 6e439cc..4130c24 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Dec  4 2016)
+// C++ code generated with wxFormBuilder (version Mar 22 2017)
 // http://www.wxformbuilder.org/
 //
 // PLEASE DO "NOT" EDIT THIS FILE!
diff --git a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp
index 19b4616..6ddf8f9 100644
--- a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp
+++ b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp
@@ -1041,45 +1041,72 @@ void C3D_RENDER_OGL_LEGACY::render_3D_module( const MODULE* module,
 
                     if( modelPtr )
                     {
-                        if( ( (!aRenderTransparentOnly) && modelPtr->Have_opaque() ) ||
-                            ( aRenderTransparentOnly && modelPtr->Have_transparent() ) )
-                        {
-                            glPushMatrix();
 
-                            glTranslatef( sM->m_Offset.x * 25.4f,
-                                          sM->m_Offset.y * 25.4f,
-                                          sM->m_Offset.z * 25.4f );
+                        unsigned int xCount = sM->m_RepeatX;
+                        unsigned int yCount = sM->m_RepeatY;
 
-                            glRotatef( -sM->m_Rotation.z, 0.0f, 0.0f, 1.0f );
-                            glRotatef( -sM->m_Rotation.y, 0.0f, 1.0f, 0.0f );
-                            glRotatef( -sM->m_Rotation.x, 1.0f, 0.0f, 0.0f );
+                        if( xCount < 1 )
+                            xCount = 1;
+                        if( yCount < 1 )
+                            yCount = 1;
 
-                            glScalef( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z );
+                        double xOffset, yOffset = 0;
 
-                            if( aRenderTransparentOnly )
-                                modelPtr->Draw_transparent();
-                            else
-                                modelPtr->Draw_opaque();
+                        // Array model along x axis
+                        for( unsigned int ii = 0; ii < xCount; ii++ )
+                        {
+                            xOffset = ii * sM->m_StepX;
 
-                            if( m_settings.GetFlag( FL_RENDER_OPENGL_SHOW_MODEL_BBOX ) )
+                            // Array model along y axis
+                            for( unsigned int jj=0; jj < yCount; jj++ )
                             {
-                                glEnable( GL_BLEND );
-                                glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+                                yOffset = jj * sM->m_StepY;
 
-                                glLineWidth( 1 );
-                                modelPtr->Draw_bboxes();
+                                if( ( (!aRenderTransparentOnly) && modelPtr->Have_opaque() ) ||
+                                    ( aRenderTransparentOnly && modelPtr->Have_transparent() ) )
+                                {
+                                    glPushMatrix();
 
-                                glDisable( GL_LIGHTING );
+                                    glTranslatef( sM->m_Offset.x * 25.4f + xOffset,
+                                                  sM->m_Offset.y * 25.4f + yOffset,
+                                                  sM->m_Offset.z * 25.4f );
 
-                                glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
+                                    glRotatef( -sM->m_Rotation.z, 0.0f, 0.0f, 1.0f );
+                                    glRotatef( -sM->m_Rotation.y, 0.0f, 1.0f, 0.0f );
+                                    glRotatef( -sM->m_Rotation.x, 1.0f, 0.0f, 0.0f );
 
-                                glLineWidth( 4 );
-                                modelPtr->Draw_bbox();
+                                    glScalef( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z );
 
-                                glEnable( GL_LIGHTING );
-                            }
+                                    if( aRenderTransparentOnly )
+                                    {
+                                        modelPtr->Draw_transparent();
+                                    }
+                                    else
+                                    {
+                                        modelPtr->Draw_opaque();
+                                    }
+
+                                    if( m_settings.GetFlag( FL_RENDER_OPENGL_SHOW_MODEL_BBOX ) )
+                                    {
+                                        glEnable( GL_BLEND );
+                                        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+
+                                        glLineWidth( 1 );
+                                        modelPtr->Draw_bboxes();
 
-                            glPopMatrix();
+                                        glDisable( GL_LIGHTING );
+
+                                        glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
+
+                                        glLineWidth( 4 );
+                                        modelPtr->Draw_bbox();
+
+                                        glEnable( GL_LIGHTING );
+                                    }
+
+                                    glPopMatrix();
+                                }
+                            }
                         }
                     }
                 }
-- 
2.7.4

From 53ff056dc8c74f74b5d70dca95ce06ed87d1644b Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Mon, 30 Oct 2017 21:53:37 +1100
Subject: [PATCH 2/9] Array feature in raytracing renderer

---
 .../c3d_render_createscene.cpp                     | 88 ++++++++++++++--------
 1 file changed, 55 insertions(+), 33 deletions(-)

diff --git a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
index 592d141..b2faa32 100644
--- a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
+++ b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
@@ -1253,41 +1253,63 @@ void C3D_RENDER_RAYTRACING::load_3D_models()
 
             while( sM != eM )
             {
-                // get it from cache
-                const S3DMODEL *modelPtr =
-                        m_settings.Get3DCacheManager()->GetModel( sM->m_Filename );
+                unsigned int xCount = sM->m_RepeatX;
+                unsigned int yCount = sM->m_RepeatY;
 
-                // only add it if the return is not NULL
-                if( modelPtr )
+                if( xCount < 1 )
+                    xCount = 1;
+
+                if( yCount < 1 )
+                    yCount = 1;
+
+                double xOffset, yOffset = 0;
+
+                for( unsigned int ii=0; ii < xCount; ii++ )
                 {
-                    glm::mat4 modelMatrix = moduleMatrix;
-
-                    modelMatrix = glm::translate( modelMatrix,
-                                                  SFVEC3F( sM->m_Offset.x * 25.4f,
-                                                           sM->m_Offset.y * 25.4f,
-                                                           sM->m_Offset.z * 25.4f ) );
-
-                    modelMatrix = glm::rotate( modelMatrix,
-                                               (float)-( sM->m_Rotation.z / 180.0f ) *
-                                               glm::pi<float>(),
-                                               SFVEC3F( 0.0f, 0.0f, 1.0f ) );
-
-                    modelMatrix = glm::rotate( modelMatrix,
-                                               (float)-( sM->m_Rotation.y / 180.0f ) *
-                                               glm::pi<float>(),
-                                               SFVEC3F( 0.0f, 1.0f, 0.0f ) );
-
-                    modelMatrix = glm::rotate( modelMatrix,
-                                               (float)-( sM->m_Rotation.x / 180.0f ) *
-                                               glm::pi<float>(),
-                                               SFVEC3F( 1.0f, 0.0f, 0.0f ) );
-
-                    modelMatrix = glm::scale( modelMatrix,
-                                              SFVEC3F( sM->m_Scale.x,
-                                                       sM->m_Scale.y,
-                                                       sM->m_Scale.z ) );
-
-                    add_3D_models( modelPtr, modelMatrix );
+                    xOffset = ii * sM->m_StepX;
+
+                    for( unsigned int jj=0; jj < yCount; jj++ )
+                    {
+
+                        yOffset = jj * sM->m_StepY;
+
+                        S3DMODEL *modelPtr = m_settings.Get3DCacheManager()->GetModel( sM->m_Filename );
+
+                        if( !modelPtr )
+                        {
+                            continue;
+                        }
+
+                        glm::mat4 modelMatrix = moduleMatrix;
+
+                        modelMatrix = glm::translate( modelMatrix,
+                                                      SFVEC3F( sM->m_Offset.x * 25.4f,
+                                                               sM->m_Offset.y * 25.4f,
+                                                               sM->m_Offset.z * 25.4f ) );
+
+                        modelMatrix = glm::rotate( modelMatrix,
+                                                   (float)-( sM->m_Rotation.z / 180.0f ) *
+                                                   glm::pi<float>(),
+                                                   SFVEC3F( 0.0f, 0.0f, 1.0f ) );
+
+                        modelMatrix = glm::rotate( modelMatrix,
+                                                   (float)-( sM->m_Rotation.y / 180.0f ) *
+                                                   glm::pi<float>(),
+                                                   SFVEC3F( 0.0f, 1.0f, 0.0f ) );
+
+                        modelMatrix = glm::rotate( modelMatrix,
+                                                   (float)-( sM->m_Rotation.x / 180.0f ) *
+                                                   glm::pi<float>(),
+                                                   SFVEC3F( 1.0f, 0.0f, 0.0f ) );
+
+                        modelMatrix = glm::scale( modelMatrix,
+                                                  SFVEC3F( sM->m_Scale.x,
+                                                           sM->m_Scale.y,
+                                                           sM->m_Scale.z ) );
+
+                        add_3D_models( modelPtr, modelMatrix );
+                    }
+
                 }
 
                 ++sM;
-- 
2.7.4

From 67376cfd5938f3881c45979fcc5ec380580d9b39 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Mon, 30 Oct 2017 22:10:49 +1100
Subject: [PATCH 3/9] Load / Save 3D model repeat

- Repeat no longer stacks
- Repeat occurs on x / y / z axes
- Write only if repeat is > 1
- Parse functionality to read repeat data back in
---
 3d-viewer/3d_cache/3d_info.h                       |  6 +-
 .../3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp | 93 ++++++++++------------
 .../c3d_render_createscene.cpp                     | 93 ++++++++++------------
 common/dsnlexer.cpp                                |  9 ++-
 common/pcb.keywords                                |  1 +
 pcbnew/kicad_plugin.cpp                            | 11 +++
 pcbnew/kicad_plugin.h                              |  3 +-
 pcbnew/pcb_parser.cpp                              | 33 +++++++-
 8 files changed, 136 insertions(+), 113 deletions(-)

diff --git a/3d-viewer/3d_cache/3d_info.h b/3d-viewer/3d_cache/3d_info.h
index 9c5cf77..365170a 100644
--- a/3d-viewer/3d_cache/3d_info.h
+++ b/3d-viewer/3d_cache/3d_info.h
@@ -48,11 +48,9 @@ struct S3D_INFO
     SGPOINT m_Offset;       ///< an offset (unit = inch) for the 3D shape
     wxString m_Filename;    ///< The 3D shape filename in 3D library
 
-    unsigned int m_RepeatX; ///< Array count along x axis
-    unsigned int m_RepeatY; ///< Array count along y axis
+    unsigned int m_Repeat;  ///< Number of times to repeat the model (1 = single instance)
 
-    double m_StepX;         ///< Array step size along x axis
-    double m_StepY;         ///< Array step size along y axis
+    SGPOINT m_Step;         ///< Array step size
 };
 
 #endif // INFO_3D_H
diff --git a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp
index 6ddf8f9..47712f1 100644
--- a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp
+++ b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp
@@ -1039,74 +1039,63 @@ void C3D_RENDER_OGL_LEGACY::render_3D_module( const MODULE* module,
                     // It is not present, try get it from cache
                     const C_OGL_3DMODEL *modelPtr = m_3dmodel_map[ sM->m_Filename ];
 
-                    if( modelPtr )
-                    {
-
-                        unsigned int xCount = sM->m_RepeatX;
-                        unsigned int yCount = sM->m_RepeatY;
+                    if( !modelPtr )
+                        continue;
 
-                        if( xCount < 1 )
-                            xCount = 1;
-                        if( yCount < 1 )
-                            yCount = 1;
+                    unsigned int repeat = sM->m_Repeat;
 
-                        double xOffset, yOffset = 0;
+                    if( repeat < 1 )
+                        repeat = 1;
 
-                        // Array model along x axis
-                        for( unsigned int ii = 0; ii < xCount; ii++ )
-                        {
-                            xOffset = ii * sM->m_StepX;
+                    double xOffset, yOffset, zOffset = 0;
 
-                            // Array model along y axis
-                            for( unsigned int jj=0; jj < yCount; jj++ )
-                            {
-                                yOffset = jj * sM->m_StepY;
-
-                                if( ( (!aRenderTransparentOnly) && modelPtr->Have_opaque() ) ||
-                                    ( aRenderTransparentOnly && modelPtr->Have_transparent() ) )
-                                {
-                                    glPushMatrix();
+                    for( unsigned int ii = 0; ii < repeat; ii++ )
+                    {
+                        xOffset = ii * sM->m_Step.x + sM->m_Offset.x;
+                        yOffset = ii * sM->m_Step.y + sM->m_Offset.y;
+                        zOffset = ii * sM->m_Step.z + sM->m_Offset.z;
 
-                                    glTranslatef( sM->m_Offset.x * 25.4f + xOffset,
-                                                  sM->m_Offset.y * 25.4f + yOffset,
-                                                  sM->m_Offset.z * 25.4f );
+                        if( ( (!aRenderTransparentOnly) && modelPtr->Have_opaque() ) ||
+                            ( aRenderTransparentOnly && modelPtr->Have_transparent() ) )
+                        {
+                            glPushMatrix();
 
-                                    glRotatef( -sM->m_Rotation.z, 0.0f, 0.0f, 1.0f );
-                                    glRotatef( -sM->m_Rotation.y, 0.0f, 1.0f, 0.0f );
-                                    glRotatef( -sM->m_Rotation.x, 1.0f, 0.0f, 0.0f );
+                            glTranslatef( xOffset, yOffset, zOffset );
 
-                                    glScalef( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z );
+                            glRotatef( -sM->m_Rotation.z, 0.0f, 0.0f, 1.0f );
+                            glRotatef( -sM->m_Rotation.y, 0.0f, 1.0f, 0.0f );
+                            glRotatef( -sM->m_Rotation.x, 1.0f, 0.0f, 0.0f );
 
-                                    if( aRenderTransparentOnly )
-                                    {
-                                        modelPtr->Draw_transparent();
-                                    }
-                                    else
-                                    {
-                                        modelPtr->Draw_opaque();
-                                    }
+                            glScalef( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z );
 
-                                    if( m_settings.GetFlag( FL_RENDER_OPENGL_SHOW_MODEL_BBOX ) )
-                                    {
-                                        glEnable( GL_BLEND );
-                                        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+                            if( aRenderTransparentOnly )
+                            {
+                                modelPtr->Draw_transparent();
+                            }
+                            else
+                            {
+                                modelPtr->Draw_opaque();
+                            }
 
-                                        glLineWidth( 1 );
-                                        modelPtr->Draw_bboxes();
+                            if( m_settings.GetFlag( FL_RENDER_OPENGL_SHOW_MODEL_BBOX ) )
+                            {
+                                glEnable( GL_BLEND );
+                                glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
 
-                                        glDisable( GL_LIGHTING );
+                                glLineWidth( 1 );
+                                modelPtr->Draw_bboxes();
 
-                                        glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
+                                glDisable( GL_LIGHTING );
 
-                                        glLineWidth( 4 );
-                                        modelPtr->Draw_bbox();
+                                glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
 
-                                        glEnable( GL_LIGHTING );
-                                    }
+                                glLineWidth( 4 );
+                                modelPtr->Draw_bbox();
 
-                                    glPopMatrix();
-                                }
+                                glEnable( GL_LIGHTING );
                             }
+
+                            glPopMatrix();
                         }
                     }
                 }
diff --git a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
index b2faa32..84dad6d 100644
--- a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
+++ b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
@@ -1253,63 +1253,54 @@ void C3D_RENDER_RAYTRACING::load_3D_models()
 
             while( sM != eM )
             {
-                unsigned int xCount = sM->m_RepeatX;
-                unsigned int yCount = sM->m_RepeatY;
+                S3DMODEL *modelPtr = m_settings.Get3DCacheManager()->GetModel( sM->m_Filename );
 
-                if( xCount < 1 )
-                    xCount = 1;
-
-                if( yCount < 1 )
-                    yCount = 1;
-
-                double xOffset, yOffset = 0;
-
-                for( unsigned int ii=0; ii < xCount; ii++ )
+                if( !modelPtr )
                 {
-                    xOffset = ii * sM->m_StepX;
-
-                    for( unsigned int jj=0; jj < yCount; jj++ )
-                    {
-
-                        yOffset = jj * sM->m_StepY;
-
-                        S3DMODEL *modelPtr = m_settings.Get3DCacheManager()->GetModel( sM->m_Filename );
-
-                        if( !modelPtr )
-                        {
-                            continue;
-                        }
-
-                        glm::mat4 modelMatrix = moduleMatrix;
-
-                        modelMatrix = glm::translate( modelMatrix,
-                                                      SFVEC3F( sM->m_Offset.x * 25.4f,
-                                                               sM->m_Offset.y * 25.4f,
-                                                               sM->m_Offset.z * 25.4f ) );
-
-                        modelMatrix = glm::rotate( modelMatrix,
-                                                   (float)-( sM->m_Rotation.z / 180.0f ) *
-                                                   glm::pi<float>(),
-                                                   SFVEC3F( 0.0f, 0.0f, 1.0f ) );
-
-                        modelMatrix = glm::rotate( modelMatrix,
-                                                   (float)-( sM->m_Rotation.y / 180.0f ) *
-                                                   glm::pi<float>(),
-                                                   SFVEC3F( 0.0f, 1.0f, 0.0f ) );
+                    continue;
+                }
 
-                        modelMatrix = glm::rotate( modelMatrix,
-                                                   (float)-( sM->m_Rotation.x / 180.0f ) *
-                                                   glm::pi<float>(),
-                                                   SFVEC3F( 1.0f, 0.0f, 0.0f ) );
+                unsigned int repeat = sM->m_Repeat;
 
-                        modelMatrix = glm::scale( modelMatrix,
-                                                  SFVEC3F( sM->m_Scale.x,
-                                                           sM->m_Scale.y,
-                                                           sM->m_Scale.z ) );
+                if( repeat < 1 )
+                    repeat = 1;
 
-                        add_3D_models( modelPtr, modelMatrix );
-                    }
+                double xOffset, yOffset, zOffset = 0;
 
+                for( unsigned int ii = 0; ii < repeat; ii++ )
+                {
+                    xOffset = ii * sM->m_Step.x + sM->m_Offset.x;
+                    yOffset = ii * sM->m_Step.y + sM->m_Offset.y;
+                    zOffset = ii * sM->m_Step.z + sM->m_Offset.z;
+
+                    glm::mat4 modelMatrix = moduleMatrix;
+
+                    modelMatrix = glm::translate( modelMatrix,
+                                                  SFVEC3F( xOffset * 25.4f,
+                                                           yOffset * 25.4f,
+                                                           zOffset * 25.4f ) );
+
+                    modelMatrix = glm::rotate( modelMatrix,
+                                               (float)-( sM->m_Rotation.z / 180.0f ) *
+                                               glm::pi<float>(),
+                                               SFVEC3F( 0.0f, 0.0f, 1.0f ) );
+
+                    modelMatrix = glm::rotate( modelMatrix,
+                                               (float)-( sM->m_Rotation.y / 180.0f ) *
+                                               glm::pi<float>(),
+                                               SFVEC3F( 0.0f, 1.0f, 0.0f ) );
+
+                    modelMatrix = glm::rotate( modelMatrix,
+                                               (float)-( sM->m_Rotation.x / 180.0f ) *
+                                               glm::pi<float>(),
+                                               SFVEC3F( 1.0f, 0.0f, 0.0f ) );
+
+                    modelMatrix = glm::scale( modelMatrix,
+                                              SFVEC3F( sM->m_Scale.x,
+                                                       sM->m_Scale.y,
+                                                       sM->m_Scale.z ) );
+
+                    add_3D_models( modelPtr, modelMatrix );
                 }
 
                 ++sM;
diff --git a/common/dsnlexer.cpp b/common/dsnlexer.cpp
index 696ebcc..d948bf4 100644
--- a/common/dsnlexer.cpp
+++ b/common/dsnlexer.cpp
@@ -353,7 +353,9 @@ bool DSNLEXER::IsSymbol( int aTok )
 void DSNLEXER::Expecting( int aTok )
 {
     wxString errText = wxString::Format(
-        _("Expecting '%s'"), GetChars( GetTokenString( aTok ) ) );
+        _("Expecting '%s' (found '%s')"),
+        GetChars( GetTokenString( aTok ) ),
+        GetChars( GetTokenString( CurTok() ) ) );
     THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
 }
 
@@ -361,7 +363,10 @@ void DSNLEXER::Expecting( int aTok )
 void DSNLEXER::Expecting( const char* text )
 {
     wxString errText = wxString::Format(
-        _("Expecting '%s'"), GetChars( wxString::FromUTF8( text ) ) );
+        _("Expecting '%s' (found '%s')"),
+        GetChars( wxString::FromUTF8( text ) ),
+        GetChars( GetTokenString( CurTok() ) ) );
+
     THROW_PARSE_ERROR( errText, CurSource(), CurLine(), CurLineNumber(), CurOffset() );
 }
 
diff --git a/common/pcb.keywords b/common/pcb.keywords
index 670bc8a..8e517e8 100644
--- a/common/pcb.keywords
+++ b/common/pcb.keywords
@@ -148,6 +148,7 @@ rev
 rect
 rect_delta
 reference
+repeat
 right
 rotate
 roundrect
diff --git a/pcbnew/kicad_plugin.cpp b/pcbnew/kicad_plugin.cpp
index 7d76413..2e6226c 100644
--- a/pcbnew/kicad_plugin.cpp
+++ b/pcbnew/kicad_plugin.cpp
@@ -1156,8 +1156,19 @@ void PCB_IO::format( MODULE* aModule, int aNestLevel ) const
                           Double2Str( bs3D->m_Rotation.y ).c_str(),
                           Double2Str( bs3D->m_Rotation.z ).c_str() );
 
+            // Write model repetition data (if used)
+            if( bs3D->m_Repeat > 1 )
+            {
+                m_out->Print( aNestLevel + 2, "(repeat %d (xyz %s %s %s))\n",
+                              bs3D->m_Repeat,
+                              Double2Str( bs3D->m_Step.x ).c_str(),
+                              Double2Str( bs3D->m_Step.y ).c_str(),
+                              Double2Str( bs3D->m_Step.z ).c_str() );
+            }
+
             m_out->Print( aNestLevel+1, ")\n" );
         }
+
         ++bs3D;
     }
 
diff --git a/pcbnew/kicad_plugin.h b/pcbnew/kicad_plugin.h
index eff7590..5c78bf7 100644
--- a/pcbnew/kicad_plugin.h
+++ b/pcbnew/kicad_plugin.h
@@ -44,7 +44,8 @@ class NETINFO_MAPPING;
 //#define SEXPR_BOARD_FILE_VERSION    20160815  // differential pair settings per net class
 //#define SEXPR_BOARD_FILE_VERSION    20170123  // EDA_TEXT refactor, moved 'hide'
 //#define SEXPR_BOARD_FILE_VERSION    20170920  // long pad names and custom pad shape
-#define SEXPR_BOARD_FILE_VERSION      20170922  // Keepout zones can exist on multiple layers
+//#define SEXPR_BOARD_FILE_VERSION    20170922  // Keepout zones can exist on multiple layers
+#define SEXPR_BOARD_FILE_VERSION      20171030  // 3D models can be arrayed in x and y directions
 
 #define CTL_STD_LAYER_NAMES         (1 << 0)    ///< Use English Standard layer names
 #define CTL_OMIT_NETS               (1 << 1)    ///< Omit pads net names (useless in library)
diff --git a/pcbnew/pcb_parser.cpp b/pcbnew/pcb_parser.cpp
index ded32c6..9fd4c36 100644
--- a/pcbnew/pcb_parser.cpp
+++ b/pcbnew/pcb_parser.cpp
@@ -362,7 +362,8 @@ MODULE_3D_SETTINGS* PCB_PARSER::parse3DModel()
             n3D->m_Offset.x = parseDouble( "x value" );
             n3D->m_Offset.y = parseDouble( "y value" );
             n3D->m_Offset.z = parseDouble( "z value" );
-            NeedRIGHT();
+
+            NeedRIGHT(); // T_xyz
             break;
 
         case T_scale:
@@ -375,7 +376,8 @@ MODULE_3D_SETTINGS* PCB_PARSER::parse3DModel()
             n3D->m_Scale.x = parseDouble( "x value" );
             n3D->m_Scale.y = parseDouble( "y value" );
             n3D->m_Scale.z = parseDouble( "z value" );
-            NeedRIGHT();
+
+            NeedRIGHT(); // T_xyz
             break;
 
         case T_rotate:
@@ -388,11 +390,36 @@ MODULE_3D_SETTINGS* PCB_PARSER::parse3DModel()
             n3D->m_Rotation.x = parseDouble( "x value" );
             n3D->m_Rotation.y = parseDouble( "y value" );
             n3D->m_Rotation.z = parseDouble( "z value" );
+
+            NeedRIGHT(); // T_xyz
+            break;
+
+        // Model repeat (array) in either x or y (or both) axes
+        case T_repeat:
+
+            n3D->m_Repeat = parseInt( "model repeat" );
+
+            if( n3D->m_Repeat < 1 )
+                n3D->m_Repeat = 1;
+
+            NeedLEFT();
+
+            token = NextTok();
+
+            if( token != T_xyz )
+            {
+                Expecting( T_xyz );
+            }
+
+            n3D->m_Step.x = parseDouble( "x value" );
+            n3D->m_Step.y = parseDouble( "y value" );
+            n3D->m_Step.z = parseDouble( "z value" );
+
             NeedRIGHT();
             break;
 
         default:
-            Expecting( "at, scale, or rotate" );
+            Expecting( "at, scale, rotate or repeat" );
         }
 
         NeedRIGHT();
-- 
2.7.4

From 80c190a16043b942fc512993d4b936348fcc3c4b Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Wed, 1 Nov 2017 22:37:10 +1100
Subject: [PATCH 4/9] Fix raytracing rendering

- Modelptr iteration was occuring at the wrong spot
- Incremental offset scaling was out by 25.4 times
---
 .../3d_render_raytracing/c3d_render_createscene.cpp  | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
index 84dad6d..a81e778 100644
--- a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
+++ b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
@@ -1253,13 +1253,6 @@ void C3D_RENDER_RAYTRACING::load_3D_models()
 
             while( sM != eM )
             {
-                S3DMODEL *modelPtr = m_settings.Get3DCacheManager()->GetModel( sM->m_Filename );
-
-                if( !modelPtr )
-                {
-                    continue;
-                }
-
                 unsigned int repeat = sM->m_Repeat;
 
                 if( repeat < 1 )
@@ -1269,6 +1262,13 @@ void C3D_RENDER_RAYTRACING::load_3D_models()
 
                 for( unsigned int ii = 0; ii < repeat; ii++ )
                 {
+                    S3DMODEL *modelPtr = m_settings.Get3DCacheManager()->GetModel( sM->m_Filename );
+
+                    if( !modelPtr )
+                    {
+                        continue;
+                    }
+
                     xOffset = ii * sM->m_Step.x + sM->m_Offset.x;
                     yOffset = ii * sM->m_Step.y + sM->m_Offset.y;
                     zOffset = ii * sM->m_Step.z + sM->m_Offset.z;
@@ -1276,9 +1276,9 @@ void C3D_RENDER_RAYTRACING::load_3D_models()
                     glm::mat4 modelMatrix = moduleMatrix;
 
                     modelMatrix = glm::translate( modelMatrix,
-                                                  SFVEC3F( xOffset * 25.4f,
-                                                           yOffset * 25.4f,
-                                                           zOffset * 25.4f ) );
+                                                  SFVEC3F( xOffset, // * 25.4f,
+                                                           yOffset, // * 25.4f,
+                                                           zOffset ) ); // * 25.4f ) );
 
                     modelMatrix = glm::rotate( modelMatrix,
                                                (float)-( sM->m_Rotation.z / 180.0f ) *
-- 
2.7.4

From 754d10452bd9bccaa822818b7408e63f15677c74 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Wed, 1 Nov 2017 22:37:51 +1100
Subject: [PATCH 5/9] Fixed VRML output

- Now models are output correctly
- Had to make a scaling fix to the model offset (was it even working properly before?)
---
 pcbnew/exporters/export_vrml.cpp | 188 ++++++++++++++++++++-------------------
 1 file changed, 98 insertions(+), 90 deletions(-)

diff --git a/pcbnew/exporters/export_vrml.cpp b/pcbnew/exporters/export_vrml.cpp
index 66dda6b..31b0e01 100644
--- a/pcbnew/exporters/export_vrml.cpp
+++ b/pcbnew/exporters/export_vrml.cpp
@@ -1319,6 +1319,11 @@ static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb,
             continue;
         }
 
+        unsigned int repeat = sM->m_Repeat;
+
+        if( repeat < 1 )
+            repeat = 1;
+
         /* Calculate 3D shape rotation:
          * this is the rotation parameters, with an additional 180 deg rotation
          * for footprints that are flipped
@@ -1349,120 +1354,123 @@ static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb,
         compose_quat( q1, q2, q1 );
         from_quat( q1, rot );
 
-        // adjust 3D shape local offset position
-        // they are given in inch, so they are converted in board IU.
-        double offsetx = sM->m_Offset.x * IU_PER_MILS * 1000.0;
-        double offsety = sM->m_Offset.y * IU_PER_MILS * 1000.0;
-        double offsetz = sM->m_Offset.z * IU_PER_MILS * 1000.0;
+        const double OFFSET_FACTOR = 1000 * IU_PER_MILS / 25.4f;
 
-        if( isFlipped )
-            offsetz = -offsetz;
-        else // In normal mode, Y axis is reversed in Pcbnew.
-            offsety = -offsety;
+        for( unsigned int ii = 0; ii < repeat; ii++ )
+        {
 
-        RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );
+            double offsetx = ( ii * sM->m_Step.x + sM->m_Offset.x ) * OFFSET_FACTOR;
+            double offsety = ( ii * sM->m_Step.y + sM->m_Offset.y ) * OFFSET_FACTOR;
+            double offsetz = ( ii * sM->m_Step.z + sM->m_Offset.z ) * OFFSET_FACTOR;
 
-        SGPOINT trans;
-        trans.x = ( offsetx + aModule->GetPosition().x ) * BOARD_SCALE + aModel.m_tx;
-        trans.y = -(offsety + aModule->GetPosition().y) * BOARD_SCALE - aModel.m_ty;
-        trans.z = (offsetz * BOARD_SCALE ) + aModel.GetLayerZ( aModule->GetLayer() );
+            if( isFlipped )
+                offsetz = -offsetz;
+            else // In normal mode, Y axis is reversed in Pcbnew.
+                offsety = -offsety;
 
-        if( USE_INLINES )
-        {
-            wxFileName srcFile = cache->GetResolver()->ResolvePath( sM->m_Filename );
-            wxFileName dstFile;
-            dstFile.SetPath( SUBDIR_3D );
-            dstFile.SetName( srcFile.GetName() );
-            dstFile.SetExt( "wrl"  );
+            RotatePoint( &offsetx, &offsety, aModule->GetOrientation() );
 
-            // copy the file if necessary
-            wxDateTime srcModTime = srcFile.GetModificationTime();
-            wxDateTime destModTime = srcModTime;
+            SGPOINT trans;
+            trans.x = ( offsetx + aModule->GetPosition().x ) * BOARD_SCALE + aModel.m_tx;
+            trans.y = -(offsety + aModule->GetPosition().y) * BOARD_SCALE - aModel.m_ty;
+            trans.z = (offsetz * BOARD_SCALE ) + aModel.GetLayerZ( aModule->GetLayer() );
 
-            destModTime.SetToCurrent();
+            if( USE_INLINES )
+            {
+                wxFileName srcFile = cache->GetResolver()->ResolvePath( sM->m_Filename );
+                wxFileName dstFile;
+                dstFile.SetPath( SUBDIR_3D );
+                dstFile.SetName( srcFile.GetName() );
+                dstFile.SetExt( "wrl"  );
 
-            if( dstFile.FileExists() )
-                destModTime = dstFile.GetModificationTime();
+                // copy the file if necessary
+                wxDateTime srcModTime = srcFile.GetModificationTime();
+                wxDateTime destModTime = srcModTime;
 
-            if( srcModTime != destModTime )
-            {
-                wxLogDebug( "Copying 3D model %s to %s.",
-                            GetChars( srcFile.GetFullPath() ),
-                            GetChars( dstFile.GetFullPath() ) );
+                destModTime.SetToCurrent();
 
-                wxString fileExt = srcFile.GetExt();
-                fileExt.LowerCase();
+                if( dstFile.FileExists() )
+                    destModTime = dstFile.GetModificationTime();
 
-                // copy VRML models and use the scenegraph library to
-                // translate other model types
-                if( fileExt == "wrl" )
+                if( srcModTime != destModTime )
                 {
-                    if( !wxCopyFile( srcFile.GetFullPath(), dstFile.GetFullPath() ) )
-                        continue;
+                    wxLogDebug( "Copying 3D model %s to %s.",
+                                GetChars( srcFile.GetFullPath() ),
+                                GetChars( dstFile.GetFullPath() ) );
+
+                    wxString fileExt = srcFile.GetExt();
+                    fileExt.LowerCase();
+
+                    // copy VRML models and use the scenegraph library to
+                    // translate other model types
+                    if( fileExt == "wrl" )
+                    {
+                        if( !wxCopyFile( srcFile.GetFullPath(), dstFile.GetFullPath() ) )
+                            continue;
+                    }
+                    else
+                    {
+                        if( !S3D::WriteVRML( dstFile.GetFullPath().ToUTF8(), true, mod3d, USE_DEFS, true ) )
+                            continue;
+                    }
                 }
-                else
+
+                (*aOutputFile) << "Transform {\n";
+
+                // only write a rotation if it is >= 0.1 deg
+                if( std::abs( rot[3] ) > 0.0001745 )
                 {
-                    if( !S3D::WriteVRML( dstFile.GetFullPath().ToUTF8(), true, mod3d, USE_DEFS, true ) )
-                        continue;
+                    (*aOutputFile) << "  rotation " << std::setprecision( 5 );
+                    (*aOutputFile) << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
                 }
-            }
-
-            (*aOutputFile) << "Transform {\n";
 
-            // only write a rotation if it is >= 0.1 deg
-            if( std::abs( rot[3] ) > 0.0001745 )
-            {
-                (*aOutputFile) << "  rotation " << std::setprecision( 5 );
-                (*aOutputFile) << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
-            }
+                (*aOutputFile) << "  translation " << std::setprecision( PRECISION );
+                (*aOutputFile) << trans.x << " ";
+                (*aOutputFile) << trans.y << " ";
+                (*aOutputFile) << trans.z << "\n";
 
-            (*aOutputFile) << "  translation " << std::setprecision( PRECISION );
-            (*aOutputFile) << trans.x << " ";
-            (*aOutputFile) << trans.y << " ";
-            (*aOutputFile) << trans.z << "\n";
+                (*aOutputFile) << "  scale ";
+                (*aOutputFile) << sM->m_Scale.x << " ";
+                (*aOutputFile) << sM->m_Scale.y << " ";
+                (*aOutputFile) << sM->m_Scale.z << "\n";
 
-            (*aOutputFile) << "  scale ";
-            (*aOutputFile) << sM->m_Scale.x << " ";
-            (*aOutputFile) << sM->m_Scale.y << " ";
-            (*aOutputFile) << sM->m_Scale.z << "\n";
+                (*aOutputFile) << "  children [\n    Inline {\n      url \"";
 
-            (*aOutputFile) << "  children [\n    Inline {\n      url \"";
+                if( USE_RELPATH )
+                {
+                    wxFileName tmp = dstFile;
+                    tmp.SetExt( "" );
+                    tmp.SetName( "" );
+                    tmp.RemoveLastDir();
+                    dstFile.MakeRelativeTo( tmp.GetPath() );
+                }
 
-            if( USE_RELPATH )
-            {
-                wxFileName tmp = dstFile;
-                tmp.SetExt( "" );
-                tmp.SetName( "" );
-                tmp.RemoveLastDir();
-                dstFile.MakeRelativeTo( tmp.GetPath() );
+                wxString fn = dstFile.GetFullPath();
+                fn.Replace( "\\", "/" );
+                (*aOutputFile) << TO_UTF8( fn ) << "\"\n    } ]\n";
+                (*aOutputFile) << "  }\n";
             }
+            else
+            {
+                IFSG_TRANSFORM* modelShape = new IFSG_TRANSFORM( aModel.m_OutputPCB.GetRawPtr() );
 
-            wxString fn = dstFile.GetFullPath();
-            fn.Replace( "\\", "/" );
-            (*aOutputFile) << TO_UTF8( fn ) << "\"\n    } ]\n";
-            (*aOutputFile) << "  }\n";
-        }
-        else
-        {
-            IFSG_TRANSFORM* modelShape = new IFSG_TRANSFORM( aModel.m_OutputPCB.GetRawPtr() );
-
-            // only write a rotation if it is >= 0.1 deg
-            if( std::abs( rot[3] ) > 0.0001745 )
-                modelShape->SetRotation( SGVECTOR( rot[0], rot[1], rot[2] ), rot[3] );
+                // only write a rotation if it is >= 0.1 deg
+                if( std::abs( rot[3] ) > 0.0001745 )
+                    modelShape->SetRotation( SGVECTOR( rot[0], rot[1], rot[2] ), rot[3] );
 
-            modelShape->SetTranslation( trans );
-            modelShape->SetScale( SGPOINT( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z ) );
+                modelShape->SetTranslation( trans );
+                modelShape->SetScale( SGPOINT( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z ) );
 
-            if( NULL == S3D::GetSGNodeParent( mod3d ) )
-            {
-                aModel.m_components.push_back( mod3d );
-                modelShape->AddChildNode( mod3d );
-            }
-            else
-            {
-                modelShape->AddRefNode( mod3d );
+                if( NULL == S3D::GetSGNodeParent( mod3d ) )
+                {
+                    aModel.m_components.push_back( mod3d );
+                    modelShape->AddChildNode( mod3d );
+                }
+                else
+                {
+                    modelShape->AddRefNode( mod3d );
+                }
             }
-
         }
 
         ++sM;
-- 
2.7.4

From cb85bde4c63d8ecae4f654fa20388e0d79245fbd Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Sun, 5 Nov 2017 23:44:04 +1100
Subject: [PATCH 6/9] STEP exporter

- Added model repeat functionality in step export
---
 utils/kicad2step/pcb/kicadmodel.cpp  | 34 +++++++++++++++++++++++++++++++++-
 utils/kicad2step/pcb/kicadmodel.h    |  3 +++
 utils/kicad2step/pcb/kicadmodule.cpp | 22 +++++++++++++++++++---
 3 files changed, 55 insertions(+), 4 deletions(-)

diff --git a/utils/kicad2step/pcb/kicadmodel.cpp b/utils/kicad2step/pcb/kicadmodel.cpp
index 6c4c033..36a14b7 100644
--- a/utils/kicad2step/pcb/kicadmodel.cpp
+++ b/utils/kicad2step/pcb/kicadmodel.cpp
@@ -28,7 +28,12 @@
 #include "kicadmodel.h"
 
 
-KICADMODEL::KICADMODEL() : m_scale( 1.0, 1.0, 1.0 )
+KICADMODEL::KICADMODEL() :
+    m_scale( 1.0, 1.0, 1.0 ),
+    m_offset( 0.0, 0.0, 0.0 ),
+    m_rotation( 0.0, 0.0, 0.0 ),
+    m_repeatStep( 0.0, 0.0, 0.0 ),
+    m_repeatCount( 1 )
 {
     return;
 }
@@ -78,11 +83,38 @@ bool KICADMODEL::Read( SEXPR::SEXPR* aEntry )
         bool ret = true;
 
         if( name == "at" )
+        {
             ret = Get3DCoordinate( child->GetChild( 1 ), m_offset );
+        }
         else if( name == "scale" )
+        {
             ret = Get3DCoordinate( child->GetChild( 1 ), m_scale );
+        }
         else if( name == "rotate" )
+        {
             ret = GetXYZRotation( child->GetChild( 1 ), m_rotation );
+        }
+        else if( name == "repeat" )
+        {
+            if( child->GetChild( 1 )->IsInteger() )
+            {
+                m_repeatCount = child->GetChild( 1 )->GetInteger();
+
+                if( m_repeatCount < 1 )
+                {
+                    m_repeatCount = 1;
+                }
+            }
+            else
+            {
+                std::ostringstream ostr;
+                ostr << "* corrupt module in PCB file; invalid repeat" << " (line " << child->GetLineNumber() << ")";
+                wxLogMessage( "%s\n", ostr.str().c_str() );
+                return false;
+            }
+
+            ret = Get3DCoordinate( child->GetChild( 2 ), m_repeatStep );
+        }
 
         if( !ret )
             return false;
diff --git a/utils/kicad2step/pcb/kicadmodel.h b/utils/kicad2step/pcb/kicadmodel.h
index f34fbac..14aebe0 100644
--- a/utils/kicad2step/pcb/kicadmodel.h
+++ b/utils/kicad2step/pcb/kicadmodel.h
@@ -43,6 +43,9 @@ public:
     TRIPLET     m_scale;
     TRIPLET     m_offset;
     TRIPLET     m_rotation;
+
+    unsigned int m_repeatCount = 1;
+    TRIPLET     m_repeatStep;
 };
 
 #endif  // KICADMODEL_H
diff --git a/utils/kicad2step/pcb/kicadmodule.cpp b/utils/kicad2step/pcb/kicadmodule.cpp
index 0b3807b..07a855f 100644
--- a/utils/kicad2step/pcb/kicadmodule.cpp
+++ b/utils/kicad2step/pcb/kicadmodule.cpp
@@ -366,9 +366,25 @@ bool KICADMODULE::ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
         std::string fname( resolver->ResolvePath(
             wxString::FromUTF8Unchecked( i->m_modelname.c_str() ) ).ToUTF8() );
 
-        if( aPCB->AddComponent( fname, m_refdes, LAYER_BOTTOM == m_side ? true : false,
-            newpos, m_rotation, i->m_offset, i->m_rotation ) )
-            hasdata = true;
+        if( i->m_repeatCount < 1 )
+            i->m_repeatCount = 1;
+
+        for( unsigned int ii = 0; ii < i->m_repeatCount; ii++ )
+        {
+            auto offset = i->m_offset;
+
+            offset.x += (double) ii * i->m_repeatStep.x / 25.4;
+            offset.y += (double) ii * i->m_repeatStep.y / 25.4;
+            offset.z += (double) ii * i->m_repeatStep.z / 25.4;
+
+            if( aPCB->AddComponent( fname, m_refdes,
+                                    LAYER_BOTTOM == m_side ? true : false,
+                                    newpos, m_rotation, offset, i->m_rotation ) )
+            {
+                hasdata = true;
+            }
+        }
+
 
     }
 
-- 
2.7.4

From f0839a49823a41d11682feb303393242578303d4 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Mon, 6 Nov 2017 00:18:10 +1100
Subject: [PATCH 7/9] Added code that was missing from commit history

Not sure how it was not added first...
---
 3d-viewer/3d_cache/3d_info.h | 11 ++++++++++-
 pcbnew/class_module.h        | 24 ++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/3d-viewer/3d_cache/3d_info.h b/3d-viewer/3d_cache/3d_info.h
index 365170a..214ace6 100644
--- a/3d-viewer/3d_cache/3d_info.h
+++ b/3d-viewer/3d_cache/3d_info.h
@@ -40,7 +40,16 @@ class MODULE_3D_SETTINGS;
 
 struct S3D_INFO
 {
-    S3D_INFO();
+    S3D_INFO() :
+        // Ensure sensible default values
+        m_Scale( 1.0, 1.0, 1.0 ),
+        m_Rotation( 0.0, 0.0, 0.0 ),
+        m_Offset( 0.0, 0.0, 0.0 ),
+        m_Repeat( 1 ),
+        m_Step( 0.0, 0.0, 0.0 )
+    {
+    }
+
     S3D_INFO( const MODULE_3D_SETTINGS& aModel );
 
     SGPOINT m_Scale;        ///< scaling factors for the 3D footprint shape
diff --git a/pcbnew/class_module.h b/pcbnew/class_module.h
index f8f74a6..e255e22 100644
--- a/pcbnew/class_module.h
+++ b/pcbnew/class_module.h
@@ -87,10 +87,34 @@ class MODULE_3D_SETTINGS
             double x, y, z;
         };
 
+        MODULE_3D_SETTINGS()
+        {
+            // Ensure default settings are sensible
+            m_Scale.x = 1;
+            m_Scale.y = 1;
+            m_Scale.z = 1;
+
+            m_Rotation.x = 0;
+            m_Rotation.y = 0;
+            m_Rotation.z = 0;
+
+            m_Offset.x = 0;
+            m_Offset.y = 0;
+            m_Offset.z = 0;
+
+            m_Repeat = 1;
+            m_Step.x = 0;
+            m_Step.y = 0;
+            m_Step.z = 0;
+        }
+
         VECTOR3D m_Scale;
         VECTOR3D m_Rotation;
         VECTOR3D m_Offset;
         wxString m_Filename;    ///< The 3D shape filename in 3D library
+
+        unsigned int m_Repeat;  ///< Number of times to repeat the model
+        VECTOR3D m_Step;        ///< Repeat step
 };
 
 class MODULE : public BOARD_ITEM_CONTAINER
-- 
2.7.4

From d1a75799492d50073af671fcfa12980d00832ab5 Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Mon, 6 Nov 2017 23:05:06 +1100
Subject: [PATCH 8/9] Implemented UI elements for model repeat

- Copied interface style for scale / rotation / offset
- Some other code cleanup
---
 3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp |   97 ++
 3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp | 1179 ++++++++++++++++++++-
 3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h   |   25 +-
 3d-viewer/3d_cache/dialogs/panel_prev_model.cpp   |  285 ++++-
 3d-viewer/3d_cache/dialogs/panel_prev_model.h     |   38 +-
 5 files changed, 1581 insertions(+), 43 deletions(-)

diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp
index 6aa8f87..77f9aef 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.cpp
@@ -173,6 +173,67 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	
 	bSizerLeft->Add( vbOffset, 0, wxEXPAND|wxRIGHT|wxTOP, 5 );
 	
+	m_enableRepeat = new wxCheckBox( this, wxID_ANY, _("Repeat model"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_enableRepeat->SetToolTip( _("Enable model repeat (array) ") );
+	
+	bSizerLeft->Add( m_enableRepeat, 0, wxALL|wxEXPAND, 5 );
+	
+	vbRepeat = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Repeat") ), wxVERTICAL );
+	
+	wxFlexGridSizer* fgSizerScale1;
+	fgSizerScale1 = new wxFlexGridSizer( 0, 3, 0, 0 );
+	fgSizerScale1->SetFlexibleDirection( wxBOTH );
+	fgSizerScale1->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
+	
+	m_staticText19 = new wxStaticText( vbRepeat->GetStaticBox(), wxID_ANY, _("N:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText19->Wrap( -1 );
+	fgSizerScale1->Add( m_staticText19, 0, wxALL, 5 );
+	
+	nrepeat = new wxTextCtrl( vbRepeat->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSizerScale1->Add( nrepeat, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	
+	m_spinNrepeat = new wxSpinButton( vbRepeat->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
+	fgSizerScale1->Add( m_spinNrepeat, 0, wxALIGN_CENTER_VERTICAL, 5 );
+	
+	m_staticText13 = new wxStaticText( vbRepeat->GetStaticBox(), wxID_ANY, _("X:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText13->Wrap( -1 );
+	fgSizerScale1->Add( m_staticText13, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	
+	xrepeat = new wxTextCtrl( vbRepeat->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSizerScale1->Add( xrepeat, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxTOP, 5 );
+	
+	m_spinXrepeat = new wxSpinButton( vbRepeat->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
+	fgSizerScale1->Add( m_spinXrepeat, 0, wxALIGN_CENTER_VERTICAL|wxTOP, 5 );
+	
+	m_staticText23 = new wxStaticText( vbRepeat->GetStaticBox(), wxID_ANY, _("Y:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText23->Wrap( -1 );
+	fgSizerScale1->Add( m_staticText23, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	
+	yrepeat = new wxTextCtrl( vbRepeat->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSizerScale1->Add( yrepeat, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP, 5 );
+	
+	m_spinYrepeat = new wxSpinButton( vbRepeat->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
+	fgSizerScale1->Add( m_spinYrepeat, 0, wxALIGN_CENTER_VERTICAL, 5 );
+	
+	m_staticText33 = new wxStaticText( vbRepeat->GetStaticBox(), wxID_ANY, _("Z:"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText33->Wrap( -1 );
+	fgSizerScale1->Add( m_staticText33, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+	
+	zrepeat = new wxTextCtrl( vbRepeat->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+	fgSizerScale1->Add( zrepeat, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT, 5 );
+	
+	m_spinZrepeat = new wxSpinButton( vbRepeat->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS|wxSP_VERTICAL );
+	fgSizerScale1->Add( m_spinZrepeat, 0, wxALIGN_CENTER_VERTICAL|wxBOTTOM, 5 );
+	
+	
+	vbRepeat->Add( fgSizerScale1, 1, wxEXPAND, 5 );
+	
+	
+	bSizerLeft->Add( vbRepeat, 1, wxEXPAND, 5 );
+	
+	
+	bSizerLeft->Add( 0, 0, 1, wxEXPAND, 5 );
+	
 	
 	bSizermain->Add( bSizerLeft, 0, wxEXPAND, 5 );
 	
@@ -246,6 +307,7 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	this->Layout();
 	
 	// Connect Events
+	this->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PANEL_PREV_3D_BASE::onUpdateUi ) );
 	xscale->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelScale ), NULL, this );
 	xscale->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
 	m_spinXscale->Connect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementScale ), NULL, this );
@@ -282,6 +344,23 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 	zoff->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
 	m_spinZoffset->Connect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementOffset ), NULL, this );
 	m_spinZoffset->Connect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementOffset ), NULL, this );
+	m_enableRepeat->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::onToggleEnableRepeat ), NULL, this );
+	nrepeat->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeatCount ), NULL, this );
+	nrepeat->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinNrepeat->Connect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeatCount ), NULL, this );
+	m_spinNrepeat->Connect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeatCount ), NULL, this );
+	xrepeat->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeat ), NULL, this );
+	xrepeat->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinXrepeat->Connect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeat ), NULL, this );
+	m_spinXrepeat->Connect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeat ), NULL, this );
+	yrepeat->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeat ), NULL, this );
+	yrepeat->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinYrepeat->Connect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeat ), NULL, this );
+	m_spinYrepeat->Connect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeat ), NULL, this );
+	zrepeat->Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeat ), NULL, this );
+	zrepeat->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinZrepeat->Connect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeat ), NULL, this );
+	m_spinZrepeat->Connect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeat ), NULL, this );
 	m_bpvISO->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::View3DISO ), NULL, this );
 	m_bpvLeft->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::View3DLeft ), NULL, this );
 	m_bpvFront->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::View3DFront ), NULL, this );
@@ -295,6 +374,7 @@ PANEL_PREV_3D_BASE::PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id, const w
 PANEL_PREV_3D_BASE::~PANEL_PREV_3D_BASE()
 {
 	// Disconnect Events
+	this->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PANEL_PREV_3D_BASE::onUpdateUi ) );
 	xscale->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelScale ), NULL, this );
 	xscale->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
 	m_spinXscale->Disconnect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementScale ), NULL, this );
@@ -331,6 +411,23 @@ PANEL_PREV_3D_BASE::~PANEL_PREV_3D_BASE()
 	zoff->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
 	m_spinZoffset->Disconnect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementOffset ), NULL, this );
 	m_spinZoffset->Disconnect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementOffset ), NULL, this );
+	m_enableRepeat->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::onToggleEnableRepeat ), NULL, this );
+	nrepeat->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeatCount ), NULL, this );
+	nrepeat->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinNrepeat->Disconnect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeatCount ), NULL, this );
+	m_spinNrepeat->Disconnect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeatCount ), NULL, this );
+	xrepeat->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeat ), NULL, this );
+	xrepeat->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinXrepeat->Disconnect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeat ), NULL, this );
+	m_spinXrepeat->Disconnect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeat ), NULL, this );
+	yrepeat->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeat ), NULL, this );
+	yrepeat->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinYrepeat->Disconnect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeat ), NULL, this );
+	m_spinYrepeat->Disconnect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeat ), NULL, this );
+	zrepeat->Disconnect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( PANEL_PREV_3D_BASE::onMouseWheelRepeat ), NULL, this );
+	zrepeat->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( PANEL_PREV_3D_BASE::updateOrientation ), NULL, this );
+	m_spinZrepeat->Disconnect( wxEVT_SCROLL_LINEDOWN, wxSpinEventHandler( PANEL_PREV_3D_BASE::onDecrementRepeat ), NULL, this );
+	m_spinZrepeat->Disconnect( wxEVT_SCROLL_LINEUP, wxSpinEventHandler( PANEL_PREV_3D_BASE::onIncrementRepeat ), NULL, this );
 	m_bpvISO->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::View3DISO ), NULL, this );
 	m_bpvLeft->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::View3DLeft ), NULL, this );
 	m_bpvFront->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PREV_3D_BASE::View3DFront ), NULL, this );
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp
index 9c61058..965bd4c 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.fbp
@@ -42,7 +42,7 @@
             <property name="minimum_size"></property>
             <property name="name">PANEL_PREV_3D_BASE</property>
             <property name="pos"></property>
-            <property name="size">503,371</property>
+            <property name="size">503,560</property>
             <property name="subclass"></property>
             <property name="tooltip"></property>
             <property name="window_extra_style"></property>
@@ -77,7 +77,7 @@
             <event name="OnRightUp"></event>
             <event name="OnSetFocus"></event>
             <event name="OnSize"></event>
-            <event name="OnUpdateUI"></event>
+            <event name="OnUpdateUI">onUpdateUi</event>
             <object class="wxBoxSizer" expanded="1">
                 <property name="minimum_size"></property>
                 <property name="name">bSizermain</property>
@@ -105,11 +105,11 @@
                                 <property name="parent">1</property>
                                 <property name="permission">none</property>
                                 <event name="OnUpdateUI"></event>
-                                <object class="sizeritem" expanded="1">
+                                <object class="sizeritem" expanded="0">
                                     <property name="border">5</property>
                                     <property name="flag">wxEXPAND</property>
                                     <property name="proportion">1</property>
-                                    <object class="wxFlexGridSizer" expanded="1">
+                                    <object class="wxFlexGridSizer" expanded="0">
                                         <property name="cols">3</property>
                                         <property name="flexible_direction">wxBOTH</property>
                                         <property name="growablecols"></property>
@@ -899,11 +899,11 @@
                                 </object>
                             </object>
                         </object>
-                        <object class="sizeritem" expanded="1">
+                        <object class="sizeritem" expanded="0">
                             <property name="border">5</property>
                             <property name="flag">wxEXPAND|wxRIGHT|wxTOP</property>
                             <property name="proportion">0</property>
-                            <object class="wxStaticBoxSizer" expanded="1">
+                            <object class="wxStaticBoxSizer" expanded="0">
                                 <property name="id">wxID_ANY</property>
                                 <property name="label">Rotation (degrees)</property>
                                 <property name="minimum_size"></property>
@@ -912,11 +912,11 @@
                                 <property name="parent">1</property>
                                 <property name="permission">none</property>
                                 <event name="OnUpdateUI"></event>
-                                <object class="sizeritem" expanded="1">
+                                <object class="sizeritem" expanded="0">
                                     <property name="border">5</property>
                                     <property name="flag">wxEXPAND</property>
                                     <property name="proportion">1</property>
-                                    <object class="wxFlexGridSizer" expanded="1">
+                                    <object class="wxFlexGridSizer" expanded="0">
                                         <property name="cols">3</property>
                                         <property name="flexible_direction">wxBOTH</property>
                                         <property name="growablecols"></property>
@@ -2513,6 +2513,1169 @@
                                 </object>
                             </object>
                         </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxALL|wxEXPAND</property>
+                            <property name="proportion">0</property>
+                            <object class="wxCheckBox" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="checked">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Repeat model</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_enableRepeat</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip">Enable model repeat (array) </property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnChar"></event>
+                                <event name="OnCheckBox">onToggleEnableRepeat</event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxStaticBoxSizer" expanded="1">
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Repeat</property>
+                                <property name="minimum_size"></property>
+                                <property name="name">vbRepeat</property>
+                                <property name="orient">wxVERTICAL</property>
+                                <property name="parent">1</property>
+                                <property name="permission">protected</property>
+                                <event name="OnUpdateUI"></event>
+                                <object class="sizeritem" expanded="1">
+                                    <property name="border">5</property>
+                                    <property name="flag">wxEXPAND</property>
+                                    <property name="proportion">1</property>
+                                    <object class="wxFlexGridSizer" expanded="1">
+                                        <property name="cols">3</property>
+                                        <property name="flexible_direction">wxBOTH</property>
+                                        <property name="growablecols"></property>
+                                        <property name="growablerows"></property>
+                                        <property name="hgap">0</property>
+                                        <property name="minimum_size"></property>
+                                        <property name="name">fgSizerScale1</property>
+                                        <property name="non_flexible_grow_mode">wxFLEX_GROWMODE_SPECIFIED</property>
+                                        <property name="permission">none</property>
+                                        <property name="rows">0</property>
+                                        <property name="vgap">0</property>
+                                        <object class="sizeritem" expanded="1">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALL</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxStaticText" expanded="1">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="label">N:</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_staticText19</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <property name="wrap">-1</property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="1">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxTextCtrl" expanded="1">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="maxlength"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">nrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="validator_data_type"></property>
+                                                <property name="validator_style">wxFILTER_NONE</property>
+                                                <property name="validator_type">wxDefaultValidator</property>
+                                                <property name="validator_variable"></property>
+                                                <property name="value"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel">onMouseWheelRepeatCount</event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnText">updateOrientation</event>
+                                                <event name="OnTextEnter"></event>
+                                                <event name="OnTextMaxLen"></event>
+                                                <event name="OnTextURL"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="1">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxSpinButton" expanded="1">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_spinNrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style">wxSP_ARROW_KEYS|wxSP_VERTICAL</property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnSpin"></event>
+                                                <event name="OnSpinDown">onDecrementRepeatCount</event>
+                                                <event name="OnSpinUp">onIncrementRepeatCount</event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxStaticText" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="label">X:</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_staticText13</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <property name="wrap">-1</property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT|wxTOP</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxTextCtrl" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="maxlength">0</property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">xrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="validator_data_type"></property>
+                                                <property name="validator_style">wxFILTER_NONE</property>
+                                                <property name="validator_type">wxDefaultValidator</property>
+                                                <property name="validator_variable"></property>
+                                                <property name="value"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel">onMouseWheelRepeat</event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnText">updateOrientation</event>
+                                                <event name="OnTextEnter"></event>
+                                                <event name="OnTextMaxLen"></event>
+                                                <event name="OnTextURL"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxTOP</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxSpinButton" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_spinXrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style">wxSP_ARROW_KEYS|wxSP_VERTICAL</property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnSpin"></event>
+                                                <event name="OnSpinDown">onDecrementRepeat</event>
+                                                <event name="OnSpinUp">onIncrementRepeat</event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxStaticText" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="label">Y:</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_staticText23</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <property name="wrap">-1</property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT|wxTOP</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxTextCtrl" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="maxlength">0</property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">yrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="validator_data_type"></property>
+                                                <property name="validator_style">wxFILTER_NONE</property>
+                                                <property name="validator_type">wxDefaultValidator</property>
+                                                <property name="validator_variable"></property>
+                                                <property name="value"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel">onMouseWheelRepeat</event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnText">updateOrientation</event>
+                                                <event name="OnTextEnter"></event>
+                                                <event name="OnTextMaxLen"></event>
+                                                <event name="OnTextURL"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxSpinButton" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_spinYrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style">wxSP_ARROW_KEYS|wxSP_VERTICAL</property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnSpin"></event>
+                                                <event name="OnSpinDown">onDecrementRepeat</event>
+                                                <event name="OnSpinUp">onIncrementRepeat</event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxStaticText" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="label">Z:</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_staticText33</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <property name="wrap">-1</property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM|wxLEFT</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxTextCtrl" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="maxlength">0</property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">zrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="validator_data_type"></property>
+                                                <property name="validator_style">wxFILTER_NONE</property>
+                                                <property name="validator_type">wxDefaultValidator</property>
+                                                <property name="validator_variable"></property>
+                                                <property name="value"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel">onMouseWheelRepeat</event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnText">updateOrientation</event>
+                                                <event name="OnTextEnter"></event>
+                                                <event name="OnTextMaxLen"></event>
+                                                <event name="OnTextURL"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxBOTTOM</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxSpinButton" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_spinZrepeat</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style">wxSP_ARROW_KEYS|wxSP_VERTICAL</property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnSpin"></event>
+                                                <event name="OnSpinDown">onDecrementRepeat</event>
+                                                <event name="OnSpinUp">onIncrementRepeat</event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="spacer" expanded="1">
+                                <property name="height">0</property>
+                                <property name="permission">protected</property>
+                                <property name="width">0</property>
+                            </object>
+                        </object>
                     </object>
                 </object>
                 <object class="sizeritem" expanded="1">
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h
index 4130c24..55fd550 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_3d_base.h
@@ -21,6 +21,7 @@
 #include <wx/spinbutt.h>
 #include <wx/sizer.h>
 #include <wx/statbox.h>
+#include <wx/checkbox.h>
 #include <wx/bitmap.h>
 #include <wx/image.h>
 #include <wx/icon.h>
@@ -66,6 +67,20 @@ class PANEL_PREV_3D_BASE : public wxPanel
 		wxStaticText* m_staticText32;
 		wxTextCtrl* zoff;
 		wxSpinButton* m_spinZoffset;
+		wxCheckBox* m_enableRepeat;
+		wxStaticBoxSizer* vbRepeat;
+		wxStaticText* m_staticText19;
+		wxTextCtrl* nrepeat;
+		wxSpinButton* m_spinNrepeat;
+		wxStaticText* m_staticText13;
+		wxTextCtrl* xrepeat;
+		wxSpinButton* m_spinXrepeat;
+		wxStaticText* m_staticText23;
+		wxTextCtrl* yrepeat;
+		wxSpinButton* m_spinYrepeat;
+		wxStaticText* m_staticText33;
+		wxTextCtrl* zrepeat;
+		wxSpinButton* m_spinZrepeat;
 		wxBoxSizer* m_SizerPanelView;
 		wxFlexGridSizer* m_fgSizerButtons;
 		wxBitmapButton* m_bpvISO;
@@ -78,6 +93,7 @@ class PANEL_PREV_3D_BASE : public wxPanel
 		wxBitmapButton* m_bpvBottom;
 		
 		// Virtual event handlers, overide them in your derived class
+		virtual void onUpdateUi( wxUpdateUIEvent& event ) { event.Skip(); }
 		virtual void onMouseWheelScale( wxMouseEvent& event ) { event.Skip(); }
 		virtual void updateOrientation( wxCommandEvent& event ) { event.Skip(); }
 		virtual void onDecrementScale( wxSpinEvent& event ) { event.Skip(); }
@@ -88,6 +104,13 @@ class PANEL_PREV_3D_BASE : public wxPanel
 		virtual void onMouseWheelOffset( wxMouseEvent& event ) { event.Skip(); }
 		virtual void onDecrementOffset( wxSpinEvent& event ) { event.Skip(); }
 		virtual void onIncrementOffset( wxSpinEvent& event ) { event.Skip(); }
+		virtual void onToggleEnableRepeat( wxCommandEvent& event ) { event.Skip(); }
+		virtual void onMouseWheelRepeatCount( wxMouseEvent& event ) { event.Skip(); }
+		virtual void onDecrementRepeatCount( wxSpinEvent& event ) { event.Skip(); }
+		virtual void onIncrementRepeatCount( wxSpinEvent& event ) { event.Skip(); }
+		virtual void onMouseWheelRepeat( wxMouseEvent& event ) { event.Skip(); }
+		virtual void onDecrementRepeat( wxSpinEvent& event ) { event.Skip(); }
+		virtual void onIncrementRepeat( wxSpinEvent& event ) { event.Skip(); }
 		virtual void View3DISO( wxCommandEvent& event ) { event.Skip(); }
 		virtual void View3DLeft( wxCommandEvent& event ) { event.Skip(); }
 		virtual void View3DFront( wxCommandEvent& event ) { event.Skip(); }
@@ -100,7 +123,7 @@ class PANEL_PREV_3D_BASE : public wxPanel
 	
 	public:
 		
-		PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 503,371 ), long style = wxTAB_TRAVERSAL ); 
+		PANEL_PREV_3D_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 503,560 ), long style = wxTAB_TRAVERSAL ); 
 		~PANEL_PREV_3D_BASE();
 	
 };
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp b/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp
index 3cc07b5..bbf0448 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp
@@ -119,18 +119,20 @@ void PANEL_PREV_3D::initPanel()
     // a few clicks.
     wxSpinButton* spinButtonList[] =
     {
-        m_spinXscale, m_spinYscale, m_spinZscale,
-        m_spinXrot, m_spinYrot, m_spinZrot,
-        m_spinXoffset,m_spinYoffset, m_spinZoffset
+        m_spinXscale,  m_spinYscale,  m_spinZscale,
+        m_spinXrot,    m_spinYrot,    m_spinZrot,
+        m_spinXoffset, m_spinYoffset, m_spinZoffset,
+        m_spinXrepeat, m_spinYrepeat, m_spinZrepeat,
+        m_spinNrepeat
     };
 
-    for( int ii = 0; ii < 9; ii++ )
+    for( int ii = 0; ii < 13; ii++ )
+    {
         spinButtonList[ii]->SetRange( INT_MIN, INT_MAX );
+    }
 }
 
 
-
-
 /**
  * @brief checkRotation
  * Ensure -MAX_ROTATION <= rotation <= MAX_ROTATION
@@ -214,26 +216,34 @@ void PANEL_PREV_3D::SetModelDataIdx( int idx, bool aReloadPreviewModule )
             yrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.y ) );
             zrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.z ) );
 
+            double scaler = 1.0f;
+
             switch( g_UserUnit )
             {
             case MILLIMETRES:
-                xoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.x * 25.4 ) );
-                yoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.y * 25.4 ) );
-                zoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.z * 25.4 ) );
+                scaler = 25.4f;
                 break;
-
             case INCHES:
-                xoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.x ) );
-                yoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.y ) );
-                zoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.z ) );
+                scaler = 1.0f;
                 break;
-
             case DEGREES:
-            case UNSCALED_UNITS:
-            default:
-                wxASSERT(0);
+           case UNSCALED_UNITS:
+           default:
+               wxASSERT(0);
             }
 
+            xoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.x * scaler ) );
+            yoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.y * scaler ) );
+            zoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.z * scaler ) );
+
+            xrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.x * scaler ) );
+            yrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.y * scaler ) );
+            zrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.z * scaler ) );
+
+            nrepeat->SetValue( wxString::Format( "%u", aModel->m_Repeat ) );
+
+            m_enableRepeat->SetValue( aModel->m_Repeat > 1 );
+
             UpdateModelName( aModel->m_Filename );
 
             if( aReloadPreviewModule && m_previewPane )
@@ -259,17 +269,26 @@ void PANEL_PREV_3D::ResetModelData( bool aReloadPreviewModule )
 {
     m_currentSelectedIdx = -1;
 
-    xscale->SetValue( wxString::FromDouble( 1.0 ) );
-    yscale->SetValue( wxString::FromDouble( 1.0 ) );
-    zscale->SetValue( wxString::FromDouble( 1.0 ) );
+    const auto ONE = wxString::FromDouble( 1.0f );
+    const auto ZERO = wxString::FromDouble( 0.0f );
+
+    xscale->SetValue( ONE );
+    yscale->SetValue( ONE );
+    zscale->SetValue( ONE );
+
+    xrot->SetValue( ZERO );
+    yrot->SetValue( ZERO );
+    zrot->SetValue( ZERO );
 
-    xrot->SetValue( wxString::FromDouble( 0.0 ) );
-    yrot->SetValue( wxString::FromDouble( 0.0 ) );
-    zrot->SetValue( wxString::FromDouble( 0.0 ) );
+    xoff->SetValue( ZERO );
+    yoff->SetValue( ZERO );
+    zoff->SetValue( ZERO );
 
-    xoff->SetValue( wxString::FromDouble( 0.0 ) );
-    yoff->SetValue( wxString::FromDouble( 0.0 ) );
-    zoff->SetValue( wxString::FromDouble( 0.0 ) );
+    xrepeat->SetValue( ZERO );
+    yrepeat->SetValue( ZERO );
+    zrepeat->SetValue( ZERO );
+
+    nrepeat->SetValue( wxString::Format( "%u", 1 ) );
 
     // This will update the model on the preview board with the current list of 3d shapes
     if( aReloadPreviewModule )
@@ -358,18 +377,40 @@ void PANEL_PREV_3D::updateOrientation( wxCommandEvent &event )
     SGPOINT rotation;
     SGPOINT offset;
 
+    SGPOINT stepSize;
+    unsigned int stepCount;
+
     getOrientationVars( scale, rotation, offset );
+    getRepeatVars( stepCount, stepSize );
 
     m_modelInfo.m_Scale.x = scale.x;
     m_modelInfo.m_Scale.y = scale.y;
     m_modelInfo.m_Scale.z = scale.z;
+
     m_modelInfo.m_Offset.x = offset.x;
     m_modelInfo.m_Offset.y = offset.y;
     m_modelInfo.m_Offset.z = offset.z;
+
     m_modelInfo.m_Rotation.x = rotation.x;
     m_modelInfo.m_Rotation.y = rotation.y;
     m_modelInfo.m_Rotation.z = rotation.z;
 
+    if( m_enableRepeat->GetValue() )
+    {
+        m_modelInfo.m_Repeat = stepCount;
+
+        m_modelInfo.m_Step.x = stepSize.x;
+        m_modelInfo.m_Step.y = stepSize.y;
+        m_modelInfo.m_Step.z = stepSize.z;
+    }
+    else
+    {
+        m_modelInfo.m_Repeat = 1;
+        m_modelInfo.m_Step.x = 0;
+        m_modelInfo.m_Step.y = 0;
+        m_modelInfo.m_Step.z = 0;
+    }
+
     if( m_currentSelectedIdx >= 0 )
     {
         // This will update the parent list with the new data
@@ -486,6 +527,129 @@ void PANEL_PREV_3D::onDecrementOffset( wxSpinEvent& event )
 }
 
 
+void PANEL_PREV_3D::onIncrementRepeat( wxSpinEvent& event )
+{
+    wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject();
+
+    wxTextCtrl* textCtrl = xrepeat;
+
+    if( spinCtrl == m_spinYrepeat )
+        textCtrl = yrepeat;
+    else if( spinCtrl == m_spinZrepeat )
+        textCtrl = zrepeat;
+
+    double step = REPEAT_INCREMENT_MM;
+
+    if( g_UserUnit == INCHES )
+        step = REPEAT_INCREMENT_MIL / 1000.0;
+
+    incrementTextCtrl( textCtrl, -step, -MAX_REPEAT_STEP, MAX_REPEAT_STEP );
+}
+
+
+void PANEL_PREV_3D::onDecrementRepeat( wxSpinEvent& event )
+{
+    wxSpinButton* spinCtrl = (wxSpinButton*) event.GetEventObject();
+
+    wxTextCtrl* textCtrl = xrepeat;
+
+    if( spinCtrl == m_spinYrepeat )
+        textCtrl = yrepeat;
+    else if( spinCtrl == m_spinZrepeat )
+        textCtrl = zrepeat;
+
+    double step = REPEAT_INCREMENT_MM;
+
+    if( g_UserUnit == INCHES )
+        step = REPEAT_INCREMENT_MIL / 1000.0;
+
+    incrementTextCtrl( textCtrl, step, -MAX_REPEAT_STEP, MAX_REPEAT_STEP );
+}
+
+
+void PANEL_PREV_3D::onIncrementRepeatCount( wxSpinEvent& event )
+{
+    unsigned int repeatCount = m_modelInfo.m_Repeat + 1;
+
+    if( repeatCount > MAX_REPEAT_COUNT )
+        repeatCount = MAX_REPEAT_COUNT;
+
+    nrepeat->SetValue( wxString::Format( "%u", repeatCount ) );
+}
+
+
+void PANEL_PREV_3D::onDecrementRepeatCount( wxSpinEvent& event )
+{
+    unsigned int repeatCount = m_modelInfo.m_Repeat;
+
+    if( repeatCount > 1 )
+        repeatCount -= 1;
+
+    nrepeat->SetValue( wxString::Format( "%u", repeatCount ) );
+}
+
+
+void PANEL_PREV_3D::onToggleEnableRepeat( wxCommandEvent& event )
+{
+    SGPOINT stepSize;
+    unsigned int stepCount;
+    getRepeatVars( stepCount, stepSize );
+
+    if( m_enableRepeat->GetValue() )
+    {
+        m_modelInfo.m_Repeat = stepCount;
+
+        m_modelInfo.m_Step.x = stepSize.x;
+        m_modelInfo.m_Step.y = stepSize.y;
+        m_modelInfo.m_Step.z = stepSize.z;
+    }
+    else
+    {
+        m_modelInfo.m_Repeat = 1;
+        m_modelInfo.m_Step.x = 0;
+        m_modelInfo.m_Step.y = 0;
+        m_modelInfo.m_Step.z = 0;
+    }
+
+    if( m_currentSelectedIdx >= 0 )
+    {
+       // This will update the parent list with the new data
+       (*m_parentInfoList)[m_currentSelectedIdx] = m_modelInfo;
+
+       // It will update the copy model in the preview board
+       updateListOnModelCopy();
+
+       // Since the OpenGL render does not need to be reloaded to update the
+       // shapes position, we just request to redraw again the canvas
+       if( m_previewPane )
+           m_previewPane->Refresh();
+    }
+
+    vbRepeat->Show( m_enableRepeat->GetValue() );
+    Update();
+}
+
+
+void PANEL_PREV_3D::onUpdateUi( wxUpdateUIEvent& event )
+{
+    bool en = m_enableRepeat->GetValue();
+
+    m_spinNrepeat->Enable( en );
+    m_spinXrepeat->Enable( en );
+    m_spinYrepeat->Enable( en );
+    m_spinZrepeat->Enable( en );
+
+    nrepeat->Enable( en );
+    xrepeat->Enable( en );
+    yrepeat->Enable( en );
+    zrepeat->Enable( en );
+
+    vbRepeat->Show( en );
+
+    Update();
+}
+
+
 void PANEL_PREV_3D::onMouseWheelScale( wxMouseEvent& event )
 {
     wxTextCtrl* textCtrl = (wxTextCtrl*) event.GetEventObject();
@@ -540,6 +704,75 @@ void PANEL_PREV_3D::onMouseWheelOffset( wxMouseEvent& event )
     incrementTextCtrl( textCtrl, step, -MAX_OFFSET, MAX_OFFSET );
 }
 
+
+void PANEL_PREV_3D::onMouseWheelRepeat( wxMouseEvent& event )
+{
+    wxTextCtrl* textCtrl = (wxTextCtrl*) event.GetEventObject();
+
+    double step;
+
+    if( g_UserUnit == INCHES )
+    {
+        step = event.ShiftDown() ? REPEAT_INCREMENT_MIL_FINE : REPEAT_INCREMENT_MIL;
+        step /= 1000.0;
+    }
+    else
+    {
+        step = event.ShiftDown() ? REPEAT_INCREMENT_MM_FINE : REPEAT_INCREMENT_MM;
+    }
+
+    if( event.GetWheelRotation() >= 0 )
+    {
+        step = -step;
+    }
+
+    incrementTextCtrl( textCtrl, step, -MAX_OFFSET, MAX_OFFSET );
+}
+
+
+void PANEL_PREV_3D::onMouseWheelRepeatCount( wxMouseEvent& event )
+{
+    int delta = 1;
+
+    if( event.GetWheelRotation() >= 0 )
+    {
+        delta = -1;
+    }
+
+    int repeatCount = m_modelInfo.m_Repeat + delta;
+
+    if( repeatCount < 1 )
+        repeatCount = 1;
+
+    if( repeatCount > MAX_REPEAT_COUNT )
+        repeatCount = MAX_REPEAT_COUNT;
+
+    nrepeat->SetValue( wxString::Format( "%u", repeatCount ) );
+}
+
+
+void PANEL_PREV_3D::getRepeatVars( unsigned int& aStepCount, SGPOINT& aStepSize )
+{
+    if( NULL == nrepeat || NULL == xrepeat || NULL == yrepeat || NULL == zrepeat )
+    {
+        return;
+    }
+
+    xrepeat->GetValue().ToDouble( &aStepSize.x );
+    yrepeat->GetValue().ToDouble( &aStepSize.y );
+    zrepeat->GetValue().ToDouble( &aStepSize.z );
+
+    long steps = 1;
+
+    nrepeat->GetValue().ToCLong( &steps );
+
+    if( steps < 1 )
+        steps = 1;
+
+    aStepCount = (unsigned int) steps;
+}
+
+
 void PANEL_PREV_3D::getOrientationVars( SGPOINT& aScale, SGPOINT& aRotation, SGPOINT& aOffset )
 {
     if( NULL == xscale || NULL == yscale || NULL == zscale
diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_model.h b/3d-viewer/3d_cache/dialogs/panel_prev_model.h
index 85cbb8e..253d612 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_model.h
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_model.h
@@ -46,20 +46,26 @@
 #define MAX_SCALE          10000.0
 #define MAX_ROTATION       180.0
 #define MAX_OFFSET         1000.0
+#define MAX_REPEAT_STEP    1000.0
+#define MAX_REPEAT_COUNT   512
 
 #define SCALE_INCREMENT_FINE    0.02
-#define SCALE_INCREMENT     0.1
+#define SCALE_INCREMENT         0.1
 
-#define ROTATION_INCREMENT 5             // in degrees, for spin button command
-#define ROTATION_INCREMENT_WHEEL 15      // in degrees, for mouse wheel command
-#define ROTATION_INCREMENT_WHEEL_FINE 1  // in degrees, for mouse wheel command
+#define ROTATION_INCREMENT              5   // in degrees, for spin button command
+#define ROTATION_INCREMENT_WHEEL        15  // in degrees, for mouse wheel command
+#define ROTATION_INCREMENT_WHEEL_FINE   1   // in degrees, for mouse wheel command
 
-#define OFFSET_INCREMENT_MM   0.5
-#define OFFSET_INCREMENT_MM_FINE 0.1
+#define OFFSET_INCREMENT_MM       0.5
+#define OFFSET_INCREMENT_MM_FINE  0.1
 
-#define OFFSET_INCREMENT_MIL   25.0
-#define OFFSET_INCREMENT_MIL_FINE   5.0
+#define OFFSET_INCREMENT_MIL      25.0
+#define OFFSET_INCREMENT_MIL_FINE 5.0
 
+#define REPEAT_INCREMENT_MM       0.5
+#define REPEAT_INCREMENT_MM_FINE  0.1
+#define REPEAT_INCREMENT_MIL      25.0
+#define REPEAT_INCREMENT_MIL_FINE 5.0
 
 // Declared classes to create pointers
 class S3D_CACHE;
@@ -114,9 +120,13 @@ private:
      */
     void updateOrientation( wxCommandEvent &event ) override;
 
+    void onToggleEnableRepeat( wxCommandEvent& event ) override;
+
 	void onMouseWheelScale( wxMouseEvent& event ) override;
 	void onMouseWheelRot( wxMouseEvent& event ) override;
 	void onMouseWheelOffset( wxMouseEvent& event ) override;
+	void onMouseWheelRepeat( wxMouseEvent& event ) override;
+	void onMouseWheelRepeatCount( wxMouseEvent& event ) override;
 
 	void onIncrementRot( wxSpinEvent& event ) override;
 	void onDecrementRot( wxSpinEvent& event ) override;
@@ -124,6 +134,11 @@ private:
 	void onDecrementScale( wxSpinEvent& event ) override;
 	void onIncrementOffset( wxSpinEvent& event ) override;
 	void onDecrementOffset( wxSpinEvent& event ) override;
+	void onIncrementRepeat( wxSpinEvent& event ) override;
+	void onDecrementRepeat( wxSpinEvent& event ) override;
+	void onIncrementRepeatCount( wxSpinEvent& event ) override;
+	void onDecrementRepeatCount( wxSpinEvent& event ) override;
+	virtual void onUpdateUi( wxUpdateUIEvent& event ) override;
 
     /**
      * @brief getOrientationVars - gets the transformation from entries and validate it
@@ -134,6 +149,13 @@ private:
     void getOrientationVars( SGPOINT& aScale, SGPOINT& aRotation, SGPOINT& aOffset );
 
     /**
+     * @brief getRepeatVars - gets the array repeat information and validate it
+     * @param aStepCount: output array repeat count var
+     * @param aStepSize: output array step size var
+     */
+    void getRepeatVars( unsigned int& aStepCount, SGPOINT& aStepSize );
+
+    /**
      * @brief updateListOnModelCopy - copy the current shape list to the copy of module that is on
      * the preview dummy board
      */
-- 
2.7.4

From 6a425549be6b316b86b3794bd74e733ba20673eb Mon Sep 17 00:00:00 2001
From: Oliver <oliver.henry.walters@xxxxxxxxx>
Date: Tue, 7 Nov 2017 00:44:50 +1100
Subject: [PATCH 9/9] Bug fix for 3D model preview widget

- Confusion between internal units led to incorrect dimension conversion
- Code cleanup
- Improved documentation in class_module.h
Bug fix for 3D model preview widget

- Confusion between internal units led to incorrect dimension conversion
- Code cleanup
- Improved documentation in class_module.h
- Fixed offset in STEP exporter
---
 3d-viewer/3d_cache/dialogs/panel_prev_model.cpp    | 94 +++++++++++-----------
 .../c3d_render_createscene.cpp                     |  5 +-
 pcbnew/class_module.h                              | 16 +++-
 pcbnew/exporters/export_vrml.cpp                   |  3 +
 utils/kicad2step/pcb/kicadmodule.cpp               |  4 +
 5 files changed, 67 insertions(+), 55 deletions(-)

diff --git a/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp b/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp
index bbf0448..52c22d3 100644
--- a/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp
+++ b/3d-viewer/3d_cache/dialogs/panel_prev_model.cpp
@@ -216,29 +216,25 @@ void PANEL_PREV_3D::SetModelDataIdx( int idx, bool aReloadPreviewModule )
             yrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.y ) );
             zrot->SetValue( wxString::Format( "%.2f", aModel->m_Rotation.z ) );
 
+            // Convert from internal units (mm) to display units
             double scaler = 1.0f;
 
             switch( g_UserUnit )
             {
-            case MILLIMETRES:
+            case INCHES:
                 scaler = 25.4f;
                 break;
-            case INCHES:
-                scaler = 1.0f;
+            default:
                 break;
-            case DEGREES:
-           case UNSCALED_UNITS:
-           default:
-               wxASSERT(0);
             }
 
-            xoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.x * scaler ) );
-            yoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.y * scaler ) );
-            zoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.z * scaler ) );
+            xoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.x / scaler ) );
+            yoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.y / scaler ) );
+            zoff->SetValue( wxString::Format( "%.4f", aModel->m_Offset.z / scaler ) );
 
-            xrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.x * scaler ) );
-            yrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.y * scaler ) );
-            zrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.z * scaler ) );
+            xrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.x / scaler ) );
+            yrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.y / scaler ) );
+            zrepeat->SetValue( wxString::Format( "%.4f", aModel->m_Step.z / scaler ) );
 
             nrepeat->SetValue( wxString::Format( "%u", aModel->m_Repeat ) );
 
@@ -387,21 +383,33 @@ void PANEL_PREV_3D::updateOrientation( wxCommandEvent &event )
     m_modelInfo.m_Scale.y = scale.y;
     m_modelInfo.m_Scale.z = scale.z;
 
-    m_modelInfo.m_Offset.x = offset.x;
-    m_modelInfo.m_Offset.y = offset.y;
-    m_modelInfo.m_Offset.z = offset.z;
-
     m_modelInfo.m_Rotation.x = rotation.x;
     m_modelInfo.m_Rotation.y = rotation.y;
     m_modelInfo.m_Rotation.z = rotation.z;
 
-    if( m_enableRepeat->GetValue() )
+    // Convert from user units back to internal units (mm)
+    double scaler = 1.0f;
+
+    switch( g_UserUnit )
+    {
+    case INCHES:
+        scaler = 25.4;
+        break;
+    default:
+        break;
+    }
+
+    m_modelInfo.m_Offset.x = offset.x * scaler;
+    m_modelInfo.m_Offset.y = offset.y * scaler;
+    m_modelInfo.m_Offset.z = offset.z * scaler;
+
+    if( m_enableRepeat->GetValue() && stepCount > 1 )
     {
         m_modelInfo.m_Repeat = stepCount;
 
-        m_modelInfo.m_Step.x = stepSize.x;
-        m_modelInfo.m_Step.y = stepSize.y;
-        m_modelInfo.m_Step.z = stepSize.z;
+        m_modelInfo.m_Step.x = stepSize.x * scaler;
+        m_modelInfo.m_Step.y = stepSize.y * scaler;
+        m_modelInfo.m_Step.z = stepSize.z * scaler;
     }
     else
     {
@@ -501,7 +509,7 @@ void PANEL_PREV_3D::onIncrementOffset( wxSpinEvent& event )
     double step = OFFSET_INCREMENT_MM;
 
     if( g_UserUnit == INCHES )
-        step = OFFSET_INCREMENT_MIL/1000.0;
+        step = OFFSET_INCREMENT_MIL / 1000.0;
 
     incrementTextCtrl( textCtrl, step, -MAX_OFFSET, MAX_OFFSET );
 }
@@ -521,7 +529,7 @@ void PANEL_PREV_3D::onDecrementOffset( wxSpinEvent& event )
     double step = OFFSET_INCREMENT_MM;
 
     if( g_UserUnit == INCHES )
-        step = OFFSET_INCREMENT_MIL/1000.0;
+        step = OFFSET_INCREMENT_MIL / 1000.0;
 
     incrementTextCtrl( textCtrl, -step, -MAX_OFFSET, MAX_OFFSET );
 }
@@ -595,13 +603,24 @@ void PANEL_PREV_3D::onToggleEnableRepeat( wxCommandEvent& event )
     unsigned int stepCount;
     getRepeatVars( stepCount, stepSize );
 
+    double scaler = 1.0f;
+
+    switch( g_UserUnit )
+    {
+    case INCHES:
+        scaler = 25.4f;
+        break;
+    default:
+        break;
+    }
+
     if( m_enableRepeat->GetValue() )
     {
         m_modelInfo.m_Repeat = stepCount;
 
-        m_modelInfo.m_Step.x = stepSize.x;
-        m_modelInfo.m_Step.y = stepSize.y;
-        m_modelInfo.m_Step.z = stepSize.z;
+        m_modelInfo.m_Step.x = stepSize.x * scaler;
+        m_modelInfo.m_Step.y = stepSize.y * scaler;
+        m_modelInfo.m_Step.z = stepSize.z * scaler;
     }
     else
     {
@@ -693,9 +712,9 @@ void PANEL_PREV_3D::onMouseWheelOffset( wxMouseEvent& event )
 
     if( g_UserUnit == INCHES )
     {
-        step = OFFSET_INCREMENT_MIL/1000.0;
+        step = OFFSET_INCREMENT_MIL / 1000.0;
         if( event.ShiftDown( ) )
-            step = OFFSET_INCREMENT_MIL_FINE/1000.0;
+            step = OFFSET_INCREMENT_MIL_FINE / 1000.0;
     }
 
     if( event.GetWheelRotation() >= 0 )
@@ -798,25 +817,6 @@ void PANEL_PREV_3D::getOrientationVars( SGPOINT& aScale, SGPOINT& aRotation, SGP
     yoff->GetValue().ToDouble( &aOffset.y );
     zoff->GetValue().ToDouble( &aOffset.z );
 
-    switch( g_UserUnit )
-    {
-    case MILLIMETRES:
-        // Convert to Inches. Offset is stored in inches.
-        aOffset.x = aOffset.x / 25.4;
-        aOffset.y = aOffset.y / 25.4;
-        aOffset.z = aOffset.z / 25.4;
-        break;
-
-    case INCHES:
-        // It is already in Inches
-        break;
-
-    case DEGREES:
-    case UNSCALED_UNITS:
-    default:
-        wxASSERT(0);
-    }
-
     return;
 }
 
diff --git a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
index a81e778..d879395 100644
--- a/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
+++ b/3d-viewer/3d_rendering/3d_render_raytracing/c3d_render_createscene.cpp
@@ -1275,10 +1275,7 @@ void C3D_RENDER_RAYTRACING::load_3D_models()
 
                     glm::mat4 modelMatrix = moduleMatrix;
 
-                    modelMatrix = glm::translate( modelMatrix,
-                                                  SFVEC3F( xOffset, // * 25.4f,
-                                                           yOffset, // * 25.4f,
-                                                           zOffset ) ); // * 25.4f ) );
+                    modelMatrix = glm::translate( modelMatrix, SFVEC3F( xOffset, yOffset, zOffset ) );
 
                     modelMatrix = glm::rotate( modelMatrix,
                                                (float)-( sM->m_Rotation.z / 180.0f ) *
diff --git a/pcbnew/class_module.h b/pcbnew/class_module.h
index e255e22..892a6db 100644
--- a/pcbnew/class_module.h
+++ b/pcbnew/class_module.h
@@ -108,13 +108,21 @@ class MODULE_3D_SETTINGS
             m_Step.z = 0;
         }
 
-        VECTOR3D m_Scale;
-        VECTOR3D m_Rotation;
-        VECTOR3D m_Offset;
+        /* 3D Model Parameter Internal Units:
+         *
+         * Scale - Dimensionless
+         * Rotation - Degrees
+         * Offset - mm
+         * Repeat - mm
+         */
+
+        VECTOR3D m_Scale;       ///< Model scale factor
+        VECTOR3D m_Rotation;    ///< Model rotation relative to footprint origin
+        VECTOR3D m_Offset;      ///< Model position relative to footprint origin
         wxString m_Filename;    ///< The 3D shape filename in 3D library
 
         unsigned int m_Repeat;  ///< Number of times to repeat the model
-        VECTOR3D m_Step;        ///< Repeat step
+        VECTOR3D m_Step;        ///< Repeat step size
 };
 
 class MODULE : public BOARD_ITEM_CONTAINER
diff --git a/pcbnew/exporters/export_vrml.cpp b/pcbnew/exporters/export_vrml.cpp
index 31b0e01..88c7bff 100644
--- a/pcbnew/exporters/export_vrml.cpp
+++ b/pcbnew/exporters/export_vrml.cpp
@@ -1321,8 +1321,11 @@ static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb,
 
         unsigned int repeat = sM->m_Repeat;
 
+        // Sanity check - there must be at least one 'copy'
         if( repeat < 1 )
+        {
             repeat = 1;
+        }
 
         /* Calculate 3D shape rotation:
          * this is the rotation parameters, with an additional 180 deg rotation
diff --git a/utils/kicad2step/pcb/kicadmodule.cpp b/utils/kicad2step/pcb/kicadmodule.cpp
index 07a855f..6442f34 100644
--- a/utils/kicad2step/pcb/kicadmodule.cpp
+++ b/utils/kicad2step/pcb/kicadmodule.cpp
@@ -373,6 +373,10 @@ bool KICADMODULE::ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
         {
             auto offset = i->m_offset;
 
+            offset.x /= 25.4f;
+            offset.y /= 25.4f;
+            offset.z /= 25.4f;
+
             offset.x += (double) ii * i->m_repeatStep.x / 25.4;
             offset.y += (double) ii * i->m_repeatStep.y / 25.4;
             offset.z += (double) ii * i->m_repeatStep.z / 25.4;
-- 
2.7.4


Follow ups