Commit 1f277fd6 authored by jean-pierre charras's avatar jean-pierre charras

Remove Kbool from Kicad. Use Clipper instead.

parent 7fd24c7f
......@@ -170,13 +170,9 @@ GLuint EDA_3D_CANVAS::CreateDrawGL_List()
if( g_Parm_3D_Visu.m_Layers < 2 )
g_Parm_3D_Visu.m_Layers = 2;
g_Parm_3D_Visu.m_BoardScale = 2.0 / max( g_Parm_3D_Visu.m_BoardSize.x,
g_Parm_3D_Visu.m_BoardScale = 2.0 / std::max( g_Parm_3D_Visu.m_BoardSize.x,
g_Parm_3D_Visu.m_BoardSize.y );
// @TODO: epoxy_width (board thickness) must be set by user,
// because all boards thickness no not match with this setup:
// double epoxy_width = 1.6; // epoxy width in mm
g_Parm_3D_Visu.m_Epoxy_Width = pcb->GetDesignSettings().GetBoardThickness()
* g_Parm_3D_Visu.m_BoardScale;
......
......@@ -46,7 +46,7 @@ endif(APPLE)
target_link_libraries( bitmap2component common polygon bitmaps
${wxWidgets_LIBRARIES}
potrace
kbool )
)
install(TARGETS bitmap2component
DESTINATION ${KICAD_BIN}
......
......@@ -70,7 +70,7 @@ bool FOOTPRINT_LIST::ReadFootprintFiles( wxArrayString& aFootprintsLibNames )
for( unsigned i=0; i<fpnames.GetCount(); ++i )
{
auto_ptr<MODULE> m( pi->FootprintLoad( libPath, fpnames[i] ) );
std::auto_ptr<MODULE> m( pi->FootprintLoad( libPath, fpnames[i] ) );
// we're loading what we enumerated, all must be there.
wxASSERT( m.get() );
......
......@@ -98,7 +98,6 @@ target_link_libraries(cvpcb
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
${OPENGL_LIBRARIES}
${GDI_PLUS_LIBRARIES}
......
......@@ -217,7 +217,6 @@ target_link_libraries(eeschema
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
${GDI_PLUS_LIBRARIES}
)
......
......@@ -121,7 +121,7 @@ endif(APPLE)
###
# Link executable target gerbview with correct libraries
###
target_link_libraries(gerbview common polygon bitmaps kbool
target_link_libraries(gerbview common polygon bitmaps
${OPENGL_LIBRARIES}
${wxWidgets_LIBRARIES}
${GDI_PLUS_LIBRARIES})
......
......@@ -51,7 +51,6 @@ if(APPLE)
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
)
else(APPLE)
......@@ -59,7 +58,6 @@ else(APPLE)
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
${GDI_PLUS_LIBRARIES}
)
......
......@@ -82,7 +82,6 @@ target_link_libraries( pcb_calculator
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
)
......
......@@ -325,7 +325,6 @@ if (KICAD_SCRIPTING_MODULES)
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
${OPENGL_LIBRARIES}
${GDI_PLUS_LIBRARIES}
......@@ -418,7 +417,6 @@ target_link_libraries(pcbnew
common
bitmaps
polygon
kbool
${wxWidgets_LIBRARIES}
${OPENGL_LIBRARIES}
${GDI_PLUS_LIBRARIES}
......
......@@ -186,7 +186,7 @@ void DIALOG_GENDRILL::InitDisplayParams()
}
else
{
if( min( pad->GetDrillSize().x, pad->GetDrillSize().y ) != 0 )
if( std::min( pad->GetDrillSize().x, pad->GetDrillSize().y ) != 0 )
{
if( pad->GetAttribute() == PAD_HOLE_NOT_PLATED )
m_notplatedPadsHoleCount++;
......
......@@ -76,6 +76,7 @@ Load() TODO's
#include <class_pcb_text.h>
using namespace boost::property_tree;
using namespace std;
typedef EAGLE_PLUGIN::BIU BIU;
typedef PTREE::const_assoc_iterator CA_ITER;
......
......@@ -63,7 +63,7 @@ static bool s_AddCutoutToCurrentZone; // if true, the next outline
static ZONE_CONTAINER* s_CurrentZone; // if != NULL, these ZONE_CONTAINER params will be used for the next zone
static wxPoint s_CursorLastPosition; // in move zone outline, last cursor position. Used to calculate the move vector
static PICKED_ITEMS_LIST s_PickedList; // a picked list to save zones for undo/redo command
static PICKED_ITEMS_LIST _AuxiliaryList; // a picked list to store zones that are deleted or added when combined
static PICKED_ITEMS_LIST s_AuxiliaryList; // a picked list to store zones that are deleted or added when combined
void PCB_EDIT_FRAME::Add_Similar_Zone( wxDC* DC, ZONE_CONTAINER* aZone )
......@@ -128,7 +128,7 @@ void PCB_EDIT_FRAME::duplicateZone( wxDC* aDC, ZONE_CONTAINER* aZone )
zoneSettings.ExportSetting( *newZone );
newZone->m_Poly->Hatch();
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList.ClearListAndDeleteItems();
SaveCopyOfZones( s_PickedList, GetBoard(), newZone->GetNet(), newZone->GetLayer() );
GetBoard()->Add( newZone );
......@@ -139,7 +139,7 @@ void PCB_EDIT_FRAME::duplicateZone( wxDC* aDC, ZONE_CONTAINER* aZone )
GetScreen()->SetCurItem( NULL ); // This outline may be deleted when merging outlines
// Combine zones if possible
GetBoard()->OnAreaPolygonModified( &_AuxiliaryList, newZone );
GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, newZone );
// Redraw zones
GetBoard()->RedrawAreasOutlines( m_canvas, aDC, GR_OR, newZone->GetLayer() );
......@@ -151,7 +151,7 @@ void PCB_EDIT_FRAME::duplicateZone( wxDC* aDC, ZONE_CONTAINER* aZone )
DisplayError( this, _( "Duplicate Zone: The outline of the duplicated zone fails DRC check!" ) );
}
UpdateCopyOfZonesList( s_PickedList, _AuxiliaryList, GetBoard() );
UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
SaveCopyInUndoList( s_PickedList, UR_UNSPECIFIED );
s_PickedList.ClearItemsList();
......@@ -245,7 +245,7 @@ void PCB_EDIT_FRAME::Start_Move_Zone_Corner( wxDC* DC, ZONE_CONTAINER* aZone,
if ( IsNewCorner )
aZone->m_Poly->DeleteCorner( corner_id );
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList.ClearListAndDeleteItems();
SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNet(),
......@@ -277,7 +277,7 @@ void PCB_EDIT_FRAME::Start_Move_Zone_Drag_Outline_Edge( wxDC* DC,
s_CurrentZone = NULL;
s_PickedList.ClearListAndDeleteItems();
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNet(),
aZone->GetLayer() );
}
......@@ -302,7 +302,7 @@ void PCB_EDIT_FRAME::Start_Move_Zone_Outlines( wxDC* DC, ZONE_CONTAINER* aZone )
}
s_PickedList.ClearListAndDeleteItems();
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNet(),
aZone->GetLayer() );
......@@ -332,7 +332,7 @@ void PCB_EDIT_FRAME::End_Move_Zone_Corner_Or_Outlines( wxDC* DC, ZONE_CONTAINER*
// Combine zones if possible
wxBusyCursor dummy;
GetBoard()->OnAreaPolygonModified( &_AuxiliaryList, aZone );
GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, aZone );
m_canvas->Refresh();
......@@ -341,7 +341,7 @@ void PCB_EDIT_FRAME::End_Move_Zone_Corner_Or_Outlines( wxDC* DC, ZONE_CONTAINER*
if( ii < 0 )
aZone = NULL; // was removed by combining zones
UpdateCopyOfZonesList( s_PickedList, _AuxiliaryList, GetBoard() );
UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items
......@@ -380,14 +380,14 @@ void PCB_EDIT_FRAME::Remove_Zone_Corner( wxDC* DC, ZONE_CONTAINER* aZone )
GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_XOR, layer );
}
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList. ClearListAndDeleteItems();
SaveCopyOfZones( s_PickedList, GetBoard(), aZone->GetNet(),
aZone->GetLayer() );
aZone->m_Poly->DeleteCorner( aZone->m_CornerSelection );
// modify zones outlines according to the new aZone shape
GetBoard()->OnAreaPolygonModified( &_AuxiliaryList, aZone );
GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, aZone );
if( DC )
{
......@@ -395,7 +395,7 @@ void PCB_EDIT_FRAME::Remove_Zone_Corner( wxDC* DC, ZONE_CONTAINER* aZone )
GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_OR, layer );
}
UpdateCopyOfZonesList( s_PickedList, _AuxiliaryList, GetBoard() );
UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items
......@@ -448,7 +448,7 @@ void Abort_Zone_Move_Corner_Or_Outlines( EDA_DRAW_PANEL* Panel, wxDC* DC )
}
Panel->SetMouseCapture( NULL, NULL );
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList. ClearListAndDeleteItems();
Panel->Refresh();
......@@ -733,7 +733,7 @@ bool PCB_EDIT_FRAME::End_Zone( wxDC* DC )
GetBoard()->RedrawFilledAreas( m_canvas, DC, GR_XOR, layer );
// Save initial zones configuration, for undo/redo, before adding new zone
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList.ClearListAndDeleteItems();
SaveCopyOfZones(s_PickedList, GetBoard(), zone->GetNet(), zone->GetLayer() );
......@@ -766,7 +766,7 @@ bool PCB_EDIT_FRAME::End_Zone( wxDC* DC )
GetScreen()->SetCurItem( NULL ); // This outline can be deleted when merging outlines
// Combine zones if possible :
GetBoard()->OnAreaPolygonModified( &_AuxiliaryList, zone );
GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, zone );
// Redraw the real edge zone :
GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_OR, layer );
......@@ -784,7 +784,7 @@ bool PCB_EDIT_FRAME::End_Zone( wxDC* DC )
DisplayError( this, _( "Area: DRC outline error" ) );
}
UpdateCopyOfZonesList( s_PickedList, _AuxiliaryList, GetBoard() );
UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items
......@@ -838,7 +838,7 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( wxDC* DC, ZONE_CONTAINER* aZone )
// Save initial zones configuration, for undo/redo, before adding new zone
// note the net name and the layer can be changed, so we must save all zones
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList.ClearListAndDeleteItems();
SaveCopyOfZones(s_PickedList, GetBoard(), -1, -1 );
......@@ -866,7 +866,7 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( wxDC* DC, ZONE_CONTAINER* aZone )
if( edited == ZONE_ABORT )
{
_AuxiliaryList.ClearListAndDeleteItems();
s_AuxiliaryList.ClearListAndDeleteItems();
s_PickedList.ClearListAndDeleteItems();
return;
}
......@@ -875,7 +875,7 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( wxDC* DC, ZONE_CONTAINER* aZone )
if( edited == ZONE_EXPORT_VALUES )
{
UpdateCopyOfZonesList( s_PickedList, _AuxiliaryList, GetBoard() );
UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
s_PickedList.ClearItemsList(); // s_ItemsListPicker is no more owner of picked items
return;
......@@ -896,12 +896,12 @@ void PCB_EDIT_FRAME::Edit_Zone_Params( wxDC* DC, ZONE_CONTAINER* aZone )
aZone->SetNetName( net->GetNetname() );
// Combine zones if possible
GetBoard()->OnAreaPolygonModified( &_AuxiliaryList, aZone );
GetBoard()->OnAreaPolygonModified( &s_AuxiliaryList, aZone );
// Redraw the real new zone outlines
GetBoard()->RedrawAreasOutlines( m_canvas, DC, GR_OR, -1 );
UpdateCopyOfZonesList( s_PickedList, _AuxiliaryList, GetBoard() );
UpdateCopyOfZonesList( s_PickedList, s_AuxiliaryList, GetBoard() );
SaveCopyInUndoList(s_PickedList, UR_UNSPECIFIED);
s_PickedList.ClearItemsList(); // s_ItemsListPicker is no longer owner of picked items
......
......@@ -174,19 +174,26 @@ int SaveCopyOfZones( PICKED_ITEMS_LIST& aPickList, BOARD* aPcb, int aNetCode, in
* @param aPcb = the Board
*
* aAuxiliaryList is a list of pickers updated by zone algorithms:
* This list cointains zones which were added or deleted during the zones combine process
* This list contains zones which were added or deleted during the zones combine process
* aPickList :is a list of zones that can be modified (changed or deleted, or not modified)
* Typically, this is the list of existing zones on the layer of the edited zone,
* before any change.
* >> if the picked zone is not changed, it is removed from list
* >> if the picked zone was deleted (i.e. not found in boad list), the picker is modified:
* - its status becomes UR_DELETED
* - the aAuxiliaryList corresponding picker is removed (if not found : set an error)
* >> if the picked zone was flagged as UR_NEW, and was deleted (i.e. not found in boad list),
* - the picker is removed
* - the zone itself if really deleted
* - the aAuxiliaryList corresponding picker is removed (if not found : set an error)
* >> if the picked zone was deleted (i.e. not found in board list), the picker is modified:
* its status becomes UR_DELETED
* the aAuxiliaryList corresponding picker is removed (if not found : set an error)
* >> if the picked zone was flagged as UR_NEW, and was after deleted ,
* perhaps combined with an other zone (i.e. not found in board list):
* the picker is removed
* the zone itself if really deleted
* the aAuxiliaryList corresponding picker is removed (if not found : set an error)
* After aPickList is cleaned, the aAuxiliaryList is read
* All pickers flagged UR_NEW are moved to aPickList
* (the corresponding zones are zone that were created by the zone combine process, mainly when adding cutaout areas)
* (the corresponding zones are zone that were created by the zone normalize and combine process,
* mainly when adding cutout areas, or creating self intersecting contours)
* All pickers flagged UR_DELETED are removed, and the coresponding zones actually deleted
* (the corresponding zones are new zone that were created by the zone normalize process,
* when creating self intersecting contours, and after combined with an existing zone.
* At the end of the update process the aAuxiliaryList must be void,
* because all pickers created by the combine process
* must have been removed (removed for new and deleted zones, or moved in aPickList.)
......@@ -274,15 +281,21 @@ void UpdateCopyOfZonesList( PICKED_ITEMS_LIST& aPickList,
// Add new zones in main pick list, and remove pickers from Auxiliary List
for( unsigned ii = 0; ii < aAuxiliaryList.GetCount(); ii++ )
for( unsigned ii = 0; ii < aAuxiliaryList.GetCount(); )
{
if( aAuxiliaryList.GetPickedItemStatus( ii ) == UR_NEW )
{
ITEM_PICKER picker = aAuxiliaryList.GetItemWrapper( ii );
aPickList.PushItem( picker );
aAuxiliaryList.RemovePicker( ii );
ii--;
}
else if( aAuxiliaryList.GetPickedItemStatus( ii ) == UR_DELETED )
{
delete aAuxiliaryList.GetPickedItemLink( ii );
aAuxiliaryList.RemovePicker( ii );
}
else
ii++;
}
// Should not occur:
......
......@@ -211,12 +211,12 @@ bool BOARD::OnAreaPolygonModified( PICKED_ITEMS_LIST* aModifiedZonesList,
// Test for bad areas: all zones must have more than 2 corners:
// Note: should not happen, but just in case.
for( unsigned ia1 = 0; ia1 < m_ZoneDescriptorList.size() - 1; )
for( unsigned ii = 0; ii < m_ZoneDescriptorList.size(); )
{
ZONE_CONTAINER* zone = m_ZoneDescriptorList[ia1];
ZONE_CONTAINER* zone = m_ZoneDescriptorList[ii];
if( zone->GetNumCorners() >= 3 )
ia1++;
ii++;
else // Remove zone because it is incorrect:
RemoveArea( aModifiedZonesList, zone );
}
......@@ -267,8 +267,9 @@ bool BOARD::CombineAllAreasInNet( PICKED_ITEMS_LIST* aDeletedList, int aNetCode,
if( curr_area->GetIsKeepout() != area2->GetIsKeepout() )
continue;
if( curr_area->GetLayer() == area2->GetLayer() )
{
if( curr_area->GetLayer() != area2->GetLayer() )
continue;
CRect b2 = area2->m_Poly->GetCornerBounds();
if( !( b1.left > b2.right || b1.right < b2.left
|| b1.bottom > b2.top || b1.top < b2.bottom ) )
......@@ -289,7 +290,6 @@ bool BOARD::CombineAllAreasInNet( PICKED_ITEMS_LIST* aDeletedList, int aNetCode,
}
}
}
}
if( mod_ia1 )
ia1--; // if modified, we need to check it again
......@@ -424,7 +424,7 @@ bool BOARD::TestAreaIntersection( ZONE_CONTAINER* area_ref, ZONE_CONTAINER* area
if( poly1->TestPointInside( x, y ) )
{
return 1;
return true;
}
}
......@@ -435,11 +435,11 @@ bool BOARD::TestAreaIntersection( ZONE_CONTAINER* area_ref, ZONE_CONTAINER* area
if( poly2->TestPointInside( x, y ) )
{
return 1;
return true;
}
}
return 0;
return false;
}
......@@ -465,10 +465,6 @@ bool BOARD::CombineAreas( PICKED_ITEMS_LIST* aDeletedList, ZONE_CONTAINER* area_
}
// polygons intersect, combine them
// TODO: test here if areas intersect and combine only if so
#if 0
// do not set to 1 (not fully working): only for me (JP. Charras) until this code is finished
KI_POLYGON_WITH_HOLES areaRefPoly;
KI_POLYGON_WITH_HOLES areaToMergePoly;
CopyPolysListToKiPolygonWithHole( area_ref->m_Poly->m_CornersList, areaRefPoly );
......@@ -478,10 +474,20 @@ bool BOARD::CombineAreas( PICKED_ITEMS_LIST* aDeletedList, ZONE_CONTAINER* area_
mergedOutlines.push_back( areaRefPoly );
mergedOutlines |= areaToMergePoly;
// We can have more than one polygon with holes in mergedOutlines
// depending on the complexity of outlines
// We should have one polygon with hole
// We can have 2 polygons with hole, if the 2 initial polygons have only one common corner
// and therefore cannot be merged (they are dectected as intersecting)
// but we should never have more than 2 polys
if( mergedOutlines.size() > 2 )
{
wxLogMessage(wxT("BOARD::CombineAreas error: more than 2 polys after merging") );
return false;
}
if( mergedOutlines.size() > 1 )
return false;
areaRefPoly = mergedOutlines[0]; // TODO: read and create all created polygons
areaRefPoly = mergedOutlines[0];
area_ref->m_Poly->RemoveAllContours();
KI_POLYGON_WITH_HOLES::iterator_type corner = areaRefPoly.begin();
......@@ -509,76 +515,6 @@ bool BOARD::CombineAreas( PICKED_ITEMS_LIST* aDeletedList, ZONE_CONTAINER* area_
area_ref->m_Poly->CloseLastContour();
hole++;
}
#else
void armBoolEng( Bool_Engine* aBooleng, bool aConvertHoles = false );
Bool_Engine* booleng = new Bool_Engine();
armBoolEng( booleng );
area_ref->m_Poly->AddPolygonsToBoolEng( booleng, GROUP_A );
area_to_combine->m_Poly->AddPolygonsToBoolEng(booleng, GROUP_B );
booleng->Do_Operation( BOOL_OR );
// create area with external contour: Recreate only area edges, NOT holes
if( booleng->StartPolygonGet() )
{
if( booleng->GetPolygonPointEdgeType() == KB_INSIDE_EDGE )
{
DisplayError( NULL, wxT( "BOARD::CombineAreas() error: unexpected hole descriptor" ) );
}
area_ref->m_Poly->RemoveAllContours();
// foreach point in the polygon
bool first = true;
while( booleng->PolygonHasMorePoints() )
{
int x = (int) booleng->GetPolygonXPoint();
int y = (int) booleng->GetPolygonYPoint();
if( first )
{
first = false;
area_ref->m_Poly->Start( area_ref->GetLayer(
), x, y, area_ref->m_Poly->GetHatchStyle() );
}
else
{
area_ref->m_Poly->AppendCorner( x, y );
}
}
booleng->EndPolygonGet();
area_ref->m_Poly->CloseLastContour();
}
// add holes
bool show_error = true;
while( booleng->StartPolygonGet() )
{
// we expect all vertex are holes inside the main outline
if( booleng->GetPolygonPointEdgeType() != KB_INSIDE_EDGE )
{
if( show_error ) // show this error only once, if happens
DisplayError( NULL,
wxT( "BOARD::CombineAreas() error: unexpected outside contour descriptor" ) );
show_error = false;
continue;
}
while( booleng->PolygonHasMorePoints() )
{
int x = (int) booleng->GetPolygonXPoint();
int y = (int) booleng->GetPolygonYPoint();
area_ref->m_Poly->AppendCorner( x, y );
}
area_ref->m_Poly->CloseLastContour();
booleng->EndPolygonGet();
}
#endif
RemoveArea( aDeletedList, area_to_combine );
......@@ -822,7 +758,7 @@ bool DRC::doEdgeZoneDrc( ZONE_CONTAINER* aArea, int aCornerIndex )
for( int ia2 = 0; ia2 < m_pcb->GetAreaCount(); ia2++ )
{
ZONE_CONTAINER* area_to_test = m_pcb->GetArea( ia2 );
int zone_clearance = max( area_to_test->m_ZoneClearance,
int zone_clearance = std::max( area_to_test->m_ZoneClearance,
aArea->m_ZoneClearance );
// test for same layer
......
......@@ -8,6 +8,7 @@ set(POLYGON_SRCS
math_for_graphics.cpp
PolyLine.cpp
polygon_test_point_inside.cpp
clipper.cpp
)
add_library(polygon STATIC ${POLYGON_SRCS})
This diff is collapsed.
......@@ -17,7 +17,7 @@
#include <vector>
#include <kbool/include/kbool/booleng.h>
//#include <kbool/include/kbool/booleng.h>
#include <pad_shapes.h>
#include <wx/gdicmn.h> // for wxPoint definition
......@@ -210,42 +210,11 @@ public:
* Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s)
* @param aNewPolygonList = a std::vector<CPolyLine*> reference where to store new CPolyLine
* needed by the normalization
* @return the polygon count (always >= 1, becuse there is at lesat one polygon)
* @return the polygon count (always >= 1, because there is at least one polygon)
* There are new polygons only if the polygon count is > 1
*/
int NormalizeAreaOutlines( std::vector<CPolyLine*>* aNewPolygonList );
// KBOOL functions
/**
* Function AddPolygonsToBoolEng
* Add a CPolyLine to a kbool engine, preparing a boolean op between polygons
* @param aBooleng : pointer on a bool engine (handle a set of polygons)
* @param aGroup : group to fill (aGroup = GROUP_A or GROUP_B) operations are made between GROUP_A and GROUP_B
*/
int AddPolygonsToBoolEng( Bool_Engine* aBooleng, GroupType aGroup );
/**
* Function MakeKboolPoly
* fill a kbool engine with a closed polyline contour
* @return error: 0 if Ok, 1 if error
*/
int MakeKboolPoly();
/**
* Function NormalizeWithKbool
* Use the Kbool Library to clip contours: if outlines are crossing, the self-crossing polygon
* is converted to non self-crossing polygon by adding extra points at the crossing locations
* and reordering corners
* if more than one outside contour are found, extra CPolyLines will be created
* because copper areas have only one outside contour
* Therefore, if this results in new CPolyLines, return them as std::vector pa
* @param aExtraPolyList: pointer on a std::vector<CPolyLine*> to store extra CPolyLines
* (when after normalization, there is more than one polygon with holes)
* @return number of contours, or -1 if error
*/
int NormalizeWithKbool( std::vector<CPolyLine*>* aExtraPolyList );
// Bezier Support
void AppendBezier( int x1, int y1, int x2, int y2, int x3, int y3 );
void AppendBezier( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
......@@ -277,7 +246,7 @@ private:
// and the len of eacvh segment
// for DIAGONAL_FULL, the pitch is twice this value
int m_utility; // a flag used in some calculations
Bool_Engine* m_Kbool_Poly_Engine; // polygons set in kbool engine data
public:
std::vector <CPolyPt> m_CornersList; // array of points for corners
std::vector <CSegment> m_HatchLines; // hatch lines showing the polygon area
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
project(kbool)
subdirs(src)
This diff is collapsed.
This diff is collapsed.
/*! \file _lnk_itr.cpp
\author Probably Klaas Holwerda
Copyright: 2001-2004 (C) Probably Klaas Holwerda
Licence: see kboollicense.txt
RCS-ID: $Id: _lnk_itr.cpp,v 1.4 2009/02/06 21:33:03 titato Exp $
*/
#ifdef __UNIX__
#include "kbool/_lnk_itr.h"
#endif
//=======================================================================
// implementation class LinkBaseIter
//=======================================================================
template<class Type>
TDLI<Type>::TDLI( DL_List<void*>* newlist ): DL_Iter<void*>( newlist )
{}
template<class Type>
TDLI<Type>::TDLI( DL_Iter<void*>* otheriter ): DL_Iter<void*>( otheriter )
{}
template<class Type>
TDLI<Type>::TDLI(): DL_Iter<void*>()
{}
// destructor TDLI
template<class Type>
TDLI<Type>::~TDLI()
{}
template<class Type>
void TDLI<Type>::delete_all()
{
DL_Node<void*>* node;
Type* obj;
for ( int i = 0; i < NB; i++ )
{
node = HD;
HD = node->_next;
obj = ( Type* )( node->_item );
delete obj;
delete node;
}
NB = 0; //reset memory used (no lost pointers)
TL = RT;
_current = RT;
}
template<class Type>
void TDLI<Type>::foreach_f( void ( *fp ) ( Type* item ) )
{
DL_Iter<void*>::foreach_f( ( void ( * )( void* ) )fp ); //call fp for each item
}
template<class Type>
void TDLI<Type>::foreach_mf( void ( Type::*mfp ) () )
{
DL_Node<void*>* node = HD; //can be 0 if empty
Type* obj;
for( int i = 0; i < NB; i++ )
{
obj = ( Type* )( node->_item );
( obj->*mfp )();
node = node->_next;
}
}
template<class Type>
void TDLI<Type>::takeover( DL_List<void*>* otherlist )
{
DL_Iter<void*>::takeover( ( DL_List<void*>* ) otherlist );
}
template<class Type>
void TDLI<Type>::takeover( TDLI* otheriter )
{
DL_Iter<void*>::takeover( ( DL_Iter<void*>* ) otheriter );
}
template<class Type>
void TDLI<Type>::takeover( TDLI* otheriter, int maxcount )
{
DL_Iter<void*>::takeover( ( DL_Iter<void*>* ) otheriter, maxcount );
}
// is item element of the list?
template<class Type>
bool TDLI<Type>::has( Type* otheritem )
{
return DL_Iter<void*>::has( ( void* ) otheritem );
}
// goto to item
template<class Type>
bool TDLI<Type>::toitem( Type* item )
{
return DL_Iter<void*>::toitem( ( void* ) item );
}
// get current item
template<class Type>
Type* TDLI<Type>::item()
{
return ( Type* ) DL_Iter<void*>::item();
}
template<class Type>
void TDLI<Type>::insend( Type* newitem )
{
DL_Iter<void*>::insend( ( void* ) newitem );
}
template<class Type>
void TDLI<Type>::insbegin( Type* newitem )
{
DL_Iter<void*>::insbegin( ( void* ) newitem );
}
template<class Type>
void TDLI<Type>::insbefore( Type* newitem )
{
DL_Iter<void*>::insbefore( ( void* ) newitem );
}
template<class Type>
void TDLI<Type>::insafter( Type* newitem )
{
DL_Iter<void*>::insafter( ( void* ) newitem );
}
template<class Type>
void TDLI<Type>::insend_unsave( Type* newitem )
{
short int iterbackup = _list->_iterlevel;
_list->_iterlevel = 0;
DL_Iter<void*>::insend( ( void* ) newitem );
_list->_iterlevel = iterbackup;
}
template<class Type>
void TDLI<Type>::insbegin_unsave( Type* newitem )
{
short int iterbackup = _list->_iterlevel;
_list->_iterlevel = 0;
DL_Iter<void*>::insbegin( ( void* ) newitem );
_list->_iterlevel = iterbackup;
}
template<class Type>
void TDLI<Type>::insbefore_unsave( Type* newitem )
{
short int iterbackup = _list->_iterlevel;
_list->_iterlevel = 0;
DL_Iter<void*>::insbefore( ( void* ) newitem );
_list->_iterlevel = iterbackup;
}
template<class Type>
void TDLI<Type>::insafter_unsave( Type* newitem )
{
short int iterbackup = _list->_iterlevel;
_list->_iterlevel = 0;
DL_Iter<void*>::insafter( ( void* ) newitem );
_list->_iterlevel = iterbackup;
}
template<class Type>
void TDLI<Type>::mergesort( int ( *f )( Type* a, Type* b ) )
{
DL_Iter<void*>::mergesort( ( int ( * )( void*, void* ) ) f );
}
template<class Type>
int TDLI<Type>::cocktailsort( int ( *f )( Type* a, Type* b ), bool ( *f2 )( Type* c, Type* d ) )
{
return DL_Iter<void*>::cocktailsort( ( int ( * )( void*, void* ) ) f, ( bool( * )( void*, void* ) ) f2 );
}
template<class Type>
TDLISort<Type>::TDLISort( DL_List<void*>* lista, int ( *newfunc )( void*, void* ) )
: DL_SortIter<void*>( lista, newfunc )
{}
template<class Type>
TDLISort<Type>::~TDLISort()
{}
template<class Type>
void TDLISort<Type>::delete_all()
{
DL_Node<void*>* node;
Type* obj;
for ( int i = 0; i < NB; i++ )
{
node = HD;
HD = node->_next;
obj = ( Type* )( node->_item );
delete obj;
delete node;
}
NB = 0; //reset memory used (no lost pointers)
TL = RT;
_current = RT;
}
// is item element of the list?
template<class Type>
bool TDLISort<Type>::has( Type* otheritem )
{
return DL_Iter<void*>::has( ( void* ) otheritem );
}
// goto to item
template<class Type>
bool TDLISort<Type>::toitem( Type* item )
{
return DL_Iter<void*>::toitem( ( void* ) item );
}
// get current item
template<class Type>
Type* TDLISort<Type>::item()
{
return ( Type* ) DL_Iter<void*>::item();
}
template<class Type>
TDLIStack<Type>::TDLIStack( DL_List<void*>* newlist ): DL_StackIter<void*>( newlist )
{}
// destructor TDLI
template<class Type>
TDLIStack<Type>::~TDLIStack()
{}
// plaats nieuw item op stack
template<class Type>
void TDLIStack<Type>::push( Type* newitem )
{
DL_StackIter<void*>::push( ( Type* ) newitem );
}
// haal bovenste item van stack
template<class Type>
Type* TDLIStack<Type>::pop()
{
return ( Type* ) DL_StackIter<void*>::pop();
}
/*! \file _lnk_itr.h
\author Klaas Holwerda
Copyright: 2001-2004 (C) Klaas Holwerda
Licence: see kboollicense.txt
RCS-ID: $Id: _lnk_itr.h,v 1.4 2009/09/10 17:04:09 titato Exp $
*/
//! author="Klaas Holwerda"
//! version="1.0"
/*
* Definitions of classes, for list implementation
* template list and iterator for any list node type
*/
#ifndef _LinkBaseIter_H
#define _LinkBaseIter_H
//! headerfiles="_dl_itr.h stdlib.h misc.h gdsmes.h"
#include <stdlib.h>
#include "kbool/booleng.h"
#define SWAP(x,y,t)((t)=(x),(x)=(y),(y)=(t))
#include "kbool/_dl_itr.h"
//! codefiles="_dl_itr.cpp"
//! Template class TDLI
/*!
class for iterator on DL_List<void*> that is type casted version of DL_Iter
\sa DL_Iter for further documentation
*/
template<class Type> class TDLI : public DL_Iter<void*>
{
public:
//!constructor
/*!
\param list to iterate on.
*/
TDLI( DL_List<void*>* list );
//!constructor
TDLI( DL_Iter<void*>* otheriter );
//! nolist constructor
TDLI();
//! destructor
~TDLI();
//!call fp for each item
void foreach_f( void ( *fp ) ( Type* item ) );
//!call fp for each item
void foreach_mf( void ( Type::*fp ) () );
/* list mutations */
//! delete all items
void delete_all ();
//! insert at end
void insend ( Type* n );
//! insert at begin
void insbegin ( Type* n );
//! insert before current
void insbefore ( Type* n );
//! insert after current
void insafter ( Type* n );
//! insert at end unsave (works even if more then one iterator is on the list
//! the user must be sure not to delete/remove items where other iterators
//! are pointing to.
void insend_unsave ( Type* n );
//! insert at begin unsave (works even if more then one iterator is on the list
//! the user must be sure not to delete/remove items where other iterators
//! are pointing to.
void insbegin_unsave ( Type* n );
//! insert before iterator position unsave (works even if more then one iterator is on the list
//! the user must be sure not to delete/remove items where other iterators
//! are pointing to.
void insbefore_unsave ( Type* n );
//! insert after iterator position unsave (works even if more then one iterator is on the list
//! the user must be sure not to delete/remove items where other iterators
//! are pointing to.
void insafter_unsave ( Type* n );
//! \sa DL_Iter::takeover(DL_List< Dtype >* otherlist )
void takeover ( DL_List<void*>* otherlist );
//! \sa DL_Iter::takeover(DL_Iter* otheriter)
void takeover ( TDLI* otheriter );
//! \sa DL_Iter::takeover(DL_Iter* otheriter, int maxcount)
void takeover ( TDLI* otheriter, int maxcount );
//! \sa DL_Iter::has
bool has ( Type* );
//! \sa DL_Iter::toitem
bool toitem ( Type* );
//!get the item then iterator is pointing at
Type* item ();
//! \sa DL_Iter::mergesort
void mergesort ( int ( *f )( Type* a, Type* b ) );
//! \sa DL_Iter::cocktailsort
int cocktailsort( int ( * ) ( Type* a, Type* b ), bool ( * ) ( Type* c, Type* d ) = NULL );
};
//! Template class TDLIsort
/*!
// class for sort iterator on DL_List<void*> that is type casted version of DL_SortIter
// see also inhereted class DL_SortIter for further documentation
*/
template<class Type> class TDLISort : public DL_SortIter<void*>
{
public:
//!constructor givin a list and a sort function
TDLISort( DL_List<void*>* list, int ( *newfunc )( void*, void* ) );
~TDLISort();
//!delete all items from the list
void delete_all();
bool has ( Type* );
bool toitem ( Type* );
Type* item ();
};
//! Template class TDLIStack
/*!
class for iterator on DL_List<void*> that is type casted version of DL_StackIter
see also inhereted class DL_StackIter for further documentation
*/
template<class Type> class TDLIStack : public DL_StackIter<void*>
{
public:
//constructor givin a list
TDLIStack( DL_List<void*>* list );
~TDLIStack();
void push( Type* );
Type* pop();
};
#include"kbool/_lnk_itr.cpp"
#endif
This diff is collapsed.
/*! \file graph.h
\author Klaas Holwerda
Copyright: 2001-2004 (C) Klaas Holwerda
Licence: see kboollicense.txt
RCS-ID: $Id: graph.h,v 1.5 2009/09/10 17:04:09 titato Exp $
*/
/* @@(#) $Source: /cvsroot/wxart2d/wxArt2D/thirdparty/kbool/include/kbool/graph.h,v $ $Revision: 1.5 $ $Date: 2009/09/10 17:04:09 $ */
/*
Program GRAPH.H
Purpose Used to Intercect and other process functions
Last Update 03-04-1996
*/
#ifndef GRAPH_H
#define GRAPH_H
#include "kbool/booleng.h"
#include "kbool/_lnk_itr.h"
#include "kbool/link.h"
#include "kbool/line.h"
#include "kbool/scanbeam.h"
class kbNode;
class kbGraphList;
//! one graph containing links that cab be connected.
class A2DKBOOLDLLEXP kbGraph
{
protected:
Bool_Engine* _GC;
public:
kbGraph( Bool_Engine* GC );
kbGraph( kbLink*, Bool_Engine* GC );
kbGraph( kbGraph* other );
~kbGraph();
bool GetBin() { return _bin; };
void SetBin( bool b ) { _bin = b; };
void Prepare( int intersectionruns );
void RoundInt( B_INT grid );
void Rotate( bool plus90 );
//! adds a link to the linklist
void AddLink( kbNode *begin, kbNode *end );
//! adds a link to the linklist
void AddLink( kbLink *a_link );
bool AreZeroLines( B_INT Marge );
//! Delete parallel lines
void DeleteDoubles();
//! delete zerolines
bool DeleteZeroLines( B_INT Marge );
bool RemoveNullLinks();
//! Process found intersections
void ProcessCrossings();
//! set flags for operations based on group
void Set_Operation_Flags();
//! Left Right values
void Remove_IN_Links();
//! reset bin and mark flags in links.
void ResetBinMark();
// Remove unused links
void ReverseAllLinks();
//! Simplify the kbGraph
bool Simplify( B_INT Marge );
//! Takes over all links of the argument
bool Smoothen( B_INT Marge );
void TakeOver( kbGraph* );
//! function for maximum performance
//! Get the First link from the kbGraph
kbLink* GetFirstLink();
kbNode* GetTopNode();
void SetBeenHere( bool );
void Reset_flags();
//! Set the group of a kbGraph
void SetGroup( GroupType );
//! Set the number of the kbGraph
void SetNumber( int );
void Reset_Mark_and_Bin();
bool GetBeenHere();
int GetGraphNum();
int GetNumberOfLinks();
void Boolean( BOOL_OP operation, kbGraphList* Result );
void Correction( kbGraphList* Result, double factor );
void MakeRing( kbGraphList* Result, double factor );
void CreateRing( kbGraphList *ring, double factor );
void CreateRing_fast( kbGraphList *ring, double factor );
void CreateArc( kbNode* center, kbLine* incoming, kbNode* end, double radius, double aber );
void CreateArc( kbNode* center, kbNode* begin, kbNode* end, double radius, bool clock, double aber );
void MakeOneDirection();
void Make_Rounded_Shape( kbLink* a_link, double factor );
bool MakeClockWise();
bool writegraph( bool linked );
bool writeintersections();
void WriteKEY( Bool_Engine* GC, FILE* file = NULL );
void WriteGraphKEY( Bool_Engine* GC );
protected:
//! Extracts partical polygons from the graph
/*
Links are sorted in XY at beginpoint. Bin and mark flag are reset.
Next start to collect subparts from the graph, setting the links bin for all found parts.
The parts are searched starting at a topleft corner NON set bin flag link.
Found parts are numbered, to be easily split into to real sperate graphs by Split()
\param operation operation to collect for.
\param detecthole if you want holes detected, influences also way of extraction.
\param foundholes when holes are found this flag is set true, but only if detecthole is set true.
*/
void Extract_Simples( BOOL_OP operation, bool detecthole, bool& foundholes );
//! split graph into small graph, using the numbers in links.
void Split( kbGraphList* partlist );
//! Collect a graph by starting at argument link
/*
Called from Extract_Simples, and assumes sorted links with bin flag unset for non extarted piece
Collect graphs pieces from a total graph, by following links set to a given boolean operation.
\param current_node start node to collect
\param operation operation to collect for.
\param detecthole if you want holes detected, influences also way of extraction.
\param graphnumber number to be given to links in the extracted graph piece
\param foundholes when holes are found this flag is set true.
*/
void CollectGraph( kbNode *current_node, BOOL_OP operation, bool detecthole, int graphnumber, bool& foundholes );
void CollectGraphLast( kbNode *current_node, BOOL_OP operation, bool detecthole, int graphnumber, bool& foundholes );
//! find a link not bin in the top left corner ( links should be sorted already )
/*!
Last found position is used to find it quickly.
Used in ExtractSimples()
*/
kbNode* GetMostTopLeft( TDLI<kbLink>* _LI );
//! calculates crossing for all links in a graph, and add those as part of the graph.
/*
It is not just crossings calculation, snapping close nodes is part of it.
This is not done at maximum stability in economic time.
There are faster ways, but hardly ever they solve the problems, and they do not snap.
Here it is on purpose split into separate steps, to get a better result in snapping, and
to reach a better stability.
\param Marge nodes and lines closer to eachother then this, are merged.
*/
bool CalculateCrossings( B_INT Marge );
//! equal nodes in position are merged into one.
int Merge_NodeToNode( B_INT Marge );
//! basic scan algorithm with a sweeping beam are line.
/*!
\param scantype a different face in the algorithm.
\param holes to detect hole when needed.
*/
int ScanGraph2( SCANTYPE scantype, bool& holes );
//! links not used for a certain operation can be deleted, simplifying extraction
void DeleteNonCond( BOOL_OP operation );
//! links not used for a certain operation can be set bin, simplifying extraction
void HandleNonCond( BOOL_OP operation );
//! debug
bool checksort();
//! used in correction/offset algorithm
bool Small( B_INT howsmall );
bool _bin;
DL_List<void*>* _linklist;
};
#endif
/*! \file graphlst.h
\author Klaas Holwerda
Copyright: 2001-2004 (C) Klaas Holwerda
Licence: see kboollicense.txt
RCS-ID: $Id: graphlst.h,v 1.4 2009/09/10 17:04:09 titato Exp $
*/
/* @@(#) $Source: /cvsroot/wxart2d/wxArt2D/thirdparty/kbool/include/kbool/graphlst.h,v $ $Revision: 1.4 $ $Date: 2009/09/10 17:04:09 $ */
/*
Program GRAPHLST.H
Purpose Implements a list of graphs (header)
Last Update 11-03-1996
*/
#ifndef GRAPHLIST_H
#define GRAPHLIST_H
#include "kbool/booleng.h"
#include "kbool/_lnk_itr.h"
#include "kbool/graph.h"
class Debug_driver;
class A2DKBOOLDLLEXP kbGraphList: public DL_List<void*>
{
protected:
Bool_Engine* _GC;
public:
kbGraphList( Bool_Engine* GC );
kbGraphList( kbGraphList* other );
~kbGraphList();
void MakeOneGraph( kbGraph *total );
void Prepare( kbGraph *total );
void MakeRings();
void Correction();
void Simplify( double marge );
void Smoothen( double marge );
void Merge();
void Boolean( BOOL_OP operation, int intersectionRunsMax );
void WriteGraphs();
void WriteGraphsKEY( Bool_Engine* GC );
protected:
void Renumber();
void UnMarkAll();
};
#endif
#ifndef __A2D_KBOOLMOD_H__
#define __A2D_KBOOLMOD_H__
#include "kbool/booleng.h"
#include "kbool/graph.h"
#include "kbool/graphlst.h"
#include "kbool/line.h"
#include "kbool/link.h"
#include "kbool/lpoint.h"
#include "kbool/node.h"
#include "kbool/record.h"
#include "kbool/scanbeam.h"
#endif
This diff is collapsed.
This diff is collapsed.
/*! \file lpoint.h
\author Klaas Holwerda
Copyright: 2001-2004 (C) Klaas Holwerda
Licence: see kboollicense.txt
RCS-ID: $Id: lpoint.h,v 1.4 2009/09/10 17:04:09 titato Exp $
*/
/* @@(#) $Source: /cvsroot/wxart2d/wxArt2D/thirdparty/kbool/include/kbool/lpoint.h,v $ $Revision: 1.4 $ $Date: 2009/09/10 17:04:09 $ */
/*
Program LPOINT.H
Purpose Definition of GDSII pointtype structure
Last Update 12-12-1995
*/
#ifndef LPOINT_H
#define LPOINT_H
#include "kbool/booleng.h"
class A2DKBOOLDLLEXP kbLPoint
{
public:
kbLPoint();
kbLPoint( B_INT const , B_INT const );
kbLPoint( kbLPoint* const );
void Set( const B_INT, const B_INT );
void Set( const kbLPoint & );
kbLPoint GetPoint();
B_INT GetX();
B_INT GetY();
void SetX( B_INT );
void SetY( B_INT );
bool Equal( const kbLPoint a_point, B_INT Marge );
bool Equal( const B_INT, const B_INT , B_INT Marge );
bool ShorterThan( const kbLPoint a_point, B_INT marge );
bool ShorterThan( const B_INT X, const B_INT Y, B_INT );
kbLPoint &operator=( const kbLPoint & );
kbLPoint &operator+( const kbLPoint & );
kbLPoint &operator-( const kbLPoint & );
kbLPoint &operator*( int );
kbLPoint &operator/( int );
int operator==( const kbLPoint & ) const;
int operator!=( const kbLPoint & ) const;
protected:
B_INT _x;
B_INT _y;
};
#endif
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*! \file valuesvc.h
\author Probably Klaas Holwerda
Copyright: 2001-2004 (C) Probably Klaas Holwerda
Licence: see kboollicense.txt
RCS-ID: $Id: valuesvc.h,v 1.2 2009/02/06 21:33:03 titato Exp $
*/
Boolean: GDSII viewer/editor + (boolean) operations on sets of 2d polygons.
Boolean Web Site:
http://boolean.klaasholwerda.nl/bool.html
kbool is also used in wxArt2D
see www.wxart2d.org
the last version of kbool can be found on this site.
This diff is collapsed.
add_executable(boolonly boolonly.cpp)
if(WIN32)
add_definitions(-D_MSWVC_)
else(WIN32)
add_definitions(-D__UNIX__)
endif(WIN32)
include_directories(${kbool_SOURCE_DIR}/..)
target_link_libraries(boolonly kbool)
This diff is collapsed.
This diff is collapsed.
mondrian ICON "sample.ico"
#include "wx/msw/wx.rc"
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.
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