mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +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"]
|
||||
path = src/pyodide/demo-app/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