Add svgfill directly in IfcOpenShell git repo

This commit is contained in:
Esteban DUGUEPEROUX
2025-11-25 13:50:45 +01:00
committed by Thomas Krijnen
parent 658713f142
commit 9775dfc2c1
13 changed files with 3597 additions and 0 deletions
+3
View File
@@ -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
Vendored Submodule
+1
+163
View File
@@ -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})
+65
View File
@@ -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:
![](examples/rects.svg)
out:
![](examples/rects_output.svg)
+80
View File
@@ -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

+1
View File
@@ -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
+509
View File
@@ -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 &current_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
+127
View File
@@ -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);
}
+122
View File
@@ -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
+633
View File
@@ -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);
}
+119
View File
@@ -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