Commit e3a3d16a authored by CHARRAS's avatar CHARRAS

small bugs fixed. Added: Support for microvias (see changelog)

parent a5e2913d
...@@ -250,7 +250,8 @@ double zpos, height; ...@@ -250,7 +250,8 @@ double zpos, height;
int color; int color;
r = via->m_Width * g_Parm_3D_Visu.m_BoardScale / 2; r = via->m_Width * g_Parm_3D_Visu.m_BoardScale / 2;
hole = g_Parm_3D_Visu.m_BoardSettings->m_ViaDrill * g_Parm_3D_Visu.m_BoardScale / 2; hole = via->GetDrillValue();
hole *= g_Parm_3D_Visu.m_BoardScale / 2;
x = via->m_Start.x * g_Parm_3D_Visu.m_BoardScale; x = via->m_Start.x * g_Parm_3D_Visu.m_BoardScale;
y = via->m_Start.y * g_Parm_3D_Visu.m_BoardScale; y = via->m_Start.y * g_Parm_3D_Visu.m_BoardScale;
......
...@@ -4,6 +4,16 @@ Started 2007-June-11 ...@@ -4,6 +4,16 @@ Started 2007-June-11
Please add newer entries at the top, list the date and your name with Please add newer entries at the top, list the date and your name with
email address. email address.
2007-Dec-12 UPDATE Jean-Pierre Charras <jean-pierre.charras@inpg.fr>
================================================================================
+eeschema:
missing footprints in export netlist (bug 1867321)
Affected: PadsPcb converter did not list all footprints
+pcbnew:
fixed: memory not freeed in block copy (minor bug)
Support for micro vias (generation of drill files not finished)
2008-jan-06 UPDATE Jean-Pierre Charras <jean-pierre.charras@inpg.fr> 2008-jan-06 UPDATE Jean-Pierre Charras <jean-pierre.charras@inpg.fr>
================================================================================ ================================================================================
+pcbnew: +pcbnew:
......
...@@ -250,7 +250,8 @@ void Write_GENERIC_NetList( WinEDA_SchematicFrame* frame, ...@@ -250,7 +250,8 @@ void Write_GENERIC_NetList( WinEDA_SchematicFrame* frame,
/* Create netlist module section */ /* Create netlist module section */
fprintf( tmpfile, "$BeginComponentList\n" ); fprintf( tmpfile, "$BeginComponentList\n" );
for( CurrScreen = ScreenSch; CurrScreen != NULL; CurrScreen = (BASE_SCREEN*) CurrScreen->Pnext ) EDA_ScreenList ScreenList( NULL );
for( CurrScreen = ScreenList.GetFirst(); CurrScreen != NULL; CurrScreen = ScreenList.GetNext() )
{ {
for( DrawList = CurrScreen->EEDrawList; DrawList != NULL; DrawList = DrawList->Pnext ) for( DrawList = CurrScreen->EEDrawList; DrawList != NULL; DrawList = DrawList->Pnext )
{ {
......
...@@ -181,7 +181,7 @@ static int SavePcbFormatAscii( WinEDA_GerberFrame* frame, FILE* aFile, ...@@ -181,7 +181,7 @@ static int SavePcbFormatAscii( WinEDA_GerberFrame* frame, FILE* aFile,
newtrack->SetLayer( 0x0F ); // Layers are 0 to 15 (Cu/Cmp) newtrack->SetLayer( 0x0F ); // Layers are 0 to 15 (Cu/Cmp)
newtrack->m_Drill = -1; newtrack->SetDrillDefault();
// Compute the via position from track position ( Via position is the // Compute the via position from track position ( Via position is the
// position of the middle of the track segment ) // position of the middle of the track segment )
......
...@@ -577,6 +577,7 @@ enum main_id { ...@@ -577,6 +577,7 @@ enum main_id {
ID_POPUP_PCB_EDIT_COTATION, ID_POPUP_PCB_EDIT_COTATION,
ID_POPUP_PCB_END_TRACK, ID_POPUP_PCB_END_TRACK,
ID_POPUP_PCB_PLACE_VIA, ID_POPUP_PCB_PLACE_VIA,
ID_POPUP_PCB_PLACE_MICROVIA,
ID_POPUP_PCB_IMPORT_PAD_SETTINGS, ID_POPUP_PCB_IMPORT_PAD_SETTINGS,
ID_POPUP_PCB_EXPORT_PAD_SETTINGS, ID_POPUP_PCB_EXPORT_PAD_SETTINGS,
......
...@@ -150,7 +150,10 @@ class EDA_BoardDesignSettings ...@@ -150,7 +150,10 @@ class EDA_BoardDesignSettings
public: public:
int m_CopperLayerCount; // Number of copper layers for this design int m_CopperLayerCount; // Number of copper layers for this design
int m_ViaDrill; // via drill (for the entire board) int m_ViaDrill; // via drill (for the entire board)
int m_MicroViaDrill; // micro via drill (for the entire board)
int m_CurrentViaSize; // Current via size int m_CurrentViaSize; // Current via size
int m_CurrentMicroViaSize; // Current micro via size
bool m_MicroViasAllowed; // true to allow micro vias
int m_ViaSizeHistory[HIST0RY_NUMBER]; // Last HIST0RY_NUMBER used via sizes int m_ViaSizeHistory[HIST0RY_NUMBER]; // Last HIST0RY_NUMBER used via sizes
int m_CurrentViaType; // via type (BLIND, TROUGHT ...), bits 1 and 2 (not 0 and 1) int m_CurrentViaType; // via type (BLIND, TROUGHT ...), bits 1 and 2 (not 0 and 1)
int m_CurrentTrackWidth; // current track width int m_CurrentTrackWidth; // current track width
...@@ -222,6 +225,14 @@ public: ...@@ -222,6 +225,14 @@ public:
* @return BOARD_ITEM* - the one selected, or NULL. * @return BOARD_ITEM* - the one selected, or NULL.
*/ */
BOARD_ITEM* GetCurItem() const { return (BOARD_ITEM*) BASE_SCREEN::GetCurItem(); } BOARD_ITEM* GetCurItem() const { return (BOARD_ITEM*) BASE_SCREEN::GetCurItem(); }
/* Return true if a microvia can be put on board
* A microvia ia a small via restricted to 2 near neighbour layers
* because its is hole is made by laser which can penetrate only one layer
* It is mainly used to connect BGA to the first inner layer
* And it is allowed from an external layer to the first inner layer
*/
bool IsMicroViaAcceptable(void);
}; };
/**********************************/ /**********************************/
......
No preview for this file type
This diff is collapsed.
...@@ -110,6 +110,12 @@ void BOARD::UnLink() ...@@ -110,6 +110,12 @@ void BOARD::UnLink()
void BOARD::Add( BOARD_ITEM* aBoardItem, int aControl ) void BOARD::Add( BOARD_ITEM* aBoardItem, int aControl )
{ {
if ( aBoardItem == NULL )
{
wxFAIL_MSG( wxT("BOARD::Add() param error: aBoardItem NULL") );
return;
}
switch( aBoardItem->Type() ) switch( aBoardItem->Type() )
{ {
// this one uses a vector // this one uses a vector
......
...@@ -199,10 +199,10 @@ wxString BOARD_ITEM::MenuText( const BOARD* aPcb ) const ...@@ -199,10 +199,10 @@ wxString BOARD_ITEM::MenuText( const BOARD* aPcb ) const
text << _( "Via" ) << wxT( " " ) << via->ShowWidth(); text << _( "Via" ) << wxT( " " ) << via->ShowWidth();
int shape = via->Shape(); int shape = via->Shape();
if( shape == VIA_BURIED ) if( shape == VIA_BLIND_BURIED )
text << wxT(" ") << _( "Blind" ); text << wxT(" ") << _( "Blind/Buried" );
else if( shape == VIA_BLIND ) else if( shape == VIA_MICROVIA )
text << wxT(" ") << _("Buried"); text << wxT(" ") << _("Micro Via");
// else say nothing about normal (through) vias // else say nothing about normal (through) vias
net = aPcb->FindNet( via->GetNet() ); net = aPcb->FindNet( via->GetNet() );
......
...@@ -65,6 +65,8 @@ wxString DRC_ITEM::GetErrorText() const ...@@ -65,6 +65,8 @@ wxString DRC_ITEM::GetErrorText() const
return wxString( _("Pad near pad") ); return wxString( _("Pad near pad") );
case DRCE_VIA_HOLE_BIGGER: case DRCE_VIA_HOLE_BIGGER:
return wxString( _("Via hole > diameter")); return wxString( _("Via hole > diameter"));
case DRCE_MICRO_VIA_INCORRECT_LAYER_PAIR:
return wxString( _("Micro Via: incorrect layer pairs (not adjacent)"));
case COPPERAREA_INSIDE_COPPERAREA: case COPPERAREA_INSIDE_COPPERAREA:
return wxString( _("Copper area inside copper area")); return wxString( _("Copper area inside copper area"));
case COPPERAREA_CLOSE_TO_COPPERAREA: case COPPERAREA_CLOSE_TO_COPPERAREA:
......
...@@ -68,6 +68,16 @@ MARKER::MARKER( int aErrorCode, const wxPoint& aMarkerPos, ...@@ -68,6 +68,16 @@ MARKER::MARKER( int aErrorCode, const wxPoint& aMarkerPos,
bText, bPos ); bText, bPos );
} }
MARKER::MARKER( int aErrorCode, const wxPoint& aMarkerPos,
const wxString& aText, const wxPoint& aPos ) :
BOARD_ITEM( NULL, TYPEMARKER ) // parent set during BOARD::Add()
{
init();
SetData( aErrorCode, aMarkerPos,
aText, aPos );
}
/* Effacement memoire de la structure */ /* Effacement memoire de la structure */
MARKER::~MARKER() MARKER::~MARKER()
...@@ -91,6 +101,17 @@ void MARKER::SetData( int aErrorCode, const wxPoint& aMarkerPos, ...@@ -91,6 +101,17 @@ void MARKER::SetData( int aErrorCode, const wxPoint& aMarkerPos,
} }
void MARKER::SetData( int aErrorCode, const wxPoint& aMarkerPos,
const wxString& aText, const wxPoint& aPos )
{
m_drc.SetData( aErrorCode, aMarkerPos,
aText, aPos );
// @todo: switch on error code to set error code specific color, and possibly bitmap.
m_Color = WHITE;
}
/* supprime du chainage la structure Struct /* supprime du chainage la structure Struct
* les structures arrieres et avant sont chainees directement * les structures arrieres et avant sont chainees directement
*/ */
...@@ -122,7 +143,8 @@ void MARKER::Display_Infos( WinEDA_DrawFrame* frame ) ...@@ -122,7 +143,8 @@ void MARKER::Display_Infos( WinEDA_DrawFrame* frame )
txtA << DRC_ITEM::ShowCoord( rpt.GetPointA() ) << wxT(": ") << rpt.GetTextA(); txtA << DRC_ITEM::ShowCoord( rpt.GetPointA() ) << wxT(": ") << rpt.GetTextA();
wxString txtB; wxString txtB;
txtB << DRC_ITEM::ShowCoord( rpt.GetPointB() ) << wxT(": ") << rpt.GetTextB(); if ( rpt.AsSecondItem() )
txtB << DRC_ITEM::ShowCoord( rpt.GetPointB() ) << wxT(": ") << rpt.GetTextB();
text_pos = 25; text_pos = 25;
Affiche_1_Parametre( frame, text_pos, txtA, txtB, DARKBROWN ); Affiche_1_Parametre( frame, text_pos, txtA, txtB, DARKBROWN );
......
...@@ -37,8 +37,18 @@ public: ...@@ -37,8 +37,18 @@ public:
MARKER( int aErrorCode, const wxPoint& aMarkerPos, MARKER( int aErrorCode, const wxPoint& aMarkerPos,
const wxString& aText, const wxPoint& aPos, const wxString& aText, const wxPoint& aPos,
const wxString& bText, const wxPoint& bPos ); const wxString& bText, const wxPoint& bPos );
/**
~MARKER(); * Constructor
* @param aErrorCode The categorizing identifier for an error
* @param aMarkerPos The position of the MARKER on the BOARD
* @param aText Text describing the object
* @param aPos The position of the object
*/
MARKER( int aErrorCode, const wxPoint& aMarkerPos,
const wxString& aText, const wxPoint& aPos );
~MARKER();
void UnLink(); void UnLink();
void Draw( WinEDA_DrawPanel* panel, wxDC* DC, int DrawMode ); void Draw( WinEDA_DrawPanel* panel, wxDC* DC, int DrawMode );
...@@ -78,6 +88,17 @@ public: ...@@ -78,6 +88,17 @@ public:
const wxString& aText, const wxPoint& aPos, const wxString& aText, const wxPoint& aPos,
const wxString& bText, const wxPoint& bPos ); const wxString& bText, const wxPoint& bPos );
/**
* Function SetData
* fills in all the reportable data associated with a MARKER.
* @param aErrorCode The categorizing identifier for an error
* @param aMarkerPos The position of the MARKER on the BOARD
* @param aText Text describing the object
* @param aPos The position of the object
*/
void SetData( int aErrorCode, const wxPoint& aMarkerPos,
const wxString& aText, const wxPoint& aPos );
/** /**
* Function GetReporter * Function GetReporter
......
...@@ -52,7 +52,7 @@ TRACK::TRACK( BOARD_ITEM* StructFather, KICAD_T idtype ) : ...@@ -52,7 +52,7 @@ TRACK::TRACK( BOARD_ITEM* StructFather, KICAD_T idtype ) :
start = end = NULL; start = end = NULL;
SetNet( 0 ); SetNet( 0 );
SetSubNet( 0 ); SetSubNet( 0 );
m_Drill = -1; SetDrillDefault();
m_Param = 0; m_Param = 0;
} }
...@@ -131,6 +131,24 @@ TRACK* TRACK::Copy() const ...@@ -131,6 +131,24 @@ TRACK* TRACK::Copy() const
} }
/**
* Function GetDrillValue
* calculate the drill value for vias (m-Drill if > 0, or default drill value for the board
* @return real drill_value
*/
int TRACK::GetDrillValue(void)
{
if ( Type() != TYPEVIA ) return 0;
if ( m_Drill >= 0 ) return m_Drill;
if ( m_Shape == VIA_MICROVIA )
return g_DesignSettings.m_MicroViaDrill;
return g_DesignSettings.m_ViaDrill;
}
/***********************/ /***********************/
bool TRACK::IsNull() bool TRACK::IsNull()
/***********************/ /***********************/
...@@ -253,7 +271,7 @@ int TRACK::ReturnMaskLayer() ...@@ -253,7 +271,7 @@ int TRACK::ReturnMaskLayer()
if( via_type == VIA_THROUGH ) if( via_type == VIA_THROUGH )
return ALL_CU_LAYERS; return ALL_CU_LAYERS;
// VIA_BLIND or VIA_BURIED: // VIA_BLIND_BURIED or VIA_MICRVIA:
int bottom_layer, top_layer; int bottom_layer, top_layer;
...@@ -522,33 +540,6 @@ TRACK* TRACK::GetEndNetCode( int NetCode ) ...@@ -522,33 +540,6 @@ TRACK* TRACK::GetEndNetCode( int NetCode )
} }
#if 0 // replaced by Save()
/********************************************/
bool TRACK::WriteTrackDescr( FILE* File )
/********************************************/
/* write a via description on file
*/
{
int type = 0;
if( Type() == TYPEVIA )
type = 1;
if( GetState( DELETED ) )
return FALSE;
fprintf( File, "Po %d %d %d %d %d %d %d\n", m_Shape,
m_Start.x, m_Start.y, m_End.x, m_End.y, m_Width, m_Drill );
fprintf( File, "De %d %d %d %lX %X\n",
m_Layer, type, GetNet(),
m_TimeStamp, ReturnStatus() );
return TRUE;
}
#endif
bool TRACK::Save( FILE* aFile ) const bool TRACK::Save( FILE* aFile ) const
{ {
int type = 0; int type = 0;
...@@ -631,21 +622,18 @@ void TRACK::Draw( WinEDA_DrawPanel* panel, wxDC* DC, int draw_mode ) ...@@ -631,21 +622,18 @@ void TRACK::Draw( WinEDA_DrawPanel* panel, wxDC* DC, int draw_mode )
GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y, rayon, color ); GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y, rayon, color );
if( rayon > (4 * zoom) ) if( rayon > (4 * zoom) )
{ {
int drill_rayon, inner_rayon = rayon - (2 * zoom); int drill_rayon = GetDrillValue() / 2;
int inner_rayon = rayon - (2 * zoom);
GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y, GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y,
inner_rayon, color ); inner_rayon, color );
// Draw the via hole if the display option allows it // Draw the via hole if the display option allows it
if( DisplayOpt.m_DisplayViaMode != VIA_HOLE_NOT_SHOW ) if( DisplayOpt.m_DisplayViaMode != VIA_HOLE_NOT_SHOW )
{ {
if( (DisplayOpt.m_DisplayViaMode == ALL_VIA_HOLE_SHOW) if( (DisplayOpt.m_DisplayViaMode == ALL_VIA_HOLE_SHOW) || // Display all drill holes requested
|| ( m_Drill > 0 ) ) ( (drill_rayon > 0 ) && ! IsDrillDefault() ) ) // Or Display non default holes requested
{ {
if( m_Drill > 0 ) if( drill_rayon < inner_rayon ) // We can show the via hole
drill_rayon = m_Drill / 2;
else
drill_rayon = g_DesignSettings.m_ViaDrill / 2;
if( drill_rayon < inner_rayon ) // We can show the via hole
{ {
GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y, GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y,
drill_rayon, color ); drill_rayon, color );
...@@ -656,6 +644,33 @@ void TRACK::Draw( WinEDA_DrawPanel* panel, wxDC* DC, int draw_mode ) ...@@ -656,6 +644,33 @@ void TRACK::Draw( WinEDA_DrawPanel* panel, wxDC* DC, int draw_mode )
if( DisplayOpt.DisplayTrackIsol ) if( DisplayOpt.DisplayTrackIsol )
GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y, GRCircle( &panel->m_ClipBox, DC, m_Start.x, m_Start.y,
rayon + g_DesignSettings.m_TrackClearence, color ); rayon + g_DesignSettings.m_TrackClearence, color );
// for Micro Vias, draw a partial cross :
// X on component layer, or + on copper layer
// (so we can see 2 superimposed microvias ):
if ( Shape() == VIA_MICROVIA )
{
int ax, ay, bx, by;
if ( IsOnLayer(COPPER_LAYER_N) )
{
ax = rayon; ay = 0;
bx = drill_rayon; by = 0;
}
else
{
ax = ay = (rayon * 707) / 1000;
bx = by = (drill_rayon * 707) / 1000;
}
/* lines | or \ */
GRLine( &panel->m_ClipBox, DC, m_Start.x - ax , m_Start.y - ay,
m_Start.x - bx , m_Start.y - by, 0, color );
GRLine( &panel->m_ClipBox, DC, m_Start.x + bx , m_Start.y + by,
m_Start.x + ax , m_Start.y + ay, 0, color );
/* lines - or / */
GRLine( &panel->m_ClipBox, DC, m_Start.x + ay, m_Start.y - ax ,
m_Start.x + by, m_Start.y - bx, 0, color );
GRLine( &panel->m_ClipBox, DC, m_Start.x - by, m_Start.y + bx ,
m_Start.x - ay, m_Start.y + ax, 0, color );
}
} }
return; return;
} }
...@@ -813,8 +828,7 @@ void TRACK::Display_Infos( WinEDA_DrawFrame* frame ) ...@@ -813,8 +828,7 @@ void TRACK::Display_Infos( WinEDA_DrawFrame* frame )
{ {
Affiche_1_Parametre( frame, text_pos, _( "Diam" ), msg, DARKCYAN ); Affiche_1_Parametre( frame, text_pos, _( "Diam" ), msg, DARKCYAN );
int drill_value = m_Drill >= 0 ? int drill_value = GetDrillValue();
m_Drill : g_DesignSettings.m_ViaDrill;
valeur_param( (unsigned) drill_value, msg ); valeur_param( (unsigned) drill_value, msg );
...@@ -909,7 +923,7 @@ void TRACK::Show( int nestLevel, std::ostream& os ) ...@@ -909,7 +923,7 @@ void TRACK::Show( int nestLevel, std::ostream& os )
" layer=\"" << m_Layer << '"' << " layer=\"" << m_Layer << '"' <<
" width=\"" << m_Width << '"' << " width=\"" << m_Width << '"' <<
// " drill=\"" << m_Drill << '"' << // " drill=\"" << GetDrillValue() << '"' <<
" netcode=\"" << GetNet() << "\">" << " netcode=\"" << GetNet() << "\">" <<
"<start" << m_Start << "/>" << "<start" << m_Start << "/>" <<
"<end" << m_End << "/>"; "<end" << m_End << "/>";
...@@ -935,12 +949,12 @@ void SEGVIA::Show( int nestLevel, std::ostream& os ) ...@@ -935,12 +949,12 @@ void SEGVIA::Show( int nestLevel, std::ostream& os )
cp = "through"; cp = "through";
break; break;
case VIA_BURIED: case VIA_BLIND_BURIED:
cp = "blind"; cp = "blind/buried";
break; break;
case VIA_BLIND: case VIA_MICROVIA:
cp = "buried"; cp = "micro via";
break; break;
default: default:
...@@ -959,7 +973,7 @@ void SEGVIA::Show( int nestLevel, std::ostream& os ) ...@@ -959,7 +973,7 @@ void SEGVIA::Show( int nestLevel, std::ostream& os )
" layers=\"" << ReturnPcbLayerName( topLayer ).Trim().mb_str() << "," " layers=\"" << ReturnPcbLayerName( topLayer ).Trim().mb_str() << ","
<< ReturnPcbLayerName( botLayer ).Trim().mb_str() << '"' << << ReturnPcbLayerName( botLayer ).Trim().mb_str() << '"' <<
" width=\"" << m_Width << '"' << " width=\"" << m_Width << '"' <<
" drill=\"" << m_Drill << '"' << " drill=\"" << GetDrillValue() << '"' <<
" netcode=\"" << GetNet() << "\">" << " netcode=\"" << GetNet() << "\">" <<
"<pos" << m_Start << "/>"; "<pos" << m_Start << "/>";
......
...@@ -9,10 +9,10 @@ ...@@ -9,10 +9,10 @@
// Via attributes (m_Shape parmeter) // Via attributes (m_Shape parmeter)
#define VIA_THROUGH 3 /* Always a through hole via */ #define VIA_THROUGH 3 /* Always a through hole via */
#define VIA_BURIED 2 /* this via can be on internal layers */ #define VIA_BLIND_BURIED 2 /* this via can be on internal layers */
#define VIA_BLIND 1 /* this via which connect from internal layers to an external layer */ #define VIA_MICROVIA 1 /* this via which connect from an external layer to the near neightbour internal layer */
#define VIA_NOT_DEFINED 0 /* reserved (unused) */ #define VIA_NOT_DEFINED 0 /* not yet used */
/***/ /***/
...@@ -22,10 +22,12 @@ public: ...@@ -22,10 +22,12 @@ public:
int m_Width; // 0 = line, > 0 = tracks, bus ... int m_Width; // 0 = line, > 0 = tracks, bus ...
wxPoint m_Start; // Line start point wxPoint m_Start; // Line start point
wxPoint m_End; // Line end point wxPoint m_End; // Line end point
int m_Shape; // vias: shape and type, Track = shape.. int m_Shape; // vias: shape and type, Track = shape..
int m_Drill; // for vias: via drill (- 1 for default value)
protected:
int m_Drill; // for vias: via drill (- 1 for default value)
public:
BOARD_ITEM* start; // pointers to a connected item (pad or track) BOARD_ITEM* start; // pointers to a connected item (pad or track)
BOARD_ITEM* end; BOARD_ITEM* end;
...@@ -64,20 +66,20 @@ public: ...@@ -64,20 +66,20 @@ public:
{ {
return m_Start; // it had to be start or end. return m_Start; // it had to be start or end.
} }
/* supprime du chainage la structure Struct */ /* supprime du chainage la structure Struct */
void UnLink(); void UnLink();
/** /**
* Function Save * Function Save
* writes the data structures for this object out to a FILE in "*.brd" format. * writes the data structures for this object out to a FILE in "*.brd" format.
* @param aFile The FILE to write to. * @param aFile The FILE to write to.
* @return bool - true if success writing else false. * @return bool - true if success writing else false.
*/ */
bool Save( FILE* aFile ) const; bool Save( FILE* aFile ) const;
/** /**
* Function Insert * Function Insert
* inserts a single TRACK, SEGVIA or SEGZONE, or a list of such, * inserts a single TRACK, SEGVIA or SEGZONE, or a list of such,
...@@ -90,13 +92,13 @@ public: ...@@ -90,13 +92,13 @@ public:
* @param InsertPoint See above * @param InsertPoint See above
*/ */
void Insert( BOARD* aPcb, BOARD_ITEM* InsertPoint ); void Insert( BOARD* aPcb, BOARD_ITEM* InsertPoint );
/** /**
* Function GetBestInsertPoint * Function GetBestInsertPoint
* searches the "best" insertion point within the track linked list. * searches the "best" insertion point within the track linked list.
* The best point is the begging of the corresponding net code section. * The best point is the begging of the corresponding net code section.
* (The BOARD::m_Track and BOARD::m_Zone lists are sorted by netcode.) * (The BOARD::m_Track and BOARD::m_Zone lists are sorted by netcode.)
* @param aPcb The BOARD to search for the insertion point. * @param aPcb The BOARD to search for the insertion point.
* @return TRACK* - the item found in the linked list (or NULL if no track) * @return TRACK* - the item found in the linked list (or NULL if no track)
*/ */
TRACK* GetBestInsertPoint( BOARD* aPcb ); TRACK* GetBestInsertPoint( BOARD* aPcb );
...@@ -134,8 +136,9 @@ public: ...@@ -134,8 +136,9 @@ public:
*/ */
double GetLength() const double GetLength() const
{ {
int dx = m_Start.x - m_End.x; int dx = m_Start.x - m_End.x;
int dy = m_Start.y - m_End.y; int dy = m_Start.y - m_End.y;
return hypot( dx, dy ); return hypot( dx, dy );
} }
...@@ -145,6 +148,32 @@ public: ...@@ -145,6 +148,32 @@ public:
/* divers */ /* divers */
int Shape() const { return m_Shape & 0xFF; } int Shape() const { return m_Shape & 0xFF; }
/**
* Function SetDrillValue
* Set the drill value for vias
* @param drill_value = new drill value
*/
void SetDrillValue(int drill_value) { m_Drill = drill_value; }
/**
* Function SetDrillDefault
* Set the drill value for vias at default value (-1)
*/
void SetDrillDefault(void) { m_Drill = -1; }
/**
* Function IsDrillDefault
* @return true if the drill value is default value (-1)
*/
bool IsDrillDefault(void) { return m_Drill < 0; }
/**
* Function GetDrillValue
* calculate the drill value for vias (m-Drill if > 0, or default drill value for the board
* @return real drill_value
*/
int GetDrillValue(void);
/** /**
* Function ReturnMaskLayer * Function ReturnMaskLayer
...@@ -203,13 +232,13 @@ public: ...@@ -203,13 +232,13 @@ public:
/** /**
* Function HitTest (overlayed) * Function HitTest (overlayed)
* tests if the given wxRect intersect this object. * tests if the given wxRect intersect this object.
* For now, an ending point must be inside this rect. * For now, an ending point must be inside this rect.
* @param refPos A wxPoint to test * @param refPos A wxPoint to test
* @return bool - true if a hit, else false * @return bool - true if a hit, else false
*/ */
bool HitTest( EDA_Rect& refArea ); bool HitTest( EDA_Rect& refArea );
/** /**
* Function GetClass * Function GetClass
* returns the class name. * returns the class name.
* @return wxString * @return wxString
...@@ -284,12 +313,14 @@ public: ...@@ -284,12 +313,14 @@ public:
* returns the position of this object. * returns the position of this object.
* @return const wxPoint& - The position of this object. * @return const wxPoint& - The position of this object.
*/ */
wxPoint& GetPosition() wxPoint& GetPosition()
{ {
return m_Start; return m_Start;
} }
void SetPosition( const wxPoint& aPoint ) { m_Start=aPoint; m_End=aPoint; }
void SetPosition( const wxPoint& aPoint ) { m_Start = aPoint; m_End = aPoint; }
/** /**
* Function GetClass * Function GetClass
* returns the class name. * returns the class name.
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#include "trigo.h" #include "trigo.h"
/**************************************************************/ /**************************************************************/
void EDA_BaseStruct::Place( WinEDA_DrawFrame* frame, wxDC* DC ) void EDA_BaseStruct::Place( WinEDA_DrawFrame* frame, wxDC* DC )
/**************************************************************/ /**************************************************************/
...@@ -301,6 +303,29 @@ void PCB_SCREEN::Init() ...@@ -301,6 +303,29 @@ void PCB_SCREEN::Init()
} }
/* Return true if a microvia can be put on board
* A microvia ia a small via restricted to 2 near neighbour layers
* because its is hole is made by laser which can penetrate only one layer
* It is mainly used to connect BGA to the first inner layer
* And it is allowed from an external layer to the first inner layer
*/
bool PCB_SCREEN::IsMicroViaAcceptable(void)
{
int copperlayercnt = g_DesignSettings.m_CopperLayerCount;
if ( ! g_DesignSettings.m_MicroViasAllowed )
return false; // Obvious..
if ( copperlayercnt < 4 )
return false; // Only on multilayer boards..
if ( (m_Active_Layer == COPPER_LAYER_N) ||
(m_Active_Layer == LAYER_CMP_N) ||
(m_Active_Layer == g_DesignSettings.m_CopperLayerCount - 2) ||
(m_Active_Layer == LAYER_N_2) )
return true;
return false;
}
/*************************/ /*************************/
/* class DISPLAY_OPTIONS */ /* class DISPLAY_OPTIONS */
/*************************/ /*************************/
...@@ -381,8 +406,9 @@ EDA_BoardDesignSettings::EDA_BoardDesignSettings() ...@@ -381,8 +406,9 @@ EDA_BoardDesignSettings::EDA_BoardDesignSettings()
m_LayerColor[ii] = default_layer_color[ii]; m_LayerColor[ii] = default_layer_color[ii];
// Layer colors (tracks and graphic items) // Layer colors (tracks and graphic items)
m_ViaColor[VIA_BLIND] = CYAN; m_ViaColor[VIA_NOT_DEFINED] = DARKGRAY;
m_ViaColor[VIA_BURIED] = BROWN; m_ViaColor[VIA_MICROVIA] = CYAN;
m_ViaColor[VIA_BLIND_BURIED] = BROWN;
m_ViaColor[VIA_THROUGH] = WHITE; m_ViaColor[VIA_THROUGH] = WHITE;
m_ModuleTextCMPColor = LIGHTGRAY; // Text module color for modules on the COMPONENT layer m_ModuleTextCMPColor = LIGHTGRAY; // Text module color for modules on the COMPONENT layer
m_ModuleTextCUColor = MAGENTA; // Text module color for modules on the COPPER layer m_ModuleTextCUColor = MAGENTA; // Text module color for modules on the COPPER layer
......
This diff is collapsed.
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
*/ */
////@begin includes ////@begin includes
#include "wx/valgen.h"
////@end includes ////@end includes
/*! /*!
...@@ -42,6 +43,9 @@ ...@@ -42,6 +43,9 @@
#define ID_TEXTCTRL1 10002 #define ID_TEXTCTRL1 10002
#define ID_TEXTCTRL2 10003 #define ID_TEXTCTRL2 10003
#define ID_VIA_TYPE_SELECTION 10004 #define ID_VIA_TYPE_SELECTION 10004
#define ID_TEXTCTRL7 10008
#define ID_TEXTCTRL6 10010
#define ID_CHECKBOX_ALLOWS_MICROVIA 10009
#define ID_TEXTCTRL3 10005 #define ID_TEXTCTRL3 10005
#define ID_TEXTCTRL4 10006 #define ID_TEXTCTRL4 10006
#define ID_TEXTCTRL5 10007 #define ID_TEXTCTRL5 10007
...@@ -85,6 +89,9 @@ public: ...@@ -85,6 +89,9 @@ public:
/// wxEVT_INIT_DIALOG event handler for ID_DIALOG /// wxEVT_INIT_DIALOG event handler for ID_DIALOG
void OnInitDialog( wxInitDialogEvent& event ); void OnInitDialog( wxInitDialogEvent& event );
/// wxEVT_COMMAND_CHECKBOX_CLICKED event handler for ID_CHECKBOX_ALLOWS_MICROVIA
void OnCheckboxAllowsMicroviaClick( wxCommandEvent& event );
/// wxEVT_COMMAND_BUTTON_CLICKED event handler for wxID_OK /// wxEVT_COMMAND_BUTTON_CLICKED event handler for wxID_OK
void OnOkClick( wxCommandEvent& event ); void OnOkClick( wxCommandEvent& event );
...@@ -115,6 +122,11 @@ public: ...@@ -115,6 +122,11 @@ public:
wxStaticText* m_ViaAltDrillValueTitle; wxStaticText* m_ViaAltDrillValueTitle;
wxTextCtrl* m_OptCustomViaDrill; wxTextCtrl* m_OptCustomViaDrill;
wxRadioBox* m_OptViaType; wxRadioBox* m_OptViaType;
wxStaticText* m_MicroViaSizeTitle;
wxTextCtrl* m_MicroViaSizeCtrl;
wxStaticText* m_MicroViaDrillTitle;
wxTextCtrl* m_MicroViaDrillCtrl;
wxCheckBox* m_AllowMicroViaCtrl;
wxStaticText* m_TrackWidthTitle; wxStaticText* m_TrackWidthTitle;
wxTextCtrl* m_OptTrackWidth; wxTextCtrl* m_OptTrackWidth;
wxStaticText* m_TrackClearanceTitle; wxStaticText* m_TrackClearanceTitle;
......
This diff is collapsed.
...@@ -215,8 +215,8 @@ void DRC::testTracks() ...@@ -215,8 +215,8 @@ void DRC::testTracks()
if( !doTrackDrc( segm, segm->Next() ) ) if( !doTrackDrc( segm, segm->Next() ) )
{ {
wxASSERT( m_currentMarker ); wxASSERT( m_currentMarker );
m_pcb->Add( m_currentMarker ); m_pcb->Add( m_currentMarker );
m_currentMarker = 0; m_currentMarker = 0;
} }
} }
} }
...@@ -367,13 +367,25 @@ MARKER* DRC::fillMarker( TRACK* aTrack, BOARD_ITEM* aItem, int aErrorCode, MARKE ...@@ -367,13 +367,25 @@ MARKER* DRC::fillMarker( TRACK* aTrack, BOARD_ITEM* aItem, int aErrorCode, MARKE
if( fillMe ) if( fillMe )
fillMe->SetData( aErrorCode, position, {
if ( aItem )
fillMe->SetData( aErrorCode, position,
textA, aTrack->GetPosition(), textA, aTrack->GetPosition(),
textB, posB ); textB, posB );
else
fillMe->SetData( aErrorCode, position,
textA, aTrack->GetPosition() );
}
else else
fillMe = new MARKER( aErrorCode, position, {
if ( aItem )
fillMe = new MARKER( aErrorCode, position,
textA, aTrack->GetPosition(), textA, aTrack->GetPosition(),
textB, posB ); textB, posB );
else
fillMe = new MARKER( aErrorCode, position,
textA, aTrack->GetPosition() );
}
return fillMe; return fillMe;
} }
...@@ -421,23 +433,41 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart ) ...@@ -421,23 +433,41 @@ bool DRC::doTrackDrc( TRACK* aRefSeg, TRACK* aStart )
m_segmAngle = 0; m_segmAngle = 0;
// @todo: is this necessary? /* Phase 0 : Test vias : */
/**************************************************************/
/* Phase 0 : test if via's hole is bigger than its diameter : */
/**************************************************************/
if( aRefSeg->Type() == TYPEVIA ) if( aRefSeg->Type() == TYPEVIA )
{ {
// test if via's hole is bigger than its diameter
// This test seems necessary since the dialog box that displays the // This test seems necessary since the dialog box that displays the
// desired via hole size and width does not enforce a hole size smaller // desired via hole size and width does not enforce a hole size smaller
// than the via's diameter. // than the via's diameter.
if( aRefSeg->m_Drill > aRefSeg->m_Width ) if( aRefSeg->GetDrillValue() > aRefSeg->m_Width )
{ {
m_currentMarker = fillMarker( aRefSeg, NULL, m_currentMarker = fillMarker( aRefSeg, NULL,
DRCE_VIA_HOLE_BIGGER, m_currentMarker ); DRCE_VIA_HOLE_BIGGER, m_currentMarker );
return false; return false;
} }
// For microvias: test if they are blindvias and only between 2 layers
// because they are used for very small drill size and are drill by laser
// and **only** one layer can be drilled
if( aRefSeg->Shape() == VIA_MICROVIA )
{
int layer1, layer2;
bool err = true;
((SEGVIA*)aRefSeg)->ReturnLayerPair(&layer1, &layer2);
if (layer1> layer2 ) EXCHG(layer1,layer2);
// test:
if (layer1 == COPPER_LAYER_N && layer2 == LAYER_N_2 ) err = false;
if (layer1 == (g_DesignSettings.m_CopperLayerCount - 2 ) && layer2 == LAYER_CMP_N ) err = false;
if ( err )
{
m_currentMarker = fillMarker( aRefSeg, NULL,
DRCE_MICRO_VIA_INCORRECT_LAYER_PAIR, m_currentMarker );
return false;
}
}
} }
// for a non horizontal or vertical segment Compute the segment angle // for a non horizontal or vertical segment Compute the segment angle
......
This diff is collapsed.
...@@ -61,6 +61,7 @@ void WinEDA_PcbFrame::Process_Special_Functions( wxCommandEvent& event ) ...@@ -61,6 +61,7 @@ void WinEDA_PcbFrame::Process_Special_Functions( wxCommandEvent& event )
case ID_POPUP_PCB_STOP_CURRENT_DRAWING: case ID_POPUP_PCB_STOP_CURRENT_DRAWING:
case ID_POPUP_PCB_END_TRACK: case ID_POPUP_PCB_END_TRACK:
case ID_POPUP_PCB_PLACE_VIA: case ID_POPUP_PCB_PLACE_VIA:
case ID_POPUP_PCB_PLACE_MICROVIA:
case ID_POPUP_PCB_IMPORT_PAD_SETTINGS: case ID_POPUP_PCB_IMPORT_PAD_SETTINGS:
case ID_POPUP_PCB_EXPORT_PAD_SETTINGS: case ID_POPUP_PCB_EXPORT_PAD_SETTINGS:
case ID_POPUP_PCB_GLOBAL_IMPORT_PAD_SETTINGS: case ID_POPUP_PCB_GLOBAL_IMPORT_PAD_SETTINGS:
...@@ -372,6 +373,9 @@ void WinEDA_PcbFrame::Process_Special_Functions( wxCommandEvent& event ) ...@@ -372,6 +373,9 @@ void WinEDA_PcbFrame::Process_Special_Functions( wxCommandEvent& event )
} }
break; break;
case ID_POPUP_PCB_PLACE_MICROVIA:
if ( ! GetScreen()->IsMicroViaAcceptable() )
break;
case ID_POPUP_PCB_PLACE_VIA: case ID_POPUP_PCB_PLACE_VIA:
DrawPanel->MouseToCursorSchema(); DrawPanel->MouseToCursorSchema();
if( GetCurItem()->m_Flags & IS_DRAGGED ) if( GetCurItem()->m_Flags & IS_DRAGGED )
...@@ -380,13 +384,17 @@ void WinEDA_PcbFrame::Process_Special_Functions( wxCommandEvent& event ) ...@@ -380,13 +384,17 @@ void WinEDA_PcbFrame::Process_Special_Functions( wxCommandEvent& event )
} }
else else
{ {
int v_type = g_DesignSettings.m_CurrentViaType;
if ( id == ID_POPUP_PCB_PLACE_MICROVIA )
g_DesignSettings.m_CurrentViaType = VIA_MICROVIA; // place micro via and switch layer
Other_Layer_Route( (TRACK*) GetCurItem(), &dc ); Other_Layer_Route( (TRACK*) GetCurItem(), &dc );
g_DesignSettings.m_CurrentViaType = v_type;
if( DisplayOpt.ContrastModeDisplay ) if( DisplayOpt.ContrastModeDisplay )
GetScreen()->SetRefreshReq(); GetScreen()->SetRefreshReq();
} }
break; break;
case ID_POPUP_PCB_DELETE_TRACKSEG: case ID_POPUP_PCB_DELETE_TRACKSEG:
if( GetCurItem() == NULL ) if( GetCurItem() == NULL )
break; break;
DrawPanel->MouseToCursorSchema(); DrawPanel->MouseToCursorSchema();
......
...@@ -34,6 +34,8 @@ int WinEDA_PcbFrame::Edit_TrackSegm_Width( wxDC* DC, TRACK* pt_segm ) ...@@ -34,6 +34,8 @@ int WinEDA_PcbFrame::Edit_TrackSegm_Width( wxDC* DC, TRACK* pt_segm )
if( pt_segm->Type() == TYPEVIA ) if( pt_segm->Type() == TYPEVIA )
{ {
consigne = pt_segm->m_Width = g_DesignSettings.m_CurrentViaSize; consigne = pt_segm->m_Width = g_DesignSettings.m_CurrentViaSize;
if ( pt_segm->m_Shape == VIA_MICROVIA )
consigne = pt_segm->m_Width = g_DesignSettings.m_CurrentMicroViaSize;
} }
if( old_w < consigne ) /* DRC utile puisque augm de dimension */ if( old_w < consigne ) /* DRC utile puisque augm de dimension */
......
...@@ -210,11 +210,10 @@ bool WinEDA_PcbFrame::Other_Layer_Route( TRACK* track, wxDC* DC ) ...@@ -210,11 +210,10 @@ bool WinEDA_PcbFrame::Other_Layer_Route( TRACK* track, wxDC* DC )
/* create the via */ /* create the via */
Via = new SEGVIA( m_Pcb ); Via = new SEGVIA( m_Pcb );
Via->m_Flags = IS_NEW; Via->m_Flags = IS_NEW;
Via->m_Width = g_DesignSettings.m_CurrentViaSize;
Via->m_Shape = g_DesignSettings.m_CurrentViaType; Via->m_Shape = g_DesignSettings.m_CurrentViaType;
Via->m_Width = g_DesignSettings.m_CurrentViaSize;
Via->SetNet( g_HightLigth_NetCode ); Via->SetNet( g_HightLigth_NetCode );
Via->m_Start = Via->m_End = g_CurrentTrackSegment->m_End; Via->m_Start = Via->m_End = g_CurrentTrackSegment->m_End;
int old_layer = GetScreen()->m_Active_Layer; int old_layer = GetScreen()->m_Active_Layer;
//swap the layers. //swap the layers.
...@@ -224,21 +223,30 @@ bool WinEDA_PcbFrame::Other_Layer_Route( TRACK* track, wxDC* DC ) ...@@ -224,21 +223,30 @@ bool WinEDA_PcbFrame::Other_Layer_Route( TRACK* track, wxDC* DC )
GetScreen()->m_Active_Layer = GetScreen()->m_Route_Layer_BOTTOM; GetScreen()->m_Active_Layer = GetScreen()->m_Route_Layer_BOTTOM;
/* Adjust the via layer pair */ /* Adjust the via layer pair */
if( Via->Shape() == VIA_BURIED ) switch ( Via->Shape() )
{ {
Via->SetLayerPair( old_layer, GetScreen()->m_Active_Layer ); case VIA_BLIND_BURIED:
} Via->SetLayerPair( old_layer, GetScreen()->m_Active_Layer );
break;
else if( Via->Shape() == VIA_BLIND ) //blind via
{ case VIA_MICROVIA: // from external to the near neghbour inner layer
// A revoir! ( la via devrait deboucher sur 1 cote ) if ( old_layer == COPPER_LAYER_N )
Via->SetLayerPair( old_layer, GetScreen()->m_Active_Layer ); GetScreen()->m_Active_Layer = LAYER_N_2;
} else if ( old_layer == LAYER_CMP_N )
GetScreen()->m_Active_Layer = m_Pcb->m_BoardSettings->m_CopperLayerCount - 2;
else else if ( old_layer == LAYER_N_2 )
{ GetScreen()->m_Active_Layer = COPPER_LAYER_N;
// Usual via is from copper to component; layer pair is 0 and 0x0F. else if ( old_layer == m_Pcb->m_BoardSettings->m_CopperLayerCount - 2 )
Via->SetLayerPair( COPPER_LAYER_N, LAYER_CMP_N ); GetScreen()->m_Active_Layer = LAYER_CMP_N;
// else error
Via->SetLayerPair( old_layer, GetScreen()->m_Active_Layer );
Via->m_Width = g_DesignSettings.m_CurrentMicroViaSize;
break;
default:
// Usual via is from copper to component; layer pair is 0 and 0x0F.
Via->SetLayerPair( COPPER_LAYER_N, LAYER_CMP_N );
break;
} }
if( Drc_On && BAD_DRC==m_drc->Drc( Via, m_Pcb->m_Track ) ) if( Drc_On && BAD_DRC==m_drc->Drc( Via, m_Pcb->m_Track ) )
......
...@@ -469,7 +469,7 @@ void WinEDA_DrillFrame::UpdatePrecisionOptions( wxCommandEvent& event ) ...@@ -469,7 +469,7 @@ void WinEDA_DrillFrame::UpdatePrecisionOptions( wxCommandEvent& event )
int WinEDA_DrillFrame::Gen_Drill_File_EXCELLON( FORET* buffer ) int WinEDA_DrillFrame::Gen_Drill_File_EXCELLON( FORET* buffer )
/***************************************************************/ /***************************************************************/
/* Create the drill file in EXECELLON format /* Create the drill file in EXCELLON format
* Return hole count * Return hole count
* buffer: Drill tools list * buffer: Drill tools list
*/ */
...@@ -497,10 +497,10 @@ int WinEDA_DrillFrame::Gen_Drill_File_EXCELLON( FORET* buffer ) ...@@ -497,10 +497,10 @@ int WinEDA_DrillFrame::Gen_Drill_File_EXCELLON( FORET* buffer )
{ {
if( pt_piste->Type() != TYPEVIA ) if( pt_piste->Type() != TYPEVIA )
continue; continue;
if( pt_piste->m_Drill == 0 ) int via_drill = pt_piste->GetDrillValue();
if( via_drill == 0 )
continue; continue;
int via_drill = ( pt_piste->m_Drill <
0 ) ? g_DesignSettings.m_ViaDrill : pt_piste->m_Drill;
if( foret->m_Diameter != via_drill ) if( foret->m_Diameter != via_drill )
continue; continue;
...@@ -789,11 +789,9 @@ int WinEDA_DrillFrame::Gen_Liste_Forets( FORET* buffer, bool print_header ) ...@@ -789,11 +789,9 @@ int WinEDA_DrillFrame::Gen_Liste_Forets( FORET* buffer, bool print_header )
{ {
if( pt_piste->Type() != TYPEVIA ) if( pt_piste->Type() != TYPEVIA )
continue; continue;
if( pt_piste->m_Drill == 0 ) int via_drill = pt_piste->GetDrillValue();
if( via_drill == 0 )
continue; continue;
int via_drill = g_DesignSettings.m_ViaDrill;
if( pt_piste->m_Drill > 0 ) // Drill value is not the default value
via_drill = pt_piste->m_Drill;
foret = GetOrAddForet( buffer, via_drill ); foret = GetOrAddForet( buffer, via_drill );
if( foret ) if( foret )
foret->m_TotalCount++; foret->m_TotalCount++;
...@@ -1343,9 +1341,7 @@ int WinEDA_DrillFrame::Plot_Drill_PcbMap( FORET* buffer, int format ) ...@@ -1343,9 +1341,7 @@ int WinEDA_DrillFrame::Plot_Drill_PcbMap( FORET* buffer, int format )
{ {
if( pt_piste->Type() != TYPEVIA ) if( pt_piste->Type() != TYPEVIA )
continue; continue;
int via_drill = g_DesignSettings.m_ViaDrill; int via_drill = pt_piste->GetDrillValue();
if( pt_piste->m_Drill >= 0 )
via_drill = pt_piste->m_Drill;
if( via_drill != foret->m_Diameter ) if( via_drill != foret->m_Diameter )
continue; continue;
pos = pt_piste->m_Start; pos = pt_piste->m_Start;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment