mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Add svgfill directly in IfcOpenShell git repo
This commit is contained in:
committed by
Thomas Krijnen
parent
658713f142
commit
9775dfc2c1
@@ -17,3 +17,6 @@
|
|||||||
[submodule "src/pyodide/demo-app/wheels"]
|
[submodule "src/pyodide/demo-app/wheels"]
|
||||||
path = src/pyodide/demo-app/wheels
|
path = src/pyodide/demo-app/wheels
|
||||||
url = https://github.com/IfcOpenShell/wasm-wheels
|
url = https://github.com/IfcOpenShell/wasm-wheels
|
||||||
|
[submodule "src/svgfill/3rdparty/svgpp"]
|
||||||
|
path = src/svgfill/3rdparty/svgpp
|
||||||
|
url = https://github.com/svgpp/svgpp
|
||||||
|
|||||||
+1
Submodule src/svgfill/3rdparty/svgpp added at 5f1870aa7b
@@ -0,0 +1,163 @@
|
|||||||
|
cmake_minimum_required (VERSION 3.10)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
project (svgfill)
|
||||||
|
|
||||||
|
cmake_policy(SET CMP0074 NEW) # find_package() uses <PackageName>_ROOT variables.
|
||||||
|
if (POLICY CMP0144)
|
||||||
|
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||||
|
endif()
|
||||||
|
if(POLICY CMP0167) # 3.30 find_package(Boost) to use BoostConfig instead of FindBoost.
|
||||||
|
cmake_policy(SET CMP0167 OLD)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
# Specify paths to install files
|
||||||
|
if(NOT BINDIR)
|
||||||
|
set(BINDIR bin)
|
||||||
|
endif()
|
||||||
|
if(NOT IS_ABSOLUTE ${BINDIR})
|
||||||
|
set(BINDIR ${CMAKE_INSTALL_BINDIR})
|
||||||
|
endif()
|
||||||
|
message(STATUS "BINDIR: ${BINDIR}")
|
||||||
|
|
||||||
|
if(NOT INCLUDEDIR)
|
||||||
|
set(INCLUDEDIR include)
|
||||||
|
endif()
|
||||||
|
if(NOT IS_ABSOLUTE ${INCLUDEDIR})
|
||||||
|
set(INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
endif()
|
||||||
|
message(STATUS "INCLUDEDIR: ${INCLUDEDIR}")
|
||||||
|
|
||||||
|
if(NOT LIBDIR)
|
||||||
|
set(LIBDIR lib)
|
||||||
|
endif()
|
||||||
|
if(NOT IS_ABSOLUTE ${LIBDIR})
|
||||||
|
set(LIBDIR ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
endif()
|
||||||
|
message(STATUS "LIBDIR: ${LIBDIR}")
|
||||||
|
|
||||||
|
set(CGAL_LIBRARY_NAMES libCGAL_Core libCGAL_ImageIO libCGAL)
|
||||||
|
|
||||||
|
if(NOT CGAL_INCLUDE_DIR)
|
||||||
|
find_package(CGAL REQUIRED)
|
||||||
|
if(NOT CGAL_DIR)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"CGAL_SUPPORT enabled, but CGAL_INCLUDE_DIR wasn't provided and CGAL package couldn't be found."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
message(STATUS "CGAL: found config at '${CGAL_DIR}'.")
|
||||||
|
set(CGAL_LIBRARIES CGAL::CGAL)
|
||||||
|
else()
|
||||||
|
set(CGAL_INCLUDE_DIR ${CGAL_INCLUDE_DIR} CACHE FILEPATH "CGAL header files")
|
||||||
|
message(STATUS "Looking for CGAL include files in: ${CGAL_INCLUDE_DIR}")
|
||||||
|
|
||||||
|
if(NOT "${CGAL_LIBRARY_DIR}" STREQUAL "")
|
||||||
|
set(CGAL_LIBRARY_DIR ${CGAL_LIBRARY_DIR} CACHE FILEPATH "CGAL library files")
|
||||||
|
message(STATUS "Looking for CGAL library files in: ${CGAL_LIBRARY_DIR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WASM_BUILD)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
|
||||||
|
set(CMAKE_FIND_ROOT_PATH "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_library(libCGAL NAMES CGAL PATHS ${CGAL_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||||
|
|
||||||
|
if(libCGAL)
|
||||||
|
message(STATUS "CGAL library files found")
|
||||||
|
foreach(lib ${CGAL_LIBRARY_NAMES})
|
||||||
|
string(REPLACE libCGAL "${lib}" lib_path "${libCGAL}")
|
||||||
|
list(APPEND CGAL_LIBRARIES "${lib_path}")
|
||||||
|
endforeach()
|
||||||
|
else()
|
||||||
|
if(NOT "${CGAL_LIBRARY_DIR}" STREQUAL "")
|
||||||
|
file(GLOB CGAL_LIBRARIES ${CGAL_LIBRARY_DIR}/CGAL*.lib)
|
||||||
|
list(LENGTH CGAL_LIBRARY_NAMES num_cgal_library_names)
|
||||||
|
list(LENGTH CGAL_LIBRARIES num_cgal_libraries)
|
||||||
|
link_directories("${CGAL_LIBRARY_DIR}")
|
||||||
|
if(NOT "${num_cgal_library_names}" STREQUAL "${num_cgal_libraries}")
|
||||||
|
message(FATAL_ERROR "Unable to find CGAL library files, aborting")
|
||||||
|
endif()
|
||||||
|
message(STATUS "CGAL library files found")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
find_library(libGMP NAMES gmp mpir PATHS ${GMP_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||||
|
find_library(libMPFR NAMES mpfr PATHS ${MPFR_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||||
|
if(NOT libGMP)
|
||||||
|
message(FATAL_ERROR "Unable to find GMP library files, aborting")
|
||||||
|
endif()
|
||||||
|
if(NOT libMPFR)
|
||||||
|
message(FATAL_ERROR "Unable to find MPFR library files, aborting")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND CGAL_LIBRARIES "${libMPFR}")
|
||||||
|
list(APPEND CGAL_LIBRARIES "${libGMP}")
|
||||||
|
endif(NOT CGAL_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if(WIN32 AND ("$ENV{CONDA_BUILD}" STREQUAL ""))
|
||||||
|
set(Boost_USE_STATIC_LIBS ON)
|
||||||
|
set(Boost_USE_MULTITHREADED ON)
|
||||||
|
if (USE_STATIC_MSVC_RUNTIME)
|
||||||
|
set(Boost_USE_STATIC_RUNTIME ON)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
# Disable Boost's autolinking as the libraries to be linked to are supplied
|
||||||
|
# already by CMake, and it's going to conflict if there are multiple, as is
|
||||||
|
# the case in conda-forge's libboost feedstock.
|
||||||
|
add_definitions(-DBOOST_ALL_NO_LIB)
|
||||||
|
if(WIN32)
|
||||||
|
# Necessary for boost version >= 1.67
|
||||||
|
set(BCRYPT_LIBRARIES "bcrypt.lib")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
add_definitions(-bigobj)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(Boost)
|
||||||
|
message(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
|
||||||
|
find_package(LibXml2 REQUIRED)
|
||||||
|
|
||||||
|
if(WASM_BUILD)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include_directories(${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR}
|
||||||
|
${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/svgpp/include
|
||||||
|
)
|
||||||
|
|
||||||
|
file(GLOB LIB_H_FILES src/*.h)
|
||||||
|
file(GLOB LIB_CPP_FILES src/svgfill.cpp src/arrange_polygons.cpp)
|
||||||
|
set(LIB_SRC_FILES ${LIB_H_FILES} ${LIB_CPP_FILES})
|
||||||
|
add_library(svgfill ${LIB_SRC_FILES})
|
||||||
|
if(LibXml2_DIR)
|
||||||
|
find_package(LibXml2 CONFIG REQUIRED)
|
||||||
|
target_compile_definitions(svgfill PRIVATE ${LIBXML2_DEFINITIONS})
|
||||||
|
endif()
|
||||||
|
target_link_libraries(svgfill ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES} ${CGAL_LIBRARIES})
|
||||||
|
|
||||||
|
add_executable(svgfill_exe src/main.cpp)
|
||||||
|
target_link_libraries(svgfill_exe svgfill)
|
||||||
|
set_property(TARGET svgfill_exe PROPERTY OUTPUT_NAME svgfill)
|
||||||
|
if(WIN32)
|
||||||
|
# both the library and the executable now result in a file with basename svgfill,
|
||||||
|
# on linux the the library is prefixed with lib as libsvgfill.a. Windows does not
|
||||||
|
# have this mechanism, so on windows the linker would be created an import library
|
||||||
|
# for the executable, also named svgfill.lib. This naming conflict results in:
|
||||||
|
# LINK : fatal error LNK1149: output filename matches input filename
|
||||||
|
# This flag tells the linker not to generate an import library and therefore no
|
||||||
|
# conflict occurs.
|
||||||
|
target_link_options(svgfill_exe PRIVATE "/NOIMPLIB")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS svgfill_exe DESTINATION ${BINDIR})
|
||||||
|
install(TARGETS svgfill DESTINATION ${LIBDIR})
|
||||||
|
install(FILES ${LIB_H_FILES} DESTINATION ${INCLUDEDIR})
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
svgfill
|
||||||
|
=======
|
||||||
|
|
||||||
|
An application to fill areas bounded by unconnected lines in SVG.
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
* [CGAL 2D Arrangements](https://doc.cgal.org/latest/Arrangement_on_surface_2/index.html) GPL
|
||||||
|
* [SVG++](http://svgpp.org/) Boost software license
|
||||||
|
|
||||||
|
Compilation
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Installation is shown based on the IfcOpenShell build script directory output:
|
||||||
|
|
||||||
|
git clone --recursive https://github.com/IfcOpenShell/svgfill
|
||||||
|
cd svgfill
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# windows
|
||||||
|
|
||||||
|
set IFCOPENSHELL_ROOT=..\path\to\ifcopenshell\directory\
|
||||||
|
cmake -DBOOST_ROOT=%IFCOPENSHELL_ROOT%\deps\boost_1_67_0 ^
|
||||||
|
-DBOOST_LIBRARYDIR=%IFCOPENSHELL_ROOT%\deps\boost_1_67_0\stage\vs2017-Win32\lib ^
|
||||||
|
-DLIBXML2_INCLUDE_DIR=%IFCOPENSHELL_ROOT%\deps\OpenCOLLADA\Externals\LibXML\include ^
|
||||||
|
-DLIBXML2_LIBRARIES=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\OpenCOLLADA\lib\opencollada\xml.lib ^
|
||||||
|
-DCGAL_INCLUDE_DIR=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\cgal\include ^
|
||||||
|
-DCGAL_LIBRARY_DIR=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\cgal\lib ^
|
||||||
|
-DGMP_INCLUDE_DIR=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\mpir ^
|
||||||
|
-DGMP_LIBRARY_DIR=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\mpir ^
|
||||||
|
-DMPFR_INCLUDE_DIR=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\mpfr ^
|
||||||
|
-DMPFR_LIBRARY_DIR=%IFCOPENSHELL_ROOT%\deps-vs2017-x86-installed\mpfr ^
|
||||||
|
..
|
||||||
|
|
||||||
|
# nix
|
||||||
|
|
||||||
|
IFCOPENSHELL_INSTALL=~/IfcOpenShell/build/$(uname -s)/$(uname -m)/install
|
||||||
|
cmake -DBOOST_ROOT=${IFCOPENSHELL_INSTALL}/boost-1.69.0 \
|
||||||
|
-DLIBXML2_INCLUDE_DIR=${IFCOPENSHELL_INSTALL}/libxml2-2.9.9/include/libxml2 \
|
||||||
|
-DLIBXML2_LIBRARIES=${IFCOPENSHELL_INSTALL}/libxml2-2.9.9/lib/libxml2.a \
|
||||||
|
-DCGAL_INCLUDE_DIR=${IFCOPENSHELL_INSTALL}/cgal-5.2/include \
|
||||||
|
-DCGAL_LIBRARY_DIR=${IFCOPENSHELL_INSTALL}/cgal-5.2/lib \
|
||||||
|
-DGMP_INCLUDE_DIR=${IFCOPENSHELL_INSTALL}/gmp-6.1.2/include \
|
||||||
|
-DGMP_LIBRARY_DIR=${IFCOPENSHELL_INSTALL}/gmp-6.1.2/lib \
|
||||||
|
-DMPFR_INCLUDE_DIR=${IFCOPENSHELL_INSTALL}/mpfr-3.1.5/include \
|
||||||
|
-DMPFR_LIBRARY_DIR=${IFCOPENSHELL_INSTALL}/mpfr-3.1.5/lib \
|
||||||
|
..
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
LGPL
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
in:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
out:
|
||||||
|
|
||||||
|

|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="210mm"
|
||||||
|
height="297mm"
|
||||||
|
viewBox="0 0 210 297"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
|
||||||
|
sodipodi:docname="rects.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.35"
|
||||||
|
inkscape:cx="400"
|
||||||
|
inkscape:cy="560"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1820"
|
||||||
|
inkscape:window-height="1051"
|
||||||
|
inkscape:window-x="-9"
|
||||||
|
inkscape:window-y="-9"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="opacity:0.99;fill:none;stroke-width:1;stroke:#000000;stroke-opacity:1"
|
||||||
|
id="rect842"
|
||||||
|
width="102.80952"
|
||||||
|
height="67.279762"
|
||||||
|
x="9.8273811"
|
||||||
|
y="43.089287" />
|
||||||
|
<rect
|
||||||
|
style="opacity:0.99;fill:none;stroke-width:1;stroke:#000000;stroke-opacity:1"
|
||||||
|
id="rect844"
|
||||||
|
width="120.19643"
|
||||||
|
height="92.226196"
|
||||||
|
x="83.910713"
|
||||||
|
y="86.178574" />
|
||||||
|
<rect
|
||||||
|
style="opacity:0.99;fill:none;stroke:#000000;stroke-width:1;stroke-opacity:1"
|
||||||
|
id="rect846"
|
||||||
|
width="33.261906"
|
||||||
|
height="213.93451"
|
||||||
|
x="130.0238"
|
||||||
|
y="8.3154764" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="M 27.970239,21.922619 186.72024,226.78571"
|
||||||
|
id="path848" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css"> <![CDATA[ path { stroke: #222222; fill: #444444; } ]]></style><g><path d="M44.3725 43.0893 L83.9107 94.1125 L83.9107 110.369 L9.82738 110.369 L9.82738 43.0893 Z" style = "fill: hsl(269,50%, 50%)"/><path d="M163.286 178.405 L163.286 86.1786 L204.107 86.1786 L204.107 178.405 Z" style = "fill: hsl(258,50%, 50%)"/><path d="M149.229 178.405 L130.024 153.62 L130.024 86.1786 L163.286 86.1786 L163.286 178.405 Z" style = "fill: hsl(4,50%, 50%)"/><path d="M130.024 86.1786 L130.024 8.31548 L163.286 8.31548 L163.286 86.1786 Z" style = "fill: hsl(295,50%, 50%)"/><path d="M96.5081 110.369 L130.024 153.62 L130.024 178.405 L83.9107 178.405 L83.9107 110.369 Z" style = "fill: hsl(157,50%, 50%)"/><path d="M83.9107 94.1125 L44.3725 43.0893 L112.637 43.0893 L112.637 86.1786 L83.9107 86.1786 L83.9107 86.1786 L83.9107 86.1786 L83.9107 86.1786 L83.9107 86.1786 Z" style = "fill: hsl(211,50%, 50%)"/><path d="M149.229 178.405 L163.286 196.544 L163.286 222.25 L130.024 222.25 L130.024 178.405 Z" style = "fill: hsl(258,50%, 50%)"/><path d="M130.024 153.62 L96.5081 110.369 L112.637 110.369 L112.637 110.369 L112.637 110.369 L112.637 110.369 L112.637 110.369 L112.637 86.1786 L130.024 86.1786 Z" style = "fill: hsl(295,50%, 50%)"/><path d="M96.5081 110.369 L83.9107 94.1125 L83.9107 86.1786 L112.637 86.1786 L112.637 110.369 Z" style = "fill: hsl(171,50%, 50%)"/><path d="M130.024 153.62 L149.229 178.405 L130.024 178.405 Z" style = "fill: hsl(127,50%, 50%)"/><path d="M163.286 196.544 L149.229 178.405 L163.286 178.405 Z" style = "fill: hsl(41,50%, 50%)"/><path d="M83.9107 94.1125 L96.5081 110.369 L83.9107 110.369 Z" style = "fill: hsl(29,50%, 50%)"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,509 @@
|
|||||||
|
#ifndef GRAPH_2D_H
|
||||||
|
#define GRAPH_2D_H
|
||||||
|
|
||||||
|
#ifdef SVGFILL_DEBUG
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename Kernel>
|
||||||
|
class Graph2D {
|
||||||
|
public:
|
||||||
|
typedef typename Kernel::Point_2 Point_2;
|
||||||
|
typedef std::pair<Point_2, Point_2> Edge;
|
||||||
|
|
||||||
|
Graph2D() {}
|
||||||
|
|
||||||
|
Graph2D(const std::map<Point_2, std::vector<Point_2>>& input_adjacency_list) {
|
||||||
|
for (const auto& kv : input_adjacency_list) {
|
||||||
|
const Point_2& u = kv.first;
|
||||||
|
const std::vector<Point_2>& neighbors = kv.second;
|
||||||
|
for (const Point_2& v : neighbors) {
|
||||||
|
if (u != v) { // no self-edges
|
||||||
|
adjacency_list[u].insert(v);
|
||||||
|
adjacency_list[v].insert(u); // Since it's undirected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_symmetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph2D(const CGAL::Polygon_2<Kernel>& loop) {
|
||||||
|
for (auto it = loop.vertices_begin(); it != loop.vertices_end(); ++it) {
|
||||||
|
auto next_it = std::next(it);
|
||||||
|
if (next_it == loop.vertices_end()) {
|
||||||
|
next_it = loop.vertices_begin();
|
||||||
|
}
|
||||||
|
adjacency_list[*it].insert(*next_it);
|
||||||
|
adjacency_list[*next_it].insert(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_loop() const {
|
||||||
|
if (adjacency_list.size() < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& p : adjacency_list) {
|
||||||
|
if (p.second.size() != 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto find(const Point_2& p) {
|
||||||
|
return adjacency_list.find(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional< CGAL::Segment_2<Kernel> > query(const Point_2& p, typename Kernel::FT eps) {
|
||||||
|
boost::optional< CGAL::Segment_2<Kernel> > closest_segment;
|
||||||
|
typename Kernel::FT closest_distance = std::numeric_limits<double>::infinity();
|
||||||
|
for (auto& p1 : adjacency_list) {
|
||||||
|
for (auto& p2 : p1.second) {
|
||||||
|
CGAL::Segment_2<Kernel>seg(p1.first, p2);
|
||||||
|
auto dist = CGAL::squared_distance(p, seg);
|
||||||
|
if (dist < eps * eps && dist < closest_distance) {
|
||||||
|
closest_distance = dist;
|
||||||
|
closest_segment = seg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closest_segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void refine(const CGAL::Segment_2<Kernel>& seg, const Point_2& p) {
|
||||||
|
remove_edge(seg.source(), seg.target());
|
||||||
|
insert(seg.source(), p);
|
||||||
|
insert(p, seg.target());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Point_2> shorted_path(const Point_2& start, const Point_2& goal) const {
|
||||||
|
auto& adj = adjacency_list;
|
||||||
|
if (adj.count(start) == 0 || adj.count(goal) == 0) return {};
|
||||||
|
|
||||||
|
std::map<Point_2, Point_2> predecessor;
|
||||||
|
std::queue<Point_2> q;
|
||||||
|
|
||||||
|
// seed BFS
|
||||||
|
predecessor[start] = start; // mark start as "seen"
|
||||||
|
q.push(start);
|
||||||
|
|
||||||
|
// BFS
|
||||||
|
bool found = false;
|
||||||
|
while (!q.empty() && !found) {
|
||||||
|
Point_2 u = q.front(); q.pop();
|
||||||
|
for (auto& v : adj.at(u)) {
|
||||||
|
// if v has no predecessor yet, it's unseen
|
||||||
|
if (!predecessor.count(v)) {
|
||||||
|
predecessor[v] = u;
|
||||||
|
q.push(v);
|
||||||
|
if (v == goal) { found = true; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) return {};
|
||||||
|
|
||||||
|
// reconstruct path
|
||||||
|
std::vector<Point_2> path;
|
||||||
|
for (Point_2 cur = goal; cur != start; cur = predecessor[cur])
|
||||||
|
path.push_back(cur);
|
||||||
|
path.push_back(start);
|
||||||
|
std::reverse(path.begin(), path.end());
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Point_2> shorted_path(const Point_2& start, const std::set<Point_2>& goal) const {
|
||||||
|
auto& adj = adjacency_list;
|
||||||
|
if (adj.count(start) == 0) return {};
|
||||||
|
|
||||||
|
std::map<Point_2, Point_2> predecessor;
|
||||||
|
std::queue<Point_2> q;
|
||||||
|
|
||||||
|
// seed BFS
|
||||||
|
predecessor[start] = start; // mark start as "seen"
|
||||||
|
q.push(start);
|
||||||
|
|
||||||
|
Point_2 used_goal;
|
||||||
|
|
||||||
|
// BFS
|
||||||
|
bool found = false;
|
||||||
|
while (!q.empty() && !found) {
|
||||||
|
Point_2 u = q.front(); q.pop();
|
||||||
|
for (auto& v : adj.at(u)) {
|
||||||
|
// if v has no predecessor yet, it's unseen
|
||||||
|
if (!predecessor.count(v)) {
|
||||||
|
predecessor[v] = u;
|
||||||
|
q.push(v);
|
||||||
|
if (goal.find(v) != goal.end()) {
|
||||||
|
found = true;
|
||||||
|
used_goal = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) return {};
|
||||||
|
|
||||||
|
// reconstruct path
|
||||||
|
std::vector<Point_2> path;
|
||||||
|
for (Point_2 cur = used_goal; cur != start; cur = predecessor[cur])
|
||||||
|
path.push_back(cur);
|
||||||
|
path.push_back(start);
|
||||||
|
std::reverse(path.begin(), path.end());
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void move(const Point_2& from, const Point_2& to) {
|
||||||
|
// @todo should check for intersections?
|
||||||
|
auto it = adjacency_list.find(from);
|
||||||
|
if (it != adjacency_list.end()) {
|
||||||
|
auto neighbours = it->second;
|
||||||
|
for (auto& n : neighbours) {
|
||||||
|
adjacency_list[n].erase(from);
|
||||||
|
adjacency_list[n].insert(to);
|
||||||
|
}
|
||||||
|
adjacency_list.erase(it);
|
||||||
|
adjacency_list.insert({ to, neighbours });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_valid() const {
|
||||||
|
typedef CGAL::Box_intersection_d::Box_with_handle_d<double, 2, size_t, CGAL::Box_intersection_d::ID_EXPLICIT> Box;
|
||||||
|
std::vector<Box> boxes;
|
||||||
|
std::vector<CGAL::Segment_2<Kernel>> segments;
|
||||||
|
for (const auto& p : adjacency_list) {
|
||||||
|
for (const auto& q : p.second) {
|
||||||
|
if (p.first < q) {
|
||||||
|
segments.emplace_back(p.first, q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto it = segments.begin(); it != segments.end(); ++it) {
|
||||||
|
boxes.emplace_back(it->bbox(), std::distance(segments.begin(), it));
|
||||||
|
}
|
||||||
|
bool any = false;
|
||||||
|
CGAL::box_self_intersection_d(boxes.begin(), boxes.end(), [this, &segments, &any](const Box& a, const Box& b) {
|
||||||
|
auto& seg1 = segments[a.handle()];
|
||||||
|
auto& seg2 = segments[b.handle()];
|
||||||
|
// Skip topologically connected segments
|
||||||
|
if (seg1.source() == seg2.source() || seg1.source() == seg2.target() || seg1.target() == seg2.source() || seg1.target() == seg2.target()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CGAL::do_intersect(seg1, seg2)) {
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminates a vertex with exactly two neighbors by connecting its neighbors
|
||||||
|
typename std::map<Point_2, std::set<Point_2>>::iterator eliminate_vertex(typename std::map<Point_2, std::set<Point_2>>::iterator it) {
|
||||||
|
if (it == adjacency_list.end()) {
|
||||||
|
// Vertex not found
|
||||||
|
return adjacency_list.end();
|
||||||
|
}
|
||||||
|
const std::set<Point_2>& neighbors = it->second;
|
||||||
|
if (neighbors.size() != 2) {
|
||||||
|
// Not exactly two neighbors
|
||||||
|
return adjacency_list.end();
|
||||||
|
}
|
||||||
|
// Get the two neighbors
|
||||||
|
auto neighbor_it = neighbors.begin();
|
||||||
|
auto u = *neighbor_it++;
|
||||||
|
auto w = *neighbor_it;
|
||||||
|
auto v = it->first;
|
||||||
|
|
||||||
|
// Remove all edge between v and u
|
||||||
|
adjacency_list[u].erase(v);
|
||||||
|
adjacency_list[v].erase(u);
|
||||||
|
|
||||||
|
// Remove all edge between v and w
|
||||||
|
adjacency_list[w].erase(v);
|
||||||
|
adjacency_list[v].erase(w);
|
||||||
|
|
||||||
|
// Add edge between u and w
|
||||||
|
adjacency_list[u].insert(w);
|
||||||
|
adjacency_list[w].insert(u);
|
||||||
|
|
||||||
|
// Remove v from adjacency_list
|
||||||
|
auto jt = adjacency_list.erase(it);
|
||||||
|
|
||||||
|
assert_symmetric();
|
||||||
|
|
||||||
|
return jt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph2D weld_vertices() const {
|
||||||
|
std::set<Point_2> points;
|
||||||
|
for (auto& p : adjacency_list) {
|
||||||
|
points.insert(p.first);
|
||||||
|
for (auto& q : p.second) {
|
||||||
|
points.insert(q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using It = typename std::set<Point_2>::iterator;
|
||||||
|
using Box = CGAL::Box_intersection_d::Box_with_handle_d<double, 2, Point_2 const*>;
|
||||||
|
|
||||||
|
std::vector<Box> boxes;
|
||||||
|
for (auto it = points.begin(); it != points.end(); ++it) {
|
||||||
|
constexpr double offset = 1.e-3;
|
||||||
|
auto b = it->bbox();
|
||||||
|
boxes.emplace_back(
|
||||||
|
CGAL::Bbox_2(b.xmin() - offset, b.ymin() - offset, b.xmax() + offset, b.ymax() + offset),
|
||||||
|
&*it
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<Point_2 const*, Point_2 const*>> overlaps;
|
||||||
|
|
||||||
|
CGAL::box_self_intersection_d(boxes.begin(), boxes.end(), [&overlaps](const Box& a, const Box& b) {
|
||||||
|
overlaps.emplace_back(a.handle(), b.handle());
|
||||||
|
});
|
||||||
|
|
||||||
|
std::map<Point_2, std::vector<Point_2>> input_adjacency_list;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::map<Point_2 const*, std::vector<Point_2 const*>> adj;
|
||||||
|
for (const auto& edge : overlaps) {
|
||||||
|
adj[edge.first].push_back(edge.second);
|
||||||
|
adj[edge.second].push_back(edge.first);
|
||||||
|
}
|
||||||
|
for (auto& p : points) {
|
||||||
|
adj[&p];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<Point_2 const*, bool> visited;
|
||||||
|
std::vector<std::vector<Point_2 const*>> connected_components;
|
||||||
|
|
||||||
|
for (auto& p : adj) {
|
||||||
|
if (!visited[p.first]) {
|
||||||
|
connected_components.emplace_back();
|
||||||
|
|
||||||
|
std::stack<Point_2 const*> stack;
|
||||||
|
stack.push(p.first);
|
||||||
|
visited[p.first] = true;
|
||||||
|
|
||||||
|
while (!stack.empty()) {
|
||||||
|
auto u = stack.top();
|
||||||
|
stack.pop();
|
||||||
|
connected_components.back().push_back(u);
|
||||||
|
|
||||||
|
for (auto& neighbor : adj[u]) {
|
||||||
|
if (!visited[neighbor]) {
|
||||||
|
visited[neighbor] = true;
|
||||||
|
stack.push(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<Point_2, Point_2> mapping;
|
||||||
|
|
||||||
|
for (auto& comp : connected_components) {
|
||||||
|
Point_2 avg(0, 0);
|
||||||
|
for (auto& c : comp) {
|
||||||
|
avg += (*c - CGAL::ORIGIN);
|
||||||
|
}
|
||||||
|
avg = CGAL::ORIGIN + ((avg - CGAL::ORIGIN) / comp.size());
|
||||||
|
|
||||||
|
for (auto& c : comp) {
|
||||||
|
mapping[*c] = avg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& comp : connected_components) {
|
||||||
|
for (auto& c : comp) {
|
||||||
|
const auto& C = mapping[*c];
|
||||||
|
for (auto& n : adjacency_list.find(*c)->second) {
|
||||||
|
const auto& N = mapping[n];
|
||||||
|
if (C != N) {
|
||||||
|
if (std::find(input_adjacency_list[C].begin(), input_adjacency_list[C].end(), N) == input_adjacency_list[C].end()) {
|
||||||
|
input_adjacency_list[C].push_back(N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Graph2D(input_adjacency_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assert_symmetric() {
|
||||||
|
#ifdef SVGFILL_DEBUG
|
||||||
|
#if 0
|
||||||
|
for (auto& p : adjacency_list) {
|
||||||
|
if (p.second.find(p.first) != p.second.end()) {
|
||||||
|
std::cout << "!! " << p.first << " self-edge" << std::endl;
|
||||||
|
throw std::runtime_error("self-edge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nlohmann::json json_obj = nlohmann::json::array();
|
||||||
|
for (auto& p : adjacency_list) {
|
||||||
|
if (p.second.size() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json pair = nlohmann::json::array();
|
||||||
|
nlohmann::json coord = nlohmann::json::array();
|
||||||
|
nlohmann::json values = nlohmann::json::array();
|
||||||
|
coord.push_back(
|
||||||
|
CGAL::to_double(p.first.x())
|
||||||
|
);
|
||||||
|
coord.push_back(
|
||||||
|
CGAL::to_double(p.first.y())
|
||||||
|
);
|
||||||
|
pair.push_back(coord);
|
||||||
|
for (auto& q : p.second) {
|
||||||
|
auto& r = adjacency_list[q];
|
||||||
|
for (auto& s : r) {
|
||||||
|
nlohmann::json coord = nlohmann::json::array();
|
||||||
|
coord.push_back(
|
||||||
|
CGAL::to_double(s.x())
|
||||||
|
);
|
||||||
|
coord.push_back(
|
||||||
|
CGAL::to_double(s.y())
|
||||||
|
);
|
||||||
|
values.push_back(coord);
|
||||||
|
}
|
||||||
|
if (r.find(p.first) == r.end()) {
|
||||||
|
for (auto& s : r) {
|
||||||
|
std::cout << " " << s << std::endl;
|
||||||
|
}
|
||||||
|
std::cout << "!! " << p.first << " not found in neighbours of " << q << std::endl;
|
||||||
|
throw std::runtime_error("internal error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pair.push_back(values);
|
||||||
|
json_obj.push_back(
|
||||||
|
pair
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fff = 0;
|
||||||
|
std::ofstream file("graph_" + std::to_string(fff++) + ".json");
|
||||||
|
file << json_obj.dump(2);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_edge(const Point_2& u, const Point_2& v) {
|
||||||
|
adjacency_list[u].erase(v);
|
||||||
|
adjacency_list[v].erase(u);
|
||||||
|
assert_symmetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(const Point_2& u, const Point_2& v) {
|
||||||
|
adjacency_list[u].insert(v);
|
||||||
|
adjacency_list[v].insert(u);
|
||||||
|
assert_symmetric();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterators over vertices
|
||||||
|
typedef typename std::map<Point_2, std::set<Point_2>>::const_iterator vertex_const_iterator;
|
||||||
|
typedef typename std::map<Point_2, std::set<Point_2>>::iterator vertex_iterator;
|
||||||
|
|
||||||
|
vertex_const_iterator vertices_begin() const {
|
||||||
|
return adjacency_list.cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_const_iterator vertices_end() const {
|
||||||
|
return adjacency_list.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_iterator vertices_begin() {
|
||||||
|
return adjacency_list.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_iterator vertices_end() {
|
||||||
|
return adjacency_list.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge iterator class
|
||||||
|
class EdgeIterator {
|
||||||
|
public:
|
||||||
|
typedef std::forward_iterator_tag iterator_category;
|
||||||
|
typedef Edge value_type;
|
||||||
|
typedef ptrdiff_t difference_type;
|
||||||
|
typedef const Edge* pointer;
|
||||||
|
typedef const Edge& reference;
|
||||||
|
|
||||||
|
EdgeIterator() : outer_it_(), inner_it_(), graph_(nullptr) {}
|
||||||
|
EdgeIterator(const Graph2D* graph, typename std::map<Point_2, std::set<Point_2>>::const_iterator outer_it)
|
||||||
|
: outer_it_(outer_it), graph_(graph) {
|
||||||
|
if (outer_it_ != graph_->adjacency_list.end()) {
|
||||||
|
inner_it_ = outer_it_->second.begin();
|
||||||
|
advance_to_valid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const {
|
||||||
|
current_edge_ = Edge(outer_it_->first, *inner_it_);
|
||||||
|
return current_edge_;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const {
|
||||||
|
current_edge_ = Edge(outer_it_->first, *inner_it_);
|
||||||
|
return ¤t_edge_;
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeIterator& operator++() {
|
||||||
|
++inner_it_;
|
||||||
|
advance_to_valid();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeIterator operator++(int) {
|
||||||
|
EdgeIterator tmp = *this;
|
||||||
|
++(*this);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const EdgeIterator& other) const {
|
||||||
|
return outer_it_ == other.outer_it_ && (outer_it_ == graph_->adjacency_list.end() || inner_it_ == other.inner_it_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const EdgeIterator& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void advance_to_valid() {
|
||||||
|
while (outer_it_ != graph_->adjacency_list.end()) {
|
||||||
|
while (inner_it_ != outer_it_->second.end() && *inner_it_ < outer_it_->first) {
|
||||||
|
++inner_it_;
|
||||||
|
}
|
||||||
|
if (inner_it_ != outer_it_->second.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++outer_it_;
|
||||||
|
if (outer_it_ != graph_->adjacency_list.end()) {
|
||||||
|
inner_it_ = outer_it_->second.begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutable Edge current_edge_;
|
||||||
|
typename std::map<Point_2, std::set<Point_2>>::const_iterator outer_it_;
|
||||||
|
typename std::set<Point_2>::const_iterator inner_it_;
|
||||||
|
const Graph2D* graph_;
|
||||||
|
};
|
||||||
|
|
||||||
|
EdgeIterator edges_begin() const {
|
||||||
|
return EdgeIterator(this, adjacency_list.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeIterator edges_end() const {
|
||||||
|
return EdgeIterator(this, adjacency_list.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<Point_2, std::set<Point_2>> adjacency_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* SVG fill *
|
||||||
|
* *
|
||||||
|
* Copyright(C) 2020 AECgeeks and Bimforce *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 3 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License *
|
||||||
|
* along with this program; if not, write to the Free Software Foundation, *
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "svgfill.h"
|
||||||
|
#include "progress.h"
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
bool valid_command_line = false;
|
||||||
|
bool random_color = false;
|
||||||
|
double eps = 1.e-5;
|
||||||
|
boost::optional<std::string> class_name;
|
||||||
|
|
||||||
|
std::vector<std::string> flags;
|
||||||
|
std::vector<std::string> args;
|
||||||
|
svgfill::solver s = svgfill::FILTERED_CARTESIAN_QUOTIENT;
|
||||||
|
std::map<std::string, svgfill::solver> solver_mapping {
|
||||||
|
{"cartesian_double", svgfill::CARTESIAN_DOUBLE},
|
||||||
|
{"cartesian_quotient", svgfill::CARTESIAN_QUOTIENT},
|
||||||
|
{"filtered_cartesian_quotient", svgfill::FILTERED_CARTESIAN_QUOTIENT},
|
||||||
|
{"exact_predicates", svgfill::EXACT_PREDICATES},
|
||||||
|
{"exact_constructions", svgfill::EXACT_CONSTRUCTIONS},
|
||||||
|
};
|
||||||
|
progress_bar::style progress_style = progress_bar::BAR;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string a = argv[i];
|
||||||
|
if (boost::starts_with(a, "-")) {
|
||||||
|
flags.push_back(a);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
args.push_back(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string fn, ofn;
|
||||||
|
|
||||||
|
if (args.size() == 2) {
|
||||||
|
fn = args[0];
|
||||||
|
ofn = args[1];
|
||||||
|
valid_command_line = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& f : flags) {
|
||||||
|
if (f == "--random-color") {
|
||||||
|
random_color = true;
|
||||||
|
}
|
||||||
|
else if (f == "-q") {
|
||||||
|
progress_style = progress_bar::DOTS;
|
||||||
|
}
|
||||||
|
else if (boost::starts_with(f, "--class=")) {
|
||||||
|
class_name = f.substr(strlen("--class="));
|
||||||
|
}
|
||||||
|
else if (boost::starts_with(f, "--solver=")) {
|
||||||
|
std::string solver_str = f.substr(strlen("--solver="));
|
||||||
|
auto it = solver_mapping.find(solver_str);
|
||||||
|
if (it == solver_mapping.end()) {
|
||||||
|
valid_command_line = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s = it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (boost::starts_with(f, "--eps=")) {
|
||||||
|
std::string eps_str = f.substr(strlen("--eps="));
|
||||||
|
eps = boost::lexical_cast<double>(eps_str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
valid_command_line = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid_command_line) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " [--random-color] [--class=...] <in.svg> <out.svg>" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<svgfill::line_segment_2>> segments;
|
||||||
|
std::vector<std::vector<svgfill::polygon_2>> polygons;
|
||||||
|
|
||||||
|
progress_bar p(std::cout, progress_style);
|
||||||
|
application_progress ap({1., 10., 1.}, p);
|
||||||
|
std::function<void(float)> pfn = [&ap](float f) { ap(f); };
|
||||||
|
|
||||||
|
std::ifstream fs(fn.c_str());
|
||||||
|
std::string data(std::istreambuf_iterator<char>{fs}, {});
|
||||||
|
fs.close();
|
||||||
|
|
||||||
|
if (!svgfill::svg_to_line_segments(data, class_name, segments)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ap.finished();
|
||||||
|
|
||||||
|
if (!svgfill::line_segments_to_polygons(s, eps, segments, polygons, pfn)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ap.finished();
|
||||||
|
|
||||||
|
std::ofstream ofs(ofn.c_str());
|
||||||
|
ofs << svgfill::polygons_to_svg(polygons, random_color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
/* *
|
||||||
|
/* Copyright 2021 AECgeeks *
|
||||||
|
/* *
|
||||||
|
/* Permission is hereby granted, free of charge, to any person obtaining a *
|
||||||
|
/* copy of this software and associated documentation files (the *
|
||||||
|
/* "Software"), to deal in the Software without restriction, including *
|
||||||
|
/* without limitation the rights to use, copy, modify, merge, publish, *
|
||||||
|
/* distribute, sublicense, and/or sell copies of the Software, and to *
|
||||||
|
/* permit persons to whom the Software is furnished to do so, subject to *
|
||||||
|
/* the following conditions: *
|
||||||
|
/* *
|
||||||
|
/* The above copyright notice and this permission notice shall be included *
|
||||||
|
/* in all copies or substantial portions of the Software. *
|
||||||
|
/* *
|
||||||
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
|
||||||
|
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
|
||||||
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
|
||||||
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY *
|
||||||
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, *
|
||||||
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE *
|
||||||
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||||
|
/* *
|
||||||
|
/***************************************************************************/
|
||||||
|
|
||||||
|
// A trivial C++ progress bar
|
||||||
|
|
||||||
|
#ifndef PROGRESS_H
|
||||||
|
#define PROGRESS_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <ostream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
class progress_bar {
|
||||||
|
std::ostream& s_;
|
||||||
|
float max_;
|
||||||
|
size_t width_;
|
||||||
|
size_t* last_emitted_p_ = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum style {
|
||||||
|
BAR, DOTS
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
style style_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
progress_bar(std::ostream& s = std::cerr, style st = BAR, float max = 1., size_t width = 50)
|
||||||
|
: s_(s)
|
||||||
|
, max_(max)
|
||||||
|
, width_(st == BAR ? width : 100U)
|
||||||
|
, style_(st)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void operator()(size_t p) {
|
||||||
|
if (last_emitted_p_ && p <= *last_emitted_p_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p > width_ ? width_ : p;
|
||||||
|
if (style_ == BAR) {
|
||||||
|
s_ << "\r[" + std::string(p, '#') + std::string(width_ - p, ' ') + "]" << std::flush;
|
||||||
|
} else {
|
||||||
|
s_ << std::string(p - (last_emitted_p_ ? *last_emitted_p_ : 0U), '.') << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_emitted_p_) {
|
||||||
|
*last_emitted_p_ = p;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
last_emitted_p_ = new size_t(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(float p) {
|
||||||
|
(*this)((size_t) (p / max_ * width_));
|
||||||
|
}
|
||||||
|
|
||||||
|
~progress_bar() {
|
||||||
|
delete last_emitted_p_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class application_progress {
|
||||||
|
std::vector<float> estimates_;
|
||||||
|
size_t phase_ = 0;
|
||||||
|
std::function<void(float)> callback_;
|
||||||
|
float total_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void operator()(float p) {
|
||||||
|
auto progress = std::accumulate(estimates_.begin(), estimates_.begin() + phase_, 0.f);
|
||||||
|
progress += p * estimates_[phase_];
|
||||||
|
callback_(progress / total_);
|
||||||
|
}
|
||||||
|
|
||||||
|
application_progress(const std::vector<float>& estimates, const std::function<void(float)>& callback)
|
||||||
|
: estimates_(estimates)
|
||||||
|
, callback_(callback)
|
||||||
|
{
|
||||||
|
total_ = std::accumulate(estimates_.begin(), estimates_.end(), 0.f);
|
||||||
|
(*this)(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finished() {
|
||||||
|
++phase_;
|
||||||
|
(*this)(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
~application_progress() {
|
||||||
|
phase_ = estimates_.size() - 2;
|
||||||
|
finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,633 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* SVG fill *
|
||||||
|
* *
|
||||||
|
* Copyright(C) 2020 AECgeeks and Bimforce *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 3 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License *
|
||||||
|
* along with this program; if not, write to the Free Software Foundation, *
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "svgfill.h"
|
||||||
|
|
||||||
|
#include <libxml/parser.h>
|
||||||
|
|
||||||
|
#include <svgpp/svgpp.hpp>
|
||||||
|
#include <svgpp/policy/xml/libxml2.hpp>
|
||||||
|
|
||||||
|
#include <CGAL/Cartesian.h>
|
||||||
|
#include <CGAL/MP_Float.h>
|
||||||
|
#include <CGAL/Quotient.h>
|
||||||
|
#include <CGAL/Arr_segment_traits_2.h>
|
||||||
|
#include <CGAL/Arrangement_2.h>
|
||||||
|
#include <CGAL/Polygon_2.h>
|
||||||
|
#include <CGAL/Polygon_with_holes_2.h>
|
||||||
|
#include <CGAL/Polygon_triangulation_decomposition_2.h>
|
||||||
|
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
|
||||||
|
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
using namespace svgpp;
|
||||||
|
|
||||||
|
class Context
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
size_t depth_ = 0;
|
||||||
|
int enabled_at_ = -1;
|
||||||
|
svgfill::point_2 start_, xy_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
boost::optional<std::string> class_name;
|
||||||
|
std::vector<std::vector<svgfill::line_segment_2>> segments;
|
||||||
|
|
||||||
|
void on_enter_element(tag::element::any)
|
||||||
|
{
|
||||||
|
++depth_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_enter_element(tag::element::g)
|
||||||
|
{
|
||||||
|
++depth_;
|
||||||
|
if (enabled_at_ == -1 && !class_name.is_initialized()) {
|
||||||
|
enabled_at_ = depth_;
|
||||||
|
segments.emplace_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_exit_element()
|
||||||
|
{
|
||||||
|
if (depth_-- == enabled_at_) {
|
||||||
|
enabled_at_ = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Str>
|
||||||
|
void set(tag::attribute::id, Str const & s) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Str>
|
||||||
|
void set(tag::attribute::class_, Str const & s) {
|
||||||
|
if (enabled_at_ == -1 && class_name.is_initialized() && std::string(s.begin(), s.size()).find(*class_name) != std::string::npos) {
|
||||||
|
enabled_at_ = depth_;
|
||||||
|
segments.emplace_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void transform_matrix(const boost::array<double, 6> & matrix)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void path_move_to(double x, double y, tag::coordinate::absolute)
|
||||||
|
{
|
||||||
|
start_ = xy_ = { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
void path_line_to(double x, double y, tag::coordinate::absolute)
|
||||||
|
{
|
||||||
|
if (enabled_at_ != -1) {
|
||||||
|
svgfill::point_2 next{ x, y };
|
||||||
|
segments.back().push_back({ xy_, next });
|
||||||
|
xy_ = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void path_cubic_bezier_to(
|
||||||
|
double x1, double y1,
|
||||||
|
double x2, double y2,
|
||||||
|
double x, double y,
|
||||||
|
tag::coordinate::absolute) {}
|
||||||
|
|
||||||
|
void path_quadratic_bezier_to(
|
||||||
|
double x1, double y1,
|
||||||
|
double x, double y,
|
||||||
|
tag::coordinate::absolute) {}
|
||||||
|
|
||||||
|
void path_elliptical_arc_to(
|
||||||
|
double rx, double ry, double x_axis_rotation,
|
||||||
|
bool large_arc_flag, bool sweep_flag,
|
||||||
|
double x, double y,
|
||||||
|
tag::coordinate::absolute) {}
|
||||||
|
|
||||||
|
void path_close_subpath() {
|
||||||
|
if (enabled_at_ != -1) {
|
||||||
|
segments.back().push_back({ xy_, start_ });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void path_exit() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef
|
||||||
|
boost::mpl::set<
|
||||||
|
// SVG Structural Elements
|
||||||
|
tag::element::svg,
|
||||||
|
tag::element::g,
|
||||||
|
// SVG Shape Elements
|
||||||
|
tag::element::circle,
|
||||||
|
tag::element::ellipse,
|
||||||
|
tag::element::line,
|
||||||
|
tag::element::path,
|
||||||
|
tag::element::polygon,
|
||||||
|
tag::element::polyline,
|
||||||
|
tag::element::rect
|
||||||
|
>::type processed_elements_t;
|
||||||
|
|
||||||
|
// This cryptic code just merges predefined sequences traits::shapes_attributes_by_element
|
||||||
|
// and traits::viewport_attributes with tag::attribute::transform and tag::attribute::xlink::href
|
||||||
|
// attributes into single MPL sequence
|
||||||
|
typedef
|
||||||
|
boost::mpl::fold<
|
||||||
|
boost::mpl::protect<
|
||||||
|
traits::shapes_attributes_by_element
|
||||||
|
>,
|
||||||
|
boost::mpl::set<
|
||||||
|
tag::attribute::id,
|
||||||
|
tag::attribute::class_
|
||||||
|
>::type,
|
||||||
|
boost::mpl::insert<boost::mpl::_1, boost::mpl::_2>
|
||||||
|
>::type processed_attributes_t;
|
||||||
|
|
||||||
|
bool svgfill::svg_to_line_segments(const std::string& data, const boost::optional<std::string>& class_name, std::vector<std::vector<line_segment_2>>& segments)
|
||||||
|
{
|
||||||
|
Context context;
|
||||||
|
context.class_name = class_name;
|
||||||
|
|
||||||
|
xmlDoc* doc = xmlReadMemory(data.c_str(), data.size(), nullptr, nullptr, 0);
|
||||||
|
xmlNode* elem = xmlDocGetRootElement(doc);
|
||||||
|
|
||||||
|
try {
|
||||||
|
document_traversal<
|
||||||
|
processed_elements<processed_elements_t>,
|
||||||
|
processed_attributes<processed_attributes_t>
|
||||||
|
>::load_document(elem, context);
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments = context.segments;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool svgfill::line_segments_to_polygons(solver s, double eps, const std::vector<std::vector<line_segment_2>>& segments, std::vector<std::vector<polygon_2>>& polygons)
|
||||||
|
{
|
||||||
|
std::function<void(float)> fn = [](float f) {};
|
||||||
|
return line_segments_to_polygons(s, eps, segments, polygons, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool svgfill::svg_to_polygons(const std::string& data, const boost::optional<std::string>& class_name, std::vector<polygon_2>& polygons) {
|
||||||
|
Context context;
|
||||||
|
context.class_name = class_name;
|
||||||
|
xmlDoc* doc = xmlReadMemory(data.c_str(), data.size(), nullptr, nullptr, 0);
|
||||||
|
xmlNode* elem = xmlDocGetRootElement(doc);
|
||||||
|
|
||||||
|
try {
|
||||||
|
document_traversal<
|
||||||
|
processed_elements<processed_elements_t>,
|
||||||
|
processed_attributes<processed_attributes_t>
|
||||||
|
>::load_document(elem, context);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::function<void(float)> fn = [](float f) {};
|
||||||
|
std::vector<std::vector<polygon_2>> ps;
|
||||||
|
if (!line_segments_to_polygons(svgfill::EXACT_PREDICATES, 0., context.segments, ps, fn)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ps.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto& p : ps) {
|
||||||
|
polygons.insert(polygons.end(), p.begin(), p.end());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Kernel>
|
||||||
|
class cgal_arrangement : public svgfill::abstract_arrangement {
|
||||||
|
typedef CGAL::Arr_segment_traits_2<Kernel> Traits_2;
|
||||||
|
typedef typename Traits_2::Point_2 Point_2;
|
||||||
|
typedef typename Traits_2::X_monotone_curve_2 Segment_2;
|
||||||
|
typedef CGAL::Arrangement_2<Traits_2> Arrangement_2;
|
||||||
|
typedef CGAL::Polygon_2<Kernel> Polygon_2;
|
||||||
|
typedef CGAL::Polygon_with_holes_2<Kernel> Polygon_wh_2;
|
||||||
|
typedef typename Arrangement_2::Inner_ccb_const_iterator Inner_ccb_const_iterator;
|
||||||
|
typedef typename Arrangement_2::Ccb_halfedge_const_circulator Ccb_halfedge_const_circulator;
|
||||||
|
typedef typename Arrangement_2::Halfedge_handle Halfedge_handle;
|
||||||
|
typedef typename Arrangement_2::Face_handle Face_handle;
|
||||||
|
|
||||||
|
Polygon_2 circ_to_poly(Ccb_halfedge_const_circulator circ)
|
||||||
|
{
|
||||||
|
Polygon_2 poly;
|
||||||
|
auto curr = circ;
|
||||||
|
do {
|
||||||
|
if (poly.size() == 0 || (*(poly.end() - 1)) != curr->source()->point()) {
|
||||||
|
poly.push_back(curr->source()->point());
|
||||||
|
}
|
||||||
|
} while (++curr != circ);
|
||||||
|
return poly;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGAL::Triangle_2<Kernel> poly_to_triangle(const Polygon_2& poly)
|
||||||
|
{
|
||||||
|
auto n = std::distance(poly.vertices_begin(), poly.vertices_end());
|
||||||
|
if (n != 3) {
|
||||||
|
throw std::runtime_error("Unexpected number of points in polygon");
|
||||||
|
}
|
||||||
|
auto p = *poly.vertices_begin();
|
||||||
|
auto q = *next(poly.vertices_begin(), 1);
|
||||||
|
auto r = *next(poly.vertices_begin(), 2);
|
||||||
|
return CGAL::Triangle_2<Kernel>(p, q, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Polygon_wh_2 circ_to_poly(Ccb_halfedge_const_circulator circ, Inner_ccb_const_iterator a, Inner_ccb_const_iterator b)
|
||||||
|
{
|
||||||
|
Polygon_wh_2 poly(circ_to_poly(circ));
|
||||||
|
for (auto it = a; it != b; ++it) {
|
||||||
|
poly.add_hole(circ_to_poly(*it));
|
||||||
|
}
|
||||||
|
return poly;
|
||||||
|
}
|
||||||
|
|
||||||
|
svgfill::point_2 create_point(const Point_2& pt)
|
||||||
|
{
|
||||||
|
return svgfill::point_2{
|
||||||
|
CGAL::to_double(pt.cartesian(0)),
|
||||||
|
CGAL::to_double(pt.cartesian(1)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_point_inside(const Polygon_wh_2& inpoly, svgfill::polygon_2& outpoly)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
std::cout << std::endl;
|
||||||
|
for (auto& p : inpoly.outer_boundary()) {
|
||||||
|
std::cout << " " << p;
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
*/
|
||||||
|
// create Delaunay triangulation and return the centroid of the largest triangle.
|
||||||
|
CGAL::Polygon_triangulation_decomposition_2<Kernel> decompositor;
|
||||||
|
std::list<Polygon_2> decom_polies;
|
||||||
|
decompositor(inpoly, std::back_inserter(decom_polies));
|
||||||
|
decom_polies.sort([](const Polygon_2& a, const Polygon_2& b) {
|
||||||
|
return a.area() > b.area();
|
||||||
|
});
|
||||||
|
if (!decom_polies.empty()) {
|
||||||
|
const Polygon_2& largest = decom_polies.front();
|
||||||
|
auto triangle = poly_to_triangle(largest);
|
||||||
|
/*
|
||||||
|
for (auto& p : decom_polies) {
|
||||||
|
std::cout << "a " << CGAL::to_double(poly_to_triangle(largest).area()) << std::endl;
|
||||||
|
}
|
||||||
|
std::cout << "triangle area " << CGAL::to_double(triangle.area()) << std::endl;
|
||||||
|
*/
|
||||||
|
outpoly.point_inside = create_point(CGAL::centroid(triangle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrangement_2 arr;
|
||||||
|
float total, i;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool operator()(double eps, const std::vector<svgfill::line_segment_2>& segments, std::function<void(float)>& progress) {
|
||||||
|
i = 0;
|
||||||
|
total = segments.size() + segments.size() / 2;
|
||||||
|
|
||||||
|
for (auto& l : segments) {
|
||||||
|
Point_2 a(l[0][0], l[0][1]);
|
||||||
|
Point_2 b(l[1][0], l[1][1]);
|
||||||
|
if (a == b) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (eps != 0.) {
|
||||||
|
auto ab = b - a;
|
||||||
|
ab /= std::sqrt(CGAL::to_double(ab.squared_length()));
|
||||||
|
// This appears to work better generally, slightly nudge the
|
||||||
|
// end points to make sure segments intersect.
|
||||||
|
a -= ab * eps;
|
||||||
|
b += ab * eps;
|
||||||
|
}
|
||||||
|
Segment_2 seg(a, b);
|
||||||
|
CGAL::insert(arr, seg);
|
||||||
|
|
||||||
|
if (progress) {
|
||||||
|
progress(i++ / total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_duplicates(svgfill::loop_2& l) {
|
||||||
|
auto norm2 = [](auto& a, auto& b) {
|
||||||
|
auto dx = a[0] - b[0];
|
||||||
|
auto dy = a[1] - b[1];
|
||||||
|
return std::sqrt(dx * dx + dy * dy);
|
||||||
|
};
|
||||||
|
|
||||||
|
while (l.size() > 1 && l.front() == l.back()) {
|
||||||
|
l.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l.size() > 1) {
|
||||||
|
auto it = l.begin();
|
||||||
|
auto next_it = std::next(it);
|
||||||
|
|
||||||
|
while (next_it != l.end()) {
|
||||||
|
if (norm2(*it, *next_it) < 1.e-8) {
|
||||||
|
next_it = l.erase(next_it);
|
||||||
|
// 'it' remains the same; 'next_it' now points to the next element
|
||||||
|
} else {
|
||||||
|
// Move both iterators forward
|
||||||
|
++it;
|
||||||
|
++next_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write(std::vector<svgfill::polygon_2>& polygons, std::function<void(float)>& progress) {
|
||||||
|
std::vector<Polygon_wh_2> ps;
|
||||||
|
ps.reserve(std::distance(arr.faces_begin(), arr.faces_end()));
|
||||||
|
|
||||||
|
for (auto it = arr.faces_begin(); it != arr.faces_end(); ++it) {
|
||||||
|
const auto& f = *it;
|
||||||
|
if (!f.is_unbounded()) {
|
||||||
|
ps.push_back(circ_to_poly(
|
||||||
|
f.outer_ccb(),
|
||||||
|
f.inner_ccbs_begin(),
|
||||||
|
f.inner_ccbs_end()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress) {
|
||||||
|
progress(i++ / total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort polygons (only taking into account outer boundary) to have inner
|
||||||
|
// loops drawn over outer boundaries. In SVG draw order is defined by
|
||||||
|
// position in the tree.
|
||||||
|
// @nb we do now add the inner boundaries to the path as well.
|
||||||
|
/*
|
||||||
|
std::sort(ps.begin(), ps.end(), [](const Polygon_wh_2& a, const Polygon_wh_2& b) {
|
||||||
|
return a.outer_boundary().area() > b.outer_boundary().area();
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
polygons.reserve(ps.size());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::transform(ps.begin(), ps.end(), std::back_inserter(polygons), [this, &progress](const Polygon_wh_2& p) {
|
||||||
|
svgfill::polygon_2 p2;
|
||||||
|
std::transform(p.outer_boundary().vertices_begin(), p.outer_boundary().vertices_end(), std::back_inserter(p2.boundary), [this](const Point_2& pt) {
|
||||||
|
return create_point(pt);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
static int NN = 0;
|
||||||
|
std::cout << NN++ << std::endl;
|
||||||
|
for (auto& p : p2.boundary) {
|
||||||
|
std::cout << std::setprecision(20) << p[0] << "," << p[1] << " ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// duplicates need to be removed after conversion from epeck to double
|
||||||
|
remove_duplicates(p2.boundary);
|
||||||
|
|
||||||
|
/*
|
||||||
|
std::cout << "> " << std::endl;
|
||||||
|
for (auto& p : p2.boundary) {
|
||||||
|
std::cout << std::setprecision(20) << p[0] << "," << p[1] << " ";
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::transform(p.holes_begin(), p.holes_end(), std::back_inserter(p2.inner_boundaries), [this](const Polygon_2& poly) {
|
||||||
|
svgfill::loop_2 lp;
|
||||||
|
std::transform(poly.vertices_begin(), poly.vertices_end(), std::back_inserter(lp), [this](const Point_2& pt) {
|
||||||
|
return create_point(pt);
|
||||||
|
});
|
||||||
|
remove_duplicates(lp);
|
||||||
|
return lp;
|
||||||
|
});
|
||||||
|
set_point_inside(p, p2);
|
||||||
|
|
||||||
|
if (progress) {
|
||||||
|
progress(i++ / total);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p2;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> get_face_pairs() {
|
||||||
|
std::vector<int> ps;
|
||||||
|
ps.reserve(arr.number_of_edges() * 2);
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
std::map<Face_handle, size_t> face_to_bounded_index;
|
||||||
|
for (auto it = arr.faces_begin(); it != arr.faces_end(); ++it) {
|
||||||
|
if (!it->is_unbounded()) {
|
||||||
|
face_to_bounded_index[it] = face_to_bounded_index.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = arr.edges_begin(); it != arr.edges_end(); ++it, ++n) {
|
||||||
|
auto v0 = it->source()->point();
|
||||||
|
double v0x = CGAL::to_double(v0.cartesian(0));
|
||||||
|
double v0y = CGAL::to_double(v0.cartesian(1));
|
||||||
|
auto v1 = it->target()->point();
|
||||||
|
double v1x = CGAL::to_double(v1.cartesian(0));
|
||||||
|
double v1y = CGAL::to_double(v1.cartesian(1));
|
||||||
|
double l = std::sqrt((v1x - v0x) * (v1x - v0x) + (v1y - v0y) * (v1y - v0y));
|
||||||
|
// std::cout << n << " l " << l << std::endl;
|
||||||
|
bool emitted = false;
|
||||||
|
if (l > 1.) {
|
||||||
|
// std::cout << std::to_string(n) << " " << v0x << " " << v0y << std::endl;
|
||||||
|
// std::cout << std::string(std::to_string(n).size(), ' ') << " " << v1x << " " << v1y << std::endl;
|
||||||
|
|
||||||
|
auto afit = face_to_bounded_index.find(it->face());
|
||||||
|
auto bfit = face_to_bounded_index.find(it->twin()->face());
|
||||||
|
|
||||||
|
if (afit != face_to_bounded_index.end() && bfit != face_to_bounded_index.end()) {
|
||||||
|
ps.push_back(afit->second);
|
||||||
|
ps.push_back(bfit->second);
|
||||||
|
emitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emitted) {
|
||||||
|
ps.push_back(-1);
|
||||||
|
ps.push_back(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
void merge(const std::vector<int>& edge_indices) {
|
||||||
|
if (edge_indices.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Halfedge_handle> to_remove;
|
||||||
|
auto eit = edge_indices.begin();
|
||||||
|
size_t n = 0;
|
||||||
|
for (auto it = arr.edges_begin(); it != arr.edges_end(); ++it, ++n) {
|
||||||
|
if (n == *eit) {
|
||||||
|
++eit;
|
||||||
|
to_remove.push_back(it);
|
||||||
|
if (eit == edge_indices.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& h : to_remove) {
|
||||||
|
/*
|
||||||
|
auto v0 = h->source()->point();
|
||||||
|
std::cout << CGAL::to_double(v0.cartesian(0)) << " " << CGAL::to_double(v0.cartesian(1)) << std::endl;
|
||||||
|
auto v1 = h->target()->point();
|
||||||
|
std::cout << CGAL::to_double(v1.cartesian(0)) << " " << CGAL::to_double(v1.cartesian(1)) << std::endl << std::endl;
|
||||||
|
*/
|
||||||
|
arr.remove_edge(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t num_edges() {
|
||||||
|
return arr.number_of_edges();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t num_faces() {
|
||||||
|
return arr.number_of_faces();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool svgfill::line_segments_to_polygons(solver s, double eps, const std::vector<std::vector<line_segment_2>>& segment_groups, std::vector<std::vector<polygon_2>>& polygons, std::function<void(float)>& progress)
|
||||||
|
{
|
||||||
|
bool b = false;
|
||||||
|
for (auto& segments : segment_groups) {
|
||||||
|
context ctx(s, eps, progress);
|
||||||
|
ctx.add(segments);
|
||||||
|
if (ctx.build()) {
|
||||||
|
ctx.write(polygons);
|
||||||
|
b = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string format_pt(const svgfill::point_2& p) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::setprecision(std::numeric_limits<double>::max_digits10);
|
||||||
|
oss << p[0] << "," << p[1];
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string format_poly(const svgfill::loop_2& p) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
for (auto it = p.begin(); it != p.end(); ++it) {
|
||||||
|
oss << ((it == p.begin()) ? "M" : " L");
|
||||||
|
oss << format_pt(*it);
|
||||||
|
}
|
||||||
|
oss << " Z";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string svgfill::polygons_to_svg(const std::vector<std::vector<polygon_2>>& polygons, bool random_color) {
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 mt(rd());
|
||||||
|
std::uniform_int_distribution<size_t> dist(0, 360);
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
oss << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:ifc=\"http://www.ifcopenshell.org/ns\">";
|
||||||
|
oss << "<style type=\"text/css\">";
|
||||||
|
oss << " <![CDATA[";
|
||||||
|
oss << " path {";
|
||||||
|
oss << " stroke: #222222;";
|
||||||
|
oss << " fill: #444444;";
|
||||||
|
oss << " }";
|
||||||
|
oss << " ]]>";
|
||||||
|
oss << "</style>";
|
||||||
|
|
||||||
|
for (auto& g : polygons) {
|
||||||
|
oss << "<g>";
|
||||||
|
for (auto& p : g) {
|
||||||
|
const int h = dist(mt);
|
||||||
|
const int s = 50;
|
||||||
|
const int l = 50;
|
||||||
|
std::string style;
|
||||||
|
if (random_color) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "style = \"fill: hsl(" << h << "," << s << "%, " << l << "%)\"";
|
||||||
|
style = oss.str();
|
||||||
|
}
|
||||||
|
oss << "<path d=\"" << format_poly(p.boundary);
|
||||||
|
for (auto& inner : p.inner_boundaries) {
|
||||||
|
oss << " " << format_poly(inner);
|
||||||
|
}
|
||||||
|
oss << "\" " << style << " ifc:pointInside=\"" << format_pt(p.point_inside) << "\"/>";
|
||||||
|
}
|
||||||
|
oss << "</g>";
|
||||||
|
}
|
||||||
|
|
||||||
|
oss << "</svg>";
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string svgfill::polygons_to_svg(const std::vector<polygon_2>& polygons, bool random_color) {
|
||||||
|
std::vector<std::vector<polygon_2>> pps = { polygons };
|
||||||
|
return polygons_to_svg(pps, random_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void svgfill::context::add(const std::vector<line_segment_2>& segments) {
|
||||||
|
segments_.insert(segments_.end(), segments.begin(), segments.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool svgfill::context::build() {
|
||||||
|
if (solver_ == CARTESIAN_DOUBLE) {
|
||||||
|
arr_ = new cgal_arrangement<CGAL::Cartesian<double>>;
|
||||||
|
} else if (solver_ == CARTESIAN_QUOTIENT) {
|
||||||
|
arr_ = new cgal_arrangement<CGAL::Cartesian<CGAL::Quotient<CGAL::MP_Float>>>;
|
||||||
|
} else if (solver_ == FILTERED_CARTESIAN_QUOTIENT) {
|
||||||
|
arr_ = new cgal_arrangement<CGAL::Filtered_kernel<CGAL::Cartesian<CGAL::Quotient<CGAL::MP_Float>>>>;
|
||||||
|
} else if (solver_ == EXACT_PREDICATES) {
|
||||||
|
arr_ = new cgal_arrangement<CGAL::Epick>;
|
||||||
|
} else if (solver_ == EXACT_CONSTRUCTIONS) {
|
||||||
|
arr_ = new cgal_arrangement<CGAL::Epeck>;
|
||||||
|
}
|
||||||
|
return (*arr_)(eps_, segments_, progress_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void svgfill::context::merge(const std::vector<int>& edge_indices) {
|
||||||
|
arr_->merge(edge_indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
void svgfill::context::write(std::vector<std::vector<polygon_2>>& p) {
|
||||||
|
std::vector<polygon_2> polygons;
|
||||||
|
arr_->write(polygons, progress_);
|
||||||
|
p.push_back(polygons);
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* SVG fill *
|
||||||
|
* *
|
||||||
|
* Copyright(C) 2020 AECgeeks and Bimforce *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Lesser General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 3 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License *
|
||||||
|
* along with this program; if not, write to the Free Software Foundation, *
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef SVGFILL_H
|
||||||
|
#define SVGFILL_H
|
||||||
|
|
||||||
|
#ifdef IFC_SHARED_BUILD
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifdef svgfill_EXPORTS
|
||||||
|
#define SVGFILL_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define SVGFILL_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
|
#else // simply assume *nix + GCC-like compiler
|
||||||
|
#define SVGFILL_API __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define SVGFILL_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace svgfill {
|
||||||
|
typedef std::array<double, 2> point_2;
|
||||||
|
typedef std::array<point_2, 2> line_segment_2;
|
||||||
|
typedef std::vector<point_2> loop_2;
|
||||||
|
struct SVGFILL_API polygon_2 {
|
||||||
|
loop_2 boundary;
|
||||||
|
std::vector<loop_2> inner_boundaries;
|
||||||
|
point_2 point_inside;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum solver {
|
||||||
|
CARTESIAN_DOUBLE,
|
||||||
|
CARTESIAN_QUOTIENT,
|
||||||
|
FILTERED_CARTESIAN_QUOTIENT,
|
||||||
|
EXACT_PREDICATES,
|
||||||
|
EXACT_CONSTRUCTIONS
|
||||||
|
};
|
||||||
|
|
||||||
|
class SVGFILL_API abstract_arrangement {
|
||||||
|
public:
|
||||||
|
virtual ~abstract_arrangement() {}
|
||||||
|
virtual bool operator()(double eps, const std::vector<svgfill::line_segment_2>& segments, std::function<void(float)>& progress) = 0;
|
||||||
|
virtual bool write(std::vector<svgfill::polygon_2>& polygons, std::function<void(float)>& progress) = 0;
|
||||||
|
virtual void merge(const std::vector<int>& edge_indices) = 0;
|
||||||
|
virtual std::vector<int> get_face_pairs() = 0;
|
||||||
|
virtual size_t num_edges() = 0;
|
||||||
|
virtual size_t num_faces() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SVGFILL_API context {
|
||||||
|
private:
|
||||||
|
solver solver_;
|
||||||
|
double eps_;
|
||||||
|
std::vector<line_segment_2> segments_;
|
||||||
|
std::function<void(float)> progress_;
|
||||||
|
// std::vector<polygon_2> polygons_;
|
||||||
|
abstract_arrangement* arr_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
context(solver s, double eps)
|
||||||
|
: solver_(s)
|
||||||
|
, eps_(eps)
|
||||||
|
, arr_(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
context(solver s, double eps, std::function<void(float)>& progress)
|
||||||
|
: solver_(s)
|
||||||
|
, eps_(eps)
|
||||||
|
, progress_(progress)
|
||||||
|
, arr_(nullptr)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void add(const std::vector<line_segment_2>& segments);
|
||||||
|
bool build();
|
||||||
|
std::vector<int> get_face_pairs() {
|
||||||
|
return arr_->get_face_pairs();
|
||||||
|
}
|
||||||
|
void merge(const std::vector<int>& edge_indices);
|
||||||
|
void write(std::vector<std::vector<polygon_2>>&);
|
||||||
|
size_t num_edges() { return arr_->num_edges(); }
|
||||||
|
size_t num_faces() { return arr_->num_faces(); }
|
||||||
|
|
||||||
|
~context() {
|
||||||
|
delete arr_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SVGFILL_API bool svg_to_line_segments(const std::string& data, const boost::optional<std::string>& class_name, std::vector<std::vector<line_segment_2>>& segments);
|
||||||
|
SVGFILL_API bool line_segments_to_polygons(solver s, double eps, const std::vector<std::vector<line_segment_2>>& segments, std::vector<std::vector<polygon_2>>& polygons);
|
||||||
|
SVGFILL_API bool line_segments_to_polygons(solver s, double eps, const std::vector<std::vector<line_segment_2>>& segments, std::vector<std::vector<polygon_2>>& polygons, std::function<void(float)>& progress);
|
||||||
|
SVGFILL_API std::string polygons_to_svg(const std::vector<std::vector<polygon_2>>& polygons, bool random_color=false);
|
||||||
|
SVGFILL_API std::string polygons_to_svg(const std::vector<polygon_2>& polygons, bool random_color = false);
|
||||||
|
SVGFILL_API bool svg_to_polygons(const std::string& data, const boost::optional<std::string>& class_name, std::vector<polygon_2>& polygons);
|
||||||
|
SVGFILL_API bool arrange_polygons(const std::vector<polygon_2>& polygons, std::vector<polygon_2>& arranged);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user