Working arrange_objects with DJD selection heuristic and a bottom-left placement strategy.
This commit is contained in:
parent
b6b7945830
commit
fd829580e9
@ -49,6 +49,8 @@ if(NOT DEFINED CMAKE_PREFIX_PATH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
enable_testing ()
|
||||
|
||||
add_subdirectory(xs)
|
||||
|
||||
get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY)
|
||||
@ -63,7 +65,6 @@ else ()
|
||||
set(PERL_PROVE "${PERL_BIN_PATH}/prove")
|
||||
endif ()
|
||||
|
||||
enable_testing ()
|
||||
add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PROJECT_SOURCE_DIR}/local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/xs)
|
||||
add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
|
||||
|
@ -679,6 +679,35 @@ add_custom_target(pot
|
||||
COMMENT "Generate pot file from strings in the source tree"
|
||||
)
|
||||
|
||||
# ##############################################################################
|
||||
# Adding libnest2d project for bin packing...
|
||||
# ##############################################################################
|
||||
|
||||
set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d")
|
||||
|
||||
if(LIBNEST2D_UNITTESTS)
|
||||
# If we want the libnest2d unit tests we need to build and executable with
|
||||
# all the libslic3r dependencies. This is needed because the clipper library
|
||||
# in the slic3r project is hacked so that it depends on the slic3r sources.
|
||||
# Unfortunately, this implies that the test executable is also dependent on
|
||||
# the libslic3r target.
|
||||
|
||||
# add_library(libslic3r_standalone STATIC ${LIBDIR}/libslic3r/utils.cpp)
|
||||
# set(LIBNEST2D_TEST_LIBRARIES
|
||||
# libslic3r libslic3r_standalone nowide libslic3r_gui admesh miniz
|
||||
# ${Boost_LIBRARIES} clipper ${EXPAT_LIBRARIES} ${GLEW_LIBRARIES}
|
||||
# polypartition poly2tri ${TBB_LIBRARIES} ${wxWidgets_LIBRARIES}
|
||||
# ${CURL_LIBRARIES}
|
||||
# )
|
||||
endif()
|
||||
|
||||
add_subdirectory(${LIBDIR}/libnest2d)
|
||||
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
|
||||
|
||||
# Add the binpack2d main sources and link them to libslic3r
|
||||
target_link_libraries(libslic3r libnest2d)
|
||||
# ##############################################################################
|
||||
|
||||
# Installation
|
||||
install(TARGETS XS DESTINATION ${PERL_VENDORARCH}/auto/Slic3r/XS)
|
||||
install(FILES lib/Slic3r/XS.pm DESTINATION ${PERL_VENDORLIB}/Slic3r)
|
||||
|
81
xs/src/libnest2d/CMakeLists.txt
Normal file
81
xs/src/libnest2d/CMakeLists.txt
Normal file
@ -0,0 +1,81 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
project(Libnest2D)
|
||||
|
||||
enable_testing()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
# Update if necessary
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||
|
||||
# Add our own cmake module path.
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/)
|
||||
|
||||
option(LIBNEST2D_UNITTESTS "If enabled, googletest framework will be downloaded
|
||||
and the provided unit tests will be included in the build." OFF)
|
||||
|
||||
#set(LIBNEST2D_GEOMETRIES_TARGET "" CACHE STRING
|
||||
# "Build libnest2d with geometry classes implemented by the chosen target.")
|
||||
|
||||
#set(libnest2D_TEST_LIBRARIES "" CACHE STRING
|
||||
# "Libraries needed to compile the test executable for libnest2d.")
|
||||
|
||||
|
||||
set(LIBNEST2D_SRCFILES
|
||||
libnest2d/libnest2d.hpp # Templates only
|
||||
libnest2d.h # Exports ready made types using template arguments
|
||||
libnest2d/geometry_traits.hpp
|
||||
libnest2d/geometries_io.hpp
|
||||
libnest2d/common.hpp
|
||||
libnest2d/placers/placer_boilerplate.hpp
|
||||
libnest2d/placers/bottomleftplacer.hpp
|
||||
libnest2d/placers/nfpplacer.hpp
|
||||
libnest2d/geometries_nfp.hpp
|
||||
libnest2d/selections/selection_boilerplate.hpp
|
||||
libnest2d/selections/filler.hpp
|
||||
libnest2d/selections/firstfit.hpp
|
||||
libnest2d/selections/djd_heuristic.hpp
|
||||
)
|
||||
|
||||
if((NOT LIBNEST2D_GEOMETRIES_TARGET) OR (LIBNEST2D_GEOMETRIES_TARGET STREQUAL ""))
|
||||
message(STATUS "libnest2D backend is default")
|
||||
|
||||
if(NOT Boost_INCLUDE_DIRS_FOUND)
|
||||
find_package(Boost REQUIRED)
|
||||
# TODO automatic download of boost geometry headers
|
||||
endif()
|
||||
|
||||
add_subdirectory(libnest2d/clipper_backend)
|
||||
|
||||
set(LIBNEST2D_GEOMETRIES_TARGET ${CLIPPER_LIBRARIES})
|
||||
|
||||
include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS})
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
list(APPEND LIBNEST2D_SRCFILES libnest2d/clipper_backend/clipper_backend.cpp
|
||||
libnest2d/clipper_backend/clipper_backend.hpp
|
||||
libnest2d/boost_alg.hpp)
|
||||
|
||||
else()
|
||||
message(STATUS "Libnest2D backend is: ${LIBNEST2D_GEOMETRIES_TARGET}")
|
||||
endif()
|
||||
|
||||
add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES} )
|
||||
target_link_libraries(libnest2d ${LIBNEST2D_GEOMETRIES_TARGET})
|
||||
target_include_directories(libnest2d PUBLIC ${CMAKE_SOURCE_DIR})
|
||||
|
||||
set(LIBNEST2D_HEADERS ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
get_directory_property(hasParent PARENT_DIRECTORY)
|
||||
if(hasParent)
|
||||
set(LIBNEST2D_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if(LIBNEST2D_UNITTESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
661
xs/src/libnest2d/LICENSE.txt
Normal file
661
xs/src/libnest2d/LICENSE.txt
Normal file
@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
36
xs/src/libnest2d/README.md
Normal file
36
xs/src/libnest2d/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Introduction
|
||||
|
||||
Libnest2D is a library and framework for the 2D bin packaging problem.
|
||||
Inspired from the [SVGNest](svgnest.com) Javascript library the project is is
|
||||
built from scratch in C++11. The library is written with a policy that it should
|
||||
be usable out of the box with a very simple interface but has to be customizable
|
||||
to the very core as well. This has led to a design where the algorithms are
|
||||
defined in a header only fashion with template only geometry types. These
|
||||
geometries can have custom or already existing implementation to avoid copying
|
||||
or having unnecessary dependencies.
|
||||
|
||||
A default backend is provided if a user just wants to use the library out of the
|
||||
box without implementing the interface of these geometry types. The default
|
||||
backend is built on top of boost geometry and the
|
||||
[polyclipping](http://www.angusj.com/delphi/clipper.php) library and implies the
|
||||
dependency on these packages as well as the compilation of the backend (although
|
||||
I may find a solution in the future to make the backend header only as well).
|
||||
|
||||
This software is currently under heavy construction and lacks a throughout
|
||||
documentation and some essential algorithms as well. At this point a fairly
|
||||
untested version of the DJD selection heuristic is working with a bottom-left
|
||||
placing strategy which may produce usable arrangements in most cases.
|
||||
|
||||
The no-fit polygon based placement strategy will be implemented in the very near
|
||||
future which should produce high quality results for convex and non convex
|
||||
polygons with holes as well.
|
||||
|
||||
# References
|
||||
- [SVGNest](https://github.com/Jack000/SVGnest)
|
||||
- [An effective heuristic for the two-dimensional irregular
|
||||
bin packing problem](http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
|
||||
- [Complete and robust no-fit polygon generation for the irregular stock cutting problem](https://www.sciencedirect.com/science/article/abs/pii/S0377221706001639)
|
||||
- [Applying Meta-Heuristic Algorithms to the Nesting
|
||||
Problem Utilising the No Fit Polygon](http://www.graham-kendall.com/papers/k2001.pdf)
|
||||
- [A comprehensive and robust procedure for obtaining the nofit polygon
|
||||
using Minkowski sums](https://www.sciencedirect.com/science/article/pii/S0305054806000669)
|
@ -0,0 +1,17 @@
|
||||
# Distributed under the OSI-approved MIT License. See accompanying
|
||||
# file LICENSE or https://github.com/Crascit/DownloadProject for details.
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.2)
|
||||
|
||||
project(${DL_ARGS_PROJ}-download NONE)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(${DL_ARGS_PROJ}-download
|
||||
${DL_ARGS_UNPARSED_ARGUMENTS}
|
||||
SOURCE_DIR "${DL_ARGS_SOURCE_DIR}"
|
||||
BINARY_DIR "${DL_ARGS_BINARY_DIR}"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
TEST_COMMAND ""
|
||||
)
|
182
xs/src/libnest2d/cmake_modules/DownloadProject.cmake
Normal file
182
xs/src/libnest2d/cmake_modules/DownloadProject.cmake
Normal file
@ -0,0 +1,182 @@
|
||||
# Distributed under the OSI-approved MIT License. See accompanying
|
||||
# file LICENSE or https://github.com/Crascit/DownloadProject for details.
|
||||
#
|
||||
# MODULE: DownloadProject
|
||||
#
|
||||
# PROVIDES:
|
||||
# download_project( PROJ projectName
|
||||
# [PREFIX prefixDir]
|
||||
# [DOWNLOAD_DIR downloadDir]
|
||||
# [SOURCE_DIR srcDir]
|
||||
# [BINARY_DIR binDir]
|
||||
# [QUIET]
|
||||
# ...
|
||||
# )
|
||||
#
|
||||
# Provides the ability to download and unpack a tarball, zip file, git repository,
|
||||
# etc. at configure time (i.e. when the cmake command is run). How the downloaded
|
||||
# and unpacked contents are used is up to the caller, but the motivating case is
|
||||
# to download source code which can then be included directly in the build with
|
||||
# add_subdirectory() after the call to download_project(). Source and build
|
||||
# directories are set up with this in mind.
|
||||
#
|
||||
# The PROJ argument is required. The projectName value will be used to construct
|
||||
# the following variables upon exit (obviously replace projectName with its actual
|
||||
# value):
|
||||
#
|
||||
# projectName_SOURCE_DIR
|
||||
# projectName_BINARY_DIR
|
||||
#
|
||||
# The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically
|
||||
# need to be provided. They can be specified if you want the downloaded source
|
||||
# and build directories to be located in a specific place. The contents of
|
||||
# projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the
|
||||
# locations used whether you provide SOURCE_DIR/BINARY_DIR or not.
|
||||
#
|
||||
# The DOWNLOAD_DIR argument does not normally need to be set. It controls the
|
||||
# location of the temporary CMake build used to perform the download.
|
||||
#
|
||||
# The PREFIX argument can be provided to change the base location of the default
|
||||
# values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments
|
||||
# are provided, then PREFIX will have no effect. The default value for PREFIX is
|
||||
# CMAKE_BINARY_DIR.
|
||||
#
|
||||
# The QUIET option can be given if you do not want to show the output associated
|
||||
# with downloading the specified project.
|
||||
#
|
||||
# In addition to the above, any other options are passed through unmodified to
|
||||
# ExternalProject_Add() to perform the actual download, patch and update steps.
|
||||
# The following ExternalProject_Add() options are explicitly prohibited (they
|
||||
# are reserved for use by the download_project() command):
|
||||
#
|
||||
# CONFIGURE_COMMAND
|
||||
# BUILD_COMMAND
|
||||
# INSTALL_COMMAND
|
||||
# TEST_COMMAND
|
||||
#
|
||||
# Only those ExternalProject_Add() arguments which relate to downloading, patching
|
||||
# and updating of the project sources are intended to be used. Also note that at
|
||||
# least one set of download-related arguments are required.
|
||||
#
|
||||
# If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to
|
||||
# prevent a check at the remote end for changes every time CMake is run
|
||||
# after the first successful download. See the documentation of the ExternalProject
|
||||
# module for more information. It is likely you will want to use this option if it
|
||||
# is available to you. Note, however, that the ExternalProject implementation contains
|
||||
# bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when
|
||||
# using the URL download method or when specifying a SOURCE_DIR with no download
|
||||
# method. Fixes for these have been created, the last of which is scheduled for
|
||||
# inclusion in CMake 3.8.0. Details can be found here:
|
||||
#
|
||||
# https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c
|
||||
# https://gitlab.kitware.com/cmake/cmake/issues/16428
|
||||
#
|
||||
# If you experience build errors related to the update step, consider avoiding
|
||||
# the use of UPDATE_DISCONNECTED.
|
||||
#
|
||||
# EXAMPLE USAGE:
|
||||
#
|
||||
# include(DownloadProject)
|
||||
# download_project(PROJ googletest
|
||||
# GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
# GIT_TAG master
|
||||
# UPDATE_DISCONNECTED 1
|
||||
# QUIET
|
||||
# )
|
||||
#
|
||||
# add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
|
||||
#
|
||||
#========================================================================================
|
||||
|
||||
|
||||
set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
function(download_project)
|
||||
|
||||
set(options QUIET)
|
||||
set(oneValueArgs
|
||||
PROJ
|
||||
PREFIX
|
||||
DOWNLOAD_DIR
|
||||
SOURCE_DIR
|
||||
BINARY_DIR
|
||||
# Prevent the following from being passed through
|
||||
CONFIGURE_COMMAND
|
||||
BUILD_COMMAND
|
||||
INSTALL_COMMAND
|
||||
TEST_COMMAND
|
||||
)
|
||||
set(multiValueArgs "")
|
||||
|
||||
cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
# Hide output if requested
|
||||
if (DL_ARGS_QUIET)
|
||||
set(OUTPUT_QUIET "OUTPUT_QUIET")
|
||||
else()
|
||||
unset(OUTPUT_QUIET)
|
||||
message(STATUS "Downloading/updating ${DL_ARGS_PROJ}")
|
||||
endif()
|
||||
|
||||
# Set up where we will put our temporary CMakeLists.txt file and also
|
||||
# the base point below which the default source and binary dirs will be.
|
||||
# The prefix must always be an absolute path.
|
||||
if (NOT DL_ARGS_PREFIX)
|
||||
set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}")
|
||||
else()
|
||||
get_filename_component(DL_ARGS_PREFIX "${DL_ARGS_PREFIX}" ABSOLUTE
|
||||
BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
if (NOT DL_ARGS_DOWNLOAD_DIR)
|
||||
set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download")
|
||||
endif()
|
||||
|
||||
# Ensure the caller can know where to find the source and build directories
|
||||
if (NOT DL_ARGS_SOURCE_DIR)
|
||||
set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src")
|
||||
endif()
|
||||
if (NOT DL_ARGS_BINARY_DIR)
|
||||
set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build")
|
||||
endif()
|
||||
set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE)
|
||||
set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE)
|
||||
|
||||
# The way that CLion manages multiple configurations, it causes a copy of
|
||||
# the CMakeCache.txt to be copied across due to it not expecting there to
|
||||
# be a project within a project. This causes the hard-coded paths in the
|
||||
# cache to be copied and builds to fail. To mitigate this, we simply
|
||||
# remove the cache if it exists before we configure the new project. It
|
||||
# is safe to do so because it will be re-generated. Since this is only
|
||||
# executed at the configure step, it should not cause additional builds or
|
||||
# downloads.
|
||||
file(REMOVE "${DL_ARGS_DOWNLOAD_DIR}/CMakeCache.txt")
|
||||
|
||||
# Create and build a separate CMake project to carry out the download.
|
||||
# If we've already previously done these steps, they will not cause
|
||||
# anything to be updated, so extra rebuilds of the project won't occur.
|
||||
# Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project
|
||||
# has this set to something not findable on the PATH.
|
||||
configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in"
|
||||
"${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt")
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}"
|
||||
-D "CMAKE_MAKE_PROGRAM:FILE=${CMAKE_MAKE_PROGRAM}"
|
||||
.
|
||||
RESULT_VARIABLE result
|
||||
${OUTPUT_QUIET}
|
||||
WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}"
|
||||
)
|
||||
if(result)
|
||||
message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}")
|
||||
endif()
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} --build .
|
||||
RESULT_VARIABLE result
|
||||
${OUTPUT_QUIET}
|
||||
WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}"
|
||||
)
|
||||
if(result)
|
||||
message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}")
|
||||
endif()
|
||||
|
||||
endfunction()
|
46
xs/src/libnest2d/cmake_modules/FindClipper.cmake
Normal file
46
xs/src/libnest2d/cmake_modules/FindClipper.cmake
Normal file
@ -0,0 +1,46 @@
|
||||
# Find Clipper library (http://www.angusj.com/delphi/clipper.php).
|
||||
# The following variables are set
|
||||
#
|
||||
# CLIPPER_FOUND
|
||||
# CLIPPER_INCLUDE_DIRS
|
||||
# CLIPPER_LIBRARIES
|
||||
#
|
||||
# It searches the environment variable $CLIPPER_PATH automatically.
|
||||
|
||||
FIND_PATH(CLIPPER_INCLUDE_DIRS clipper.hpp
|
||||
$ENV{CLIPPER_PATH}
|
||||
$ENV{CLIPPER_PATH}/cpp/
|
||||
$ENV{CLIPPER_PATH}/include/
|
||||
$ENV{CLIPPER_PATH}/include/polyclipping/
|
||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/
|
||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/include/polyclipping/
|
||||
/opt/local/include/
|
||||
/opt/local/include/polyclipping/
|
||||
/usr/local/include/
|
||||
/usr/local/include/polyclipping/
|
||||
/usr/include
|
||||
/usr/include/polyclipping/)
|
||||
|
||||
FIND_LIBRARY(CLIPPER_LIBRARIES polyclipping
|
||||
$ENV{CLIPPER_PATH}
|
||||
$ENV{CLIPPER_PATH}/cpp/
|
||||
$ENV{CLIPPER_PATH}/cpp/build/
|
||||
$ENV{CLIPPER_PATH}/lib/
|
||||
$ENV{CLIPPER_PATH}/lib/polyclipping/
|
||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/
|
||||
${PROJECT_SOURCE_DIR}/python/pymesh/third_party/lib/polyclipping/
|
||||
/opt/local/lib/
|
||||
/opt/local/lib/polyclipping/
|
||||
/usr/local/lib/
|
||||
/usr/local/lib/polyclipping/
|
||||
/usr/lib/polyclipping)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Clipper
|
||||
"Clipper library cannot be found. Consider set CLIPPER_PATH environment variable"
|
||||
CLIPPER_INCLUDE_DIRS
|
||||
CLIPPER_LIBRARIES)
|
||||
|
||||
MARK_AS_ADVANCED(
|
||||
CLIPPER_INCLUDE_DIRS
|
||||
CLIPPER_LIBRARIES)
|
37
xs/src/libnest2d/libnest2d.h
Normal file
37
xs/src/libnest2d/libnest2d.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef LIBNEST2D_H
|
||||
#define LIBNEST2D_H
|
||||
|
||||
// The type of backend should be set conditionally by the cmake configuriation
|
||||
// for now we set it statically to clipper backend
|
||||
#include <libnest2d/clipper_backend/clipper_backend.hpp>
|
||||
|
||||
#include <libnest2d/libnest2d.hpp>
|
||||
#include <libnest2d/placers/bottomleftplacer.hpp>
|
||||
#include <libnest2d/placers/nfpplacer.hpp>
|
||||
#include <libnest2d/selections/firstfit.hpp>
|
||||
#include <libnest2d/selections/filler.hpp>
|
||||
#include <libnest2d/selections/djd_heuristic.hpp>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
using Point = PointImpl;
|
||||
using Coord = TCoord<PointImpl>;
|
||||
using Box = _Box<PointImpl>;
|
||||
using Segment = _Segment<PointImpl>;
|
||||
|
||||
using Item = _Item<PolygonImpl>;
|
||||
using Rectangle = _Rectangle<PolygonImpl>;
|
||||
|
||||
using PackGroup = _PackGroup<PolygonImpl>;
|
||||
using IndexedPackGroup = _IndexedPackGroup<PolygonImpl>;
|
||||
|
||||
using FillerSelection = strategies::_FillerSelection<PolygonImpl>;
|
||||
using FirstFitSelection = strategies::_FirstFitSelection<PolygonImpl>;
|
||||
using DJDHeuristic = strategies::_DJDHeuristic<PolygonImpl>;
|
||||
|
||||
using BottomLeftPlacer = strategies::_BottomLeftPlacer<PolygonImpl>;
|
||||
using NofitPolyPlacer = strategies::_NofitPolyPlacer<PolygonImpl>;
|
||||
|
||||
}
|
||||
|
||||
#endif // LIBNEST2D_H
|
431
xs/src/libnest2d/libnest2d/boost_alg.hpp
Normal file
431
xs/src/libnest2d/libnest2d/boost_alg.hpp
Normal file
@ -0,0 +1,431 @@
|
||||
#ifndef BOOST_ALG_HPP
|
||||
#define BOOST_ALG_HPP
|
||||
|
||||
#ifndef DISABLE_BOOST_SERIALIZE
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <boost/geometry.hpp>
|
||||
|
||||
// this should be removed to not confuse the compiler
|
||||
// #include <libnest2d.h>
|
||||
|
||||
namespace bp2d {
|
||||
|
||||
using libnest2d::TCoord;
|
||||
using libnest2d::PointImpl;
|
||||
using Coord = TCoord<PointImpl>;
|
||||
using libnest2d::PolygonImpl;
|
||||
using libnest2d::PathImpl;
|
||||
using libnest2d::Orientation;
|
||||
using libnest2d::OrientationType;
|
||||
using libnest2d::getX;
|
||||
using libnest2d::getY;
|
||||
using libnest2d::setX;
|
||||
using libnest2d::setY;
|
||||
using Box = libnest2d::_Box<PointImpl>;
|
||||
using Segment = libnest2d::_Segment<PointImpl>;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to make all the binpack2d geometry types available to boost. The real
|
||||
* models of the geometries remain the same if a conforming model for binpack2d
|
||||
* was defined by the library client. Boost is used only as an optional
|
||||
* implementer of some algorithms that can be implemented by the model itself
|
||||
* if a faster alternative exists.
|
||||
*
|
||||
* However, boost has its own type traits and we have to define the needed
|
||||
* specializations to be able to use boost::geometry. This can be done with the
|
||||
* already provided model.
|
||||
*/
|
||||
namespace boost {
|
||||
namespace geometry {
|
||||
namespace traits {
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Point concept adaptaion ************************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
template<> struct tag<bp2d::PointImpl> {
|
||||
using type = point_tag;
|
||||
};
|
||||
|
||||
template<> struct coordinate_type<bp2d::PointImpl> {
|
||||
using type = bp2d::Coord;
|
||||
};
|
||||
|
||||
template<> struct coordinate_system<bp2d::PointImpl> {
|
||||
using type = cs::cartesian;
|
||||
};
|
||||
|
||||
template<> struct dimension<bp2d::PointImpl>: boost::mpl::int_<2> {};
|
||||
|
||||
template<>
|
||||
struct access<bp2d::PointImpl, 0 > {
|
||||
static inline bp2d::Coord get(bp2d::PointImpl const& a) {
|
||||
return libnest2d::getX(a);
|
||||
}
|
||||
|
||||
static inline void set(bp2d::PointImpl& a,
|
||||
bp2d::Coord const& value) {
|
||||
libnest2d::setX(a, value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct access<bp2d::PointImpl, 1 > {
|
||||
static inline bp2d::Coord get(bp2d::PointImpl const& a) {
|
||||
return libnest2d::getY(a);
|
||||
}
|
||||
|
||||
static inline void set(bp2d::PointImpl& a,
|
||||
bp2d::Coord const& value) {
|
||||
libnest2d::setY(a, value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Box concept adaptaion **************************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
template<> struct tag<bp2d::Box> {
|
||||
using type = box_tag;
|
||||
};
|
||||
|
||||
template<> struct point_type<bp2d::Box> {
|
||||
using type = bp2d::PointImpl;
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Box, min_corner, 0> {
|
||||
static inline bp2d::Coord get(bp2d::Box const& box) {
|
||||
return bp2d::getX(box.minCorner());
|
||||
}
|
||||
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
|
||||
bp2d::setX(box.minCorner(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Box, min_corner, 1> {
|
||||
static inline bp2d::Coord get(bp2d::Box const& box) {
|
||||
return bp2d::getY(box.minCorner());
|
||||
}
|
||||
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
|
||||
bp2d::setY(box.minCorner(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Box, max_corner, 0> {
|
||||
static inline bp2d::Coord get(bp2d::Box const& box) {
|
||||
return bp2d::getX(box.maxCorner());
|
||||
}
|
||||
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
|
||||
bp2d::setX(box.maxCorner(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Box, max_corner, 1> {
|
||||
static inline bp2d::Coord get(bp2d::Box const& box) {
|
||||
return bp2d::getY(box.maxCorner());
|
||||
}
|
||||
static inline void set(bp2d::Box &box, bp2d::Coord const& coord) {
|
||||
bp2d::setY(box.maxCorner(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Segement concept adaptaion *********************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
template<> struct tag<bp2d::Segment> {
|
||||
using type = segment_tag;
|
||||
};
|
||||
|
||||
template<> struct point_type<bp2d::Segment> {
|
||||
using type = bp2d::PointImpl;
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Segment, 0, 0> {
|
||||
static inline bp2d::Coord get(bp2d::Segment const& seg) {
|
||||
return bp2d::getX(seg.first());
|
||||
}
|
||||
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
|
||||
bp2d::setX(seg.first(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Segment, 0, 1> {
|
||||
static inline bp2d::Coord get(bp2d::Segment const& seg) {
|
||||
return bp2d::getY(seg.first());
|
||||
}
|
||||
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
|
||||
bp2d::setY(seg.first(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Segment, 1, 0> {
|
||||
static inline bp2d::Coord get(bp2d::Segment const& seg) {
|
||||
return bp2d::getX(seg.second());
|
||||
}
|
||||
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
|
||||
bp2d::setX(seg.second(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct indexed_access<bp2d::Segment, 1, 1> {
|
||||
static inline bp2d::Coord get(bp2d::Segment const& seg) {
|
||||
return bp2d::getY(seg.second());
|
||||
}
|
||||
static inline void set(bp2d::Segment &seg, bp2d::Coord const& coord) {
|
||||
bp2d::setY(seg.second(), coord);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* Polygon concept adaptaion ************************************************ */
|
||||
/* ************************************************************************** */
|
||||
|
||||
// Connversion between binpack2d::Orientation and order_selector ///////////////
|
||||
|
||||
template<bp2d::Orientation> struct ToBoostOrienation {};
|
||||
|
||||
template<>
|
||||
struct ToBoostOrienation<bp2d::Orientation::CLOCKWISE> {
|
||||
static const order_selector Value = clockwise;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ToBoostOrienation<bp2d::Orientation::COUNTER_CLOCKWISE> {
|
||||
static const order_selector Value = counterclockwise;
|
||||
};
|
||||
|
||||
static const bp2d::Orientation RealOrientation =
|
||||
bp2d::OrientationType<bp2d::PolygonImpl>::Value;
|
||||
|
||||
// Ring implementation /////////////////////////////////////////////////////////
|
||||
|
||||
// Boost would refer to ClipperLib::Path (alias bp2d::PolygonImpl) as a ring
|
||||
template<> struct tag<bp2d::PathImpl> {
|
||||
using type = ring_tag;
|
||||
};
|
||||
|
||||
template<> struct point_order<bp2d::PathImpl> {
|
||||
static const order_selector value =
|
||||
ToBoostOrienation<RealOrientation>::Value;
|
||||
};
|
||||
|
||||
// All our Paths should be closed for the bin packing application
|
||||
template<> struct closure<bp2d::PathImpl> {
|
||||
static const closure_selector value = closed;
|
||||
};
|
||||
|
||||
// Polygon implementation //////////////////////////////////////////////////////
|
||||
|
||||
template<> struct tag<bp2d::PolygonImpl> {
|
||||
using type = polygon_tag;
|
||||
};
|
||||
|
||||
template<> struct exterior_ring<bp2d::PolygonImpl> {
|
||||
static inline bp2d::PathImpl& get(bp2d::PolygonImpl& p) {
|
||||
return libnest2d::ShapeLike::getContour(p);
|
||||
}
|
||||
|
||||
static inline bp2d::PathImpl const& get(bp2d::PolygonImpl const& p) {
|
||||
return libnest2d::ShapeLike::getContour(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct ring_const_type<bp2d::PolygonImpl> {
|
||||
using type = const bp2d::PathImpl&;
|
||||
};
|
||||
|
||||
template<> struct ring_mutable_type<bp2d::PolygonImpl> {
|
||||
using type = bp2d::PathImpl&;
|
||||
};
|
||||
|
||||
template<> struct interior_const_type<bp2d::PolygonImpl> {
|
||||
using type = const libnest2d::THolesContainer<bp2d::PolygonImpl>&;
|
||||
};
|
||||
|
||||
template<> struct interior_mutable_type<bp2d::PolygonImpl> {
|
||||
using type = libnest2d::THolesContainer<bp2d::PolygonImpl>&;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct interior_rings<bp2d::PolygonImpl> {
|
||||
|
||||
static inline libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
|
||||
bp2d::PolygonImpl& p)
|
||||
{
|
||||
return libnest2d::ShapeLike::holes(p);
|
||||
}
|
||||
|
||||
static inline const libnest2d::THolesContainer<bp2d::PolygonImpl>& get(
|
||||
bp2d::PolygonImpl const& p)
|
||||
{
|
||||
return libnest2d::ShapeLike::holes(p);
|
||||
}
|
||||
};
|
||||
|
||||
} // traits
|
||||
} // geometry
|
||||
|
||||
// This is an addition to the ring implementation
|
||||
template<>
|
||||
struct range_value<bp2d::PathImpl> {
|
||||
using type = bp2d::PointImpl;
|
||||
};
|
||||
|
||||
} // boost
|
||||
|
||||
namespace libnest2d { // Now the algorithms that boost can provide...
|
||||
|
||||
template<>
|
||||
inline double PointLike::distance(const PointImpl& p1,
|
||||
const PointImpl& p2 )
|
||||
{
|
||||
return boost::geometry::distance(p1, p2);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline double PointLike::distance(const PointImpl& p,
|
||||
const bp2d::Segment& seg )
|
||||
{
|
||||
return boost::geometry::distance(p, seg);
|
||||
}
|
||||
|
||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
||||
template<>
|
||||
inline bool ShapeLike::intersects(const PathImpl& sh1,
|
||||
const PathImpl& sh2)
|
||||
{
|
||||
return boost::geometry::intersects(sh1, sh2);
|
||||
}
|
||||
|
||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
||||
template<>
|
||||
inline bool ShapeLike::intersects(const PolygonImpl& sh1,
|
||||
const PolygonImpl& sh2) {
|
||||
return boost::geometry::intersects(sh1, sh2);
|
||||
}
|
||||
|
||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
||||
template<>
|
||||
inline bool ShapeLike::intersects(const bp2d::Segment& s1,
|
||||
const bp2d::Segment& s2) {
|
||||
return boost::geometry::intersects(s1, s2);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_BOOST_AREA
|
||||
template<>
|
||||
inline double ShapeLike::area(const PolygonImpl& shape) {
|
||||
return boost::geometry::area(shape);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<>
|
||||
inline bool ShapeLike::isInside(const PointImpl& point,
|
||||
const PolygonImpl& shape)
|
||||
{
|
||||
return boost::geometry::within(point, shape);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool ShapeLike::isInside(const PolygonImpl& sh1,
|
||||
const PolygonImpl& sh2)
|
||||
{
|
||||
return boost::geometry::within(sh1, sh2);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool ShapeLike::touches( const PolygonImpl& sh1,
|
||||
const PolygonImpl& sh2)
|
||||
{
|
||||
return boost::geometry::touches(sh1, sh2);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bp2d::Box ShapeLike::boundingBox(const PolygonImpl& sh) {
|
||||
bp2d::Box b;
|
||||
boost::geometry::envelope(sh, b);
|
||||
return b;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads) {
|
||||
namespace trans = boost::geometry::strategy::transform;
|
||||
|
||||
PolygonImpl cpy = sh;
|
||||
|
||||
trans::rotate_transformer<boost::geometry::radian, Radians, 2, 2>
|
||||
rotate(rads);
|
||||
boost::geometry::transform(cpy, sh, rotate);
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ShapeLike::translate(PolygonImpl& sh, const PointImpl& offs) {
|
||||
namespace trans = boost::geometry::strategy::transform;
|
||||
|
||||
PolygonImpl cpy = sh;
|
||||
trans::translate_transformer<bp2d::Coord, 2, 2> translate(
|
||||
bp2d::getX(offs), bp2d::getY(offs));
|
||||
|
||||
boost::geometry::transform(cpy, sh, translate);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_BOOST_OFFSET
|
||||
template<>
|
||||
inline void ShapeLike::offset(PolygonImpl& sh, bp2d::Coord distance) {
|
||||
PolygonImpl cpy = sh;
|
||||
boost::geometry::buffer(cpy, sh, distance);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_BOOST_MINKOWSKI_ADD
|
||||
template<>
|
||||
inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
|
||||
const PolygonImpl& /*other*/) {
|
||||
return sh;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_BOOST_SERIALIZE
|
||||
template<>
|
||||
inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
|
||||
const PolygonImpl& sh)
|
||||
{
|
||||
|
||||
std::stringstream ss;
|
||||
std::string style = "fill: orange; stroke: black; stroke-width: 1px;";
|
||||
auto svg_data = boost::geometry::svg(sh, style);
|
||||
|
||||
ss << svg_data << std::endl;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_BOOST_UNSERIALIZE
|
||||
template<>
|
||||
inline void ShapeLike::unserialize<libnest2d::Formats::SVG>(
|
||||
PolygonImpl& sh,
|
||||
const std::string& str)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
template<> inline std::pair<bool, std::string>
|
||||
ShapeLike::isValid(const PolygonImpl& sh) {
|
||||
std::string message;
|
||||
bool ret = boost::geometry::is_valid(sh, message);
|
||||
|
||||
return {ret, message};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // BOOST_ALG_HPP
|
48
xs/src/libnest2d/libnest2d/clipper_backend/CMakeLists.txt
Normal file
48
xs/src/libnest2d/libnest2d/clipper_backend/CMakeLists.txt
Normal file
@ -0,0 +1,48 @@
|
||||
if(NOT TARGET clipper) # If there is a clipper target in the parent project we are good to go.
|
||||
|
||||
find_package(Clipper QUIET)
|
||||
|
||||
if(NOT CLIPPER_FOUND)
|
||||
find_package(Subversion QUIET)
|
||||
if(Subversion_FOUND)
|
||||
message(STATUS "Clipper not found so it will be downloaded.")
|
||||
# Silently download and build the library in the build dir
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.2)
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
|
||||
else()
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
|
||||
endif()
|
||||
|
||||
include(DownloadProject)
|
||||
download_project( PROJ clipper_library
|
||||
SVN_REPOSITORY https://svn.code.sf.net/p/polyclipping/code/trunk/cpp
|
||||
SVN_REVISION -r540
|
||||
#SOURCE_SUBDIR cpp
|
||||
INSTALL_COMMAND ""
|
||||
CONFIGURE_COMMAND "" # Not working, I will just add the source files
|
||||
${UPDATE_DISCONNECTED_IF_AVAILABLE}
|
||||
)
|
||||
|
||||
# This is not working and I dont have time to fix it
|
||||
# add_subdirectory(${clipper_library_SOURCE_DIR}/cpp
|
||||
# ${clipper_library_BINARY_DIR}
|
||||
# )
|
||||
|
||||
add_library(clipper_lib STATIC
|
||||
${clipper_library_SOURCE_DIR}/clipper.cpp
|
||||
${clipper_library_SOURCE_DIR}/clipper.hpp)
|
||||
|
||||
set(CLIPPER_INCLUDE_DIRS ${clipper_library_SOURCE_DIR}
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(CLIPPER_LIBRARIES clipper_lib PARENT_SCOPE)
|
||||
|
||||
else()
|
||||
message(FATAL_ERROR "Can't find clipper library and no SVN client found to download.
|
||||
You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
set(CLIPPER_LIBRARIES clipper PARENT_SCOPE)
|
||||
endif()
|
@ -0,0 +1,77 @@
|
||||
#include "clipper_backend.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
namespace {
|
||||
class HoleCache {
|
||||
friend struct libnest2d::ShapeLike;
|
||||
|
||||
std::unordered_map< const PolygonImpl*, ClipperLib::Paths> map;
|
||||
|
||||
ClipperLib::Paths& _getHoles(const PolygonImpl* p) {
|
||||
ClipperLib::Paths& paths = map[p];
|
||||
|
||||
if(paths.size() != p->Childs.size()) {
|
||||
paths.reserve(p->Childs.size());
|
||||
|
||||
for(auto np : p->Childs) {
|
||||
paths.emplace_back(np->Contour);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
ClipperLib::Paths& getHoles(PolygonImpl& p) {
|
||||
return _getHoles(&p);
|
||||
}
|
||||
|
||||
const ClipperLib::Paths& getHoles(const PolygonImpl& p) {
|
||||
return _getHoles(&p);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
HoleCache holeCache;
|
||||
|
||||
template<>
|
||||
std::string ShapeLike::toString(const PolygonImpl& sh)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
for(auto p : sh.Contour) {
|
||||
ss << p.X << " " << p.Y << "\n";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template<> PolygonImpl ShapeLike::create( std::initializer_list< PointImpl > il)
|
||||
{
|
||||
PolygonImpl p;
|
||||
p.Contour = il;
|
||||
|
||||
// Expecting that the coordinate system Y axis is positive in upwards
|
||||
// direction
|
||||
if(ClipperLib::Orientation(p.Contour)) {
|
||||
// Not clockwise then reverse the b*tch
|
||||
ClipperLib::ReversePath(p.Contour);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
template<>
|
||||
const THolesContainer<PolygonImpl>& ShapeLike::holes(
|
||||
const PolygonImpl& sh)
|
||||
{
|
||||
return holeCache.getHoles(sh);
|
||||
}
|
||||
|
||||
template<>
|
||||
THolesContainer<PolygonImpl>& ShapeLike::holes(PolygonImpl& sh) {
|
||||
return holeCache.getHoles(sh);
|
||||
}
|
||||
|
||||
}
|
||||
|
240
xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp
Normal file
240
xs/src/libnest2d/libnest2d/clipper_backend/clipper_backend.hpp
Normal file
@ -0,0 +1,240 @@
|
||||
#ifndef CLIPPER_BACKEND_HPP
|
||||
#define CLIPPER_BACKEND_HPP
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <cassert>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../geometry_traits.hpp"
|
||||
#include "../geometries_nfp.hpp"
|
||||
|
||||
#include <clipper.hpp>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
// Aliases for convinience
|
||||
using PointImpl = ClipperLib::IntPoint;
|
||||
using PolygonImpl = ClipperLib::PolyNode;
|
||||
using PathImpl = ClipperLib::Path;
|
||||
|
||||
inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) {
|
||||
p.X += pa.X;
|
||||
p.Y += pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline PointImpl operator+(const PointImpl& p1, const PointImpl& p2) {
|
||||
PointImpl ret = p1;
|
||||
ret += p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline PointImpl& operator -=(PointImpl& p, const PointImpl& pa ) {
|
||||
p.X -= pa.X;
|
||||
p.Y -= pa.Y;
|
||||
return p;
|
||||
}
|
||||
|
||||
inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
|
||||
PointImpl ret = p1;
|
||||
ret -= p2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//extern HoleCache holeCache;
|
||||
|
||||
// Type of coordinate units used by Clipper
|
||||
template<> struct CoordType<PointImpl> {
|
||||
using Type = ClipperLib::cInt;
|
||||
};
|
||||
|
||||
// Type of point used by Clipper
|
||||
template<> struct PointType<PolygonImpl> {
|
||||
using Type = PointImpl;
|
||||
};
|
||||
|
||||
// Type of vertex iterator used by Clipper
|
||||
template<> struct VertexIteratorType<PolygonImpl> {
|
||||
using Type = ClipperLib::Path::iterator;
|
||||
};
|
||||
|
||||
// Type of vertex iterator used by Clipper
|
||||
template<> struct VertexConstIteratorType<PolygonImpl> {
|
||||
using Type = ClipperLib::Path::const_iterator;
|
||||
};
|
||||
|
||||
template<> struct CountourType<PolygonImpl> {
|
||||
using Type = PathImpl;
|
||||
};
|
||||
|
||||
// Tell binpack2d how to extract the X coord from a ClipperPoint object
|
||||
template<> inline TCoord<PointImpl> PointLike::x(const PointImpl& p)
|
||||
{
|
||||
return p.X;
|
||||
}
|
||||
|
||||
// Tell binpack2d how to extract the Y coord from a ClipperPoint object
|
||||
template<> inline TCoord<PointImpl> PointLike::y(const PointImpl& p)
|
||||
{
|
||||
return p.Y;
|
||||
}
|
||||
|
||||
// Tell binpack2d how to extract the X coord from a ClipperPoint object
|
||||
template<> inline TCoord<PointImpl>& PointLike::x(PointImpl& p)
|
||||
{
|
||||
return p.X;
|
||||
}
|
||||
|
||||
// Tell binpack2d how to extract the Y coord from a ClipperPoint object
|
||||
template<>
|
||||
inline TCoord<PointImpl>& PointLike::y(PointImpl& p)
|
||||
{
|
||||
return p.Y;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ShapeLike::reserve(PolygonImpl& sh, unsigned long vertex_capacity)
|
||||
{
|
||||
return sh.Contour.reserve(vertex_capacity);
|
||||
}
|
||||
|
||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
||||
template<>
|
||||
inline double ShapeLike::area(const PolygonImpl& sh) {
|
||||
#define DISABLE_BOOST_AREA
|
||||
double ret = ClipperLib::Area(sh.Contour);
|
||||
// if(OrientationType<PolygonImpl>::Value == Orientation::COUNTER_CLOCKWISE)
|
||||
// ret = -ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
|
||||
#define DISABLE_BOOST_OFFSET
|
||||
|
||||
using ClipperLib::ClipperOffset;
|
||||
using ClipperLib::jtMiter;
|
||||
using ClipperLib::etClosedPolygon;
|
||||
using ClipperLib::Paths;
|
||||
|
||||
ClipperOffset offs;
|
||||
Paths result;
|
||||
offs.AddPath(sh.Contour, jtMiter, etClosedPolygon);
|
||||
offs.Execute(result, static_cast<double>(distance));
|
||||
|
||||
// I dont know why does the offsetting revert the orientation and
|
||||
// it removes the last vertex as well so boost will not have a closed
|
||||
// polygon
|
||||
|
||||
assert(result.size() == 1);
|
||||
sh.Contour = result.front();
|
||||
|
||||
// recreate closed polygon
|
||||
sh.Contour.push_back(sh.Contour.front());
|
||||
|
||||
if(ClipperLib::Orientation(sh.Contour)) {
|
||||
// Not clockwise then reverse the b*tch
|
||||
ClipperLib::ReversePath(sh.Contour);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
|
||||
const PolygonImpl& other)
|
||||
{
|
||||
#define DISABLE_BOOST_MINKOWSKI_ADD
|
||||
|
||||
ClipperLib::Paths solution;
|
||||
|
||||
ClipperLib::MinkowskiSum(sh.Contour, other.Contour, solution, true);
|
||||
|
||||
assert(solution.size() == 1);
|
||||
|
||||
sh.Contour = solution.front();
|
||||
|
||||
return sh;
|
||||
}
|
||||
|
||||
// Tell binpack2d how to make string out of a ClipperPolygon object
|
||||
template<> std::string ShapeLike::toString(const PolygonImpl& sh);
|
||||
|
||||
template<>
|
||||
inline TVertexIterator<PolygonImpl> ShapeLike::begin(PolygonImpl& sh)
|
||||
{
|
||||
return sh.Contour.begin();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline TVertexIterator<PolygonImpl> ShapeLike::end(PolygonImpl& sh)
|
||||
{
|
||||
return sh.Contour.end();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline TVertexConstIterator<PolygonImpl> ShapeLike::cbegin(
|
||||
const PolygonImpl& sh)
|
||||
{
|
||||
return sh.Contour.cbegin();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline TVertexConstIterator<PolygonImpl> ShapeLike::cend(
|
||||
const PolygonImpl& sh)
|
||||
{
|
||||
return sh.Contour.cend();
|
||||
}
|
||||
|
||||
template<> struct HolesContainer<PolygonImpl> {
|
||||
using Type = ClipperLib::Paths;
|
||||
};
|
||||
|
||||
template<>
|
||||
PolygonImpl ShapeLike::create( std::initializer_list< PointImpl > il);
|
||||
|
||||
template<>
|
||||
const THolesContainer<PolygonImpl>& ShapeLike::holes(
|
||||
const PolygonImpl& sh);
|
||||
|
||||
template<>
|
||||
THolesContainer<PolygonImpl>& ShapeLike::holes(PolygonImpl& sh);
|
||||
|
||||
template<>
|
||||
inline TCountour<PolygonImpl>& ShapeLike::getHole(PolygonImpl& sh,
|
||||
unsigned long idx)
|
||||
{
|
||||
return sh.Childs[idx]->Contour;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline const TCountour<PolygonImpl>& ShapeLike::getHole(const PolygonImpl& sh,
|
||||
unsigned long idx) {
|
||||
return sh.Childs[idx]->Contour;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline size_t ShapeLike::holeCount(const PolygonImpl& sh) {
|
||||
return sh.Childs.size();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline PathImpl& ShapeLike::getContour(PolygonImpl& sh) {
|
||||
return sh.Contour;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline const PathImpl& ShapeLike::getContour(const PolygonImpl& sh) {
|
||||
return sh.Contour;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#define DISABLE_BOOST_SERIALIZE
|
||||
//#define DISABLE_BOOST_UNSERIALIZE
|
||||
|
||||
// All other operators and algorithms are implemented with boost
|
||||
#include "../boost_alg.hpp"
|
||||
|
||||
#endif // CLIPPER_BACKEND_HPP
|
94
xs/src/libnest2d/libnest2d/common.hpp
Normal file
94
xs/src/libnest2d/libnest2d/common.hpp
Normal file
@ -0,0 +1,94 @@
|
||||
#ifndef LIBNEST2D_CONFIG_HPP
|
||||
#define LIBNEST2D_CONFIG_HPP
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
|
||||
#define BP2D_NOEXCEPT
|
||||
#define BP2D_CONSTEXPR
|
||||
#elif __cplusplus >= 201103L
|
||||
#define BP2D_NOEXCEPT noexcept
|
||||
#define BP2D_CONSTEXPR constexpr
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
template< class T >
|
||||
struct remove_cvref {
|
||||
using type = typename std::remove_cv<
|
||||
typename std::remove_reference<T>::type>::type;
|
||||
};
|
||||
|
||||
template< class T >
|
||||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
template<bool B, class T>
|
||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
||||
/**
|
||||
* A useful little tool for triggering static_assert error messages e.g. when
|
||||
* a mandatory template specialization (implementation) is missing.
|
||||
*
|
||||
* \tparam T A template argument that may come from and outer template method.
|
||||
*/
|
||||
template<class T> struct always_false { enum { value = false }; };
|
||||
|
||||
const auto BP2D_CONSTEXPR Pi = 3.141592653589793238463; // 2*std::acos(0);
|
||||
|
||||
/**
|
||||
* @brief Only for the Radian and Degrees classes to behave as doubles.
|
||||
*/
|
||||
class Double {
|
||||
double val_;
|
||||
public:
|
||||
Double(): val_(double{}) { }
|
||||
Double(double d) : val_(d) { }
|
||||
|
||||
operator double() const BP2D_NOEXCEPT { return val_; }
|
||||
operator double&() BP2D_NOEXCEPT { return val_; }
|
||||
};
|
||||
|
||||
class Degrees;
|
||||
|
||||
/**
|
||||
* @brief Data type representing radians. It supports conversion to degrees.
|
||||
*/
|
||||
class Radians: public Double {
|
||||
public:
|
||||
Radians(double rads = Double() ): Double(rads) {}
|
||||
inline Radians(const Degrees& degs);
|
||||
|
||||
inline operator Degrees();
|
||||
inline double toDegrees();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Data type representing degrees. It supports conversion to radians.
|
||||
*/
|
||||
class Degrees: public Double {
|
||||
public:
|
||||
Degrees(double deg = Double()): Double(deg) {}
|
||||
Degrees(const Radians& rads): Double( rads * 180/Pi ) {}
|
||||
inline double toRadians() { return Radians(*this);}
|
||||
};
|
||||
|
||||
inline bool operator==(const Degrees& deg, const Radians& rads) {
|
||||
Degrees deg2 = rads;
|
||||
auto diff = std::abs(deg - deg2);
|
||||
return diff < 0.0001;
|
||||
}
|
||||
|
||||
inline bool operator==(const Radians& rads, const Degrees& deg) {
|
||||
return deg == rads;
|
||||
}
|
||||
|
||||
inline Radians::operator Degrees() { return *this * 180/Pi; }
|
||||
|
||||
inline Radians::Radians(const Degrees °s): Double( degs * Pi/180) {}
|
||||
|
||||
inline double Radians::toDegrees() { return operator Degrees(); }
|
||||
|
||||
}
|
||||
#endif // LIBNEST2D_CONFIG_HPP
|
18
xs/src/libnest2d/libnest2d/geometries_io.hpp
Normal file
18
xs/src/libnest2d/libnest2d/geometries_io.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef GEOMETRIES_IO_HPP
|
||||
#define GEOMETRIES_IO_HPP
|
||||
|
||||
#include "libnest2d.hpp"
|
||||
|
||||
#include <ostream>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
template<class RawShape>
|
||||
std::ostream& operator<<(std::ostream& stream, const _Item<RawShape>& sh) {
|
||||
stream << sh.toString() << "\n";
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // GEOMETRIES_IO_HPP
|
125
xs/src/libnest2d/libnest2d/geometries_nfp.hpp
Normal file
125
xs/src/libnest2d/libnest2d/geometries_nfp.hpp
Normal file
@ -0,0 +1,125 @@
|
||||
#ifndef GEOMETRIES_NOFITPOLYGON_HPP
|
||||
#define GEOMETRIES_NOFITPOLYGON_HPP
|
||||
|
||||
#include "geometry_traits.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
struct Nfp {
|
||||
|
||||
template<class RawShape>
|
||||
static RawShape& minkowskiAdd(RawShape& sh, const RawShape& /*other*/) {
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::minkowskiAdd() unimplemented!");
|
||||
return sh;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other) {
|
||||
auto isConvex = [](const RawShape& sh) {
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
using Vertex = TPoint<RawShape>;
|
||||
using Edge = _Segment<Vertex>;
|
||||
|
||||
auto nfpConvexConvex = [] (
|
||||
const RawShape& sh,
|
||||
const RawShape& cother)
|
||||
{
|
||||
RawShape other = cother;
|
||||
|
||||
// Make it counter-clockwise
|
||||
for(auto shit = ShapeLike::begin(other);
|
||||
shit != ShapeLike::end(other); ++shit ) {
|
||||
auto& v = *shit;
|
||||
setX(v, -getX(v));
|
||||
setY(v, -getY(v));
|
||||
}
|
||||
|
||||
RawShape rsh;
|
||||
std::vector<Edge> edgelist;
|
||||
|
||||
size_t cap = ShapeLike::contourVertexCount(sh) +
|
||||
ShapeLike::contourVertexCount(other);
|
||||
|
||||
edgelist.reserve(cap);
|
||||
ShapeLike::reserve(rsh, cap);
|
||||
|
||||
{
|
||||
auto first = ShapeLike::cbegin(sh);
|
||||
auto next = first + 1;
|
||||
auto endit = ShapeLike::cend(sh);
|
||||
|
||||
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
||||
}
|
||||
|
||||
{
|
||||
auto first = ShapeLike::cbegin(other);
|
||||
auto next = first + 1;
|
||||
auto endit = ShapeLike::cend(other);
|
||||
|
||||
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
|
||||
}
|
||||
|
||||
std::sort(edgelist.begin(), edgelist.end(),
|
||||
[](const Edge& e1, const Edge& e2)
|
||||
{
|
||||
return e1.angleToXaxis() > e2.angleToXaxis();
|
||||
});
|
||||
|
||||
ShapeLike::addVertex(rsh, edgelist.front().first());
|
||||
ShapeLike::addVertex(rsh, edgelist.front().second());
|
||||
|
||||
auto tmp = std::next(ShapeLike::begin(rsh));
|
||||
|
||||
// Construct final nfp
|
||||
for(auto eit = std::next(edgelist.begin());
|
||||
eit != edgelist.end();
|
||||
++eit) {
|
||||
|
||||
auto dx = getX(*tmp) - getX(eit->first());
|
||||
auto dy = getY(*tmp) - getY(eit->first());
|
||||
|
||||
ShapeLike::addVertex(rsh, getX(eit->second())+dx,
|
||||
getY(eit->second())+dy );
|
||||
|
||||
tmp = std::next(tmp);
|
||||
}
|
||||
|
||||
return rsh;
|
||||
};
|
||||
|
||||
RawShape rsh;
|
||||
|
||||
enum e_dispatch {
|
||||
CONVEX_CONVEX,
|
||||
CONCAVE_CONVEX,
|
||||
CONVEX_CONCAVE,
|
||||
CONCAVE_CONCAVE
|
||||
};
|
||||
|
||||
int sel = isConvex(sh) ? CONVEX_CONVEX : CONCAVE_CONVEX;
|
||||
sel += isConvex(other) ? CONVEX_CONVEX : CONVEX_CONCAVE;
|
||||
|
||||
switch(sel) {
|
||||
case CONVEX_CONVEX:
|
||||
rsh = nfpConvexConvex(sh, other); break;
|
||||
case CONCAVE_CONVEX:
|
||||
break;
|
||||
case CONVEX_CONCAVE:
|
||||
break;
|
||||
case CONCAVE_CONCAVE:
|
||||
break;
|
||||
}
|
||||
|
||||
return rsh;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // GEOMETRIES_NOFITPOLYGON_HPP
|
501
xs/src/libnest2d/libnest2d/geometry_traits.hpp
Normal file
501
xs/src/libnest2d/libnest2d/geometry_traits.hpp
Normal file
@ -0,0 +1,501 @@
|
||||
#ifndef GEOMETRY_TRAITS_HPP
|
||||
#define GEOMETRY_TRAITS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
/// Getting the coordinate data type for a geometry class.
|
||||
template<class GeomClass> struct CoordType { using Type = long; };
|
||||
|
||||
/// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
|
||||
template<class GeomType>
|
||||
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
|
||||
|
||||
/// Getting the type of point structure used by a shape.
|
||||
template<class Shape> struct PointType { using Type = void; };
|
||||
|
||||
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
|
||||
template<class Shape>
|
||||
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
|
||||
|
||||
/// Getting the VertexIterator type of a shape class.
|
||||
template<class Shape> struct VertexIteratorType { using Type = void; };
|
||||
|
||||
/// Getting the const vertex iterator for a shape class.
|
||||
template<class Shape> struct VertexConstIteratorType { using Type = void; };
|
||||
|
||||
/**
|
||||
* TVertexIterator<Shape> as shorthand for
|
||||
* `typename VertexIteratorType<Shape>::Type`
|
||||
*/
|
||||
template<class Shape>
|
||||
using TVertexIterator =
|
||||
typename VertexIteratorType<remove_cvref_t<Shape>>::Type;
|
||||
|
||||
/**
|
||||
* \brief TVertexConstIterator<Shape> as shorthand for
|
||||
* `typename VertexConstIteratorType<Shape>::Type`
|
||||
*/
|
||||
template<class ShapeClass>
|
||||
using TVertexConstIterator =
|
||||
typename VertexConstIteratorType<remove_cvref_t<ShapeClass>>::Type;
|
||||
|
||||
/**
|
||||
* \brief A point pair base class for other point pairs (segment, box, ...).
|
||||
* \tparam RawPoint The actual point type to use.
|
||||
*/
|
||||
template<class RawPoint>
|
||||
struct PointPair {
|
||||
RawPoint p1;
|
||||
RawPoint p2;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief An abstraction of a box;
|
||||
*/
|
||||
template<class RawPoint>
|
||||
class _Box: PointPair<RawPoint> {
|
||||
using PointPair<RawPoint>::p1;
|
||||
using PointPair<RawPoint>::p2;
|
||||
public:
|
||||
|
||||
inline _Box() {}
|
||||
inline _Box(const RawPoint& p, const RawPoint& pp):
|
||||
PointPair<RawPoint>({p, pp}) {}
|
||||
|
||||
inline _Box(TCoord<RawPoint> width, TCoord<RawPoint> height):
|
||||
_Box(RawPoint{0, 0}, RawPoint{width, height}) {}
|
||||
|
||||
inline const RawPoint& minCorner() const BP2D_NOEXCEPT { return p1; }
|
||||
inline const RawPoint& maxCorner() const BP2D_NOEXCEPT { return p2; }
|
||||
|
||||
inline RawPoint& minCorner() BP2D_NOEXCEPT { return p1; }
|
||||
inline RawPoint& maxCorner() BP2D_NOEXCEPT { return p2; }
|
||||
|
||||
inline TCoord<RawPoint> width() const BP2D_NOEXCEPT;
|
||||
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
|
||||
};
|
||||
|
||||
template<class RawPoint>
|
||||
class _Segment: PointPair<RawPoint> {
|
||||
using PointPair<RawPoint>::p1;
|
||||
using PointPair<RawPoint>::p2;
|
||||
public:
|
||||
|
||||
inline _Segment() {}
|
||||
inline _Segment(const RawPoint& p, const RawPoint& pp):
|
||||
PointPair<RawPoint>({p, pp}) {}
|
||||
|
||||
inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; }
|
||||
inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; }
|
||||
|
||||
inline RawPoint& first() BP2D_NOEXCEPT { return p1; }
|
||||
inline RawPoint& second() BP2D_NOEXCEPT { return p2; }
|
||||
|
||||
inline Radians angleToXaxis() const;
|
||||
};
|
||||
|
||||
class PointLike {
|
||||
public:
|
||||
|
||||
template<class RawPoint>
|
||||
static TCoord<RawPoint> x(const RawPoint& p)
|
||||
{
|
||||
return p.x();
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static TCoord<RawPoint> y(const RawPoint& p)
|
||||
{
|
||||
return p.y();
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static TCoord<RawPoint>& x(RawPoint& p)
|
||||
{
|
||||
return p.x();
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static TCoord<RawPoint>& y(RawPoint& p)
|
||||
{
|
||||
return p.y();
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
|
||||
{
|
||||
static_assert(always_false<RawPoint>::value,
|
||||
"PointLike::distance(point, point) unimplemented");
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static double distance(const RawPoint& /*p1*/,
|
||||
const _Segment<RawPoint>& /*s*/)
|
||||
{
|
||||
static_assert(always_false<RawPoint>::value,
|
||||
"PointLike::distance(point, segment) unimplemented");
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static std::pair<TCoord<RawPoint>, bool> horizontalDistance(
|
||||
const RawPoint& p, const _Segment<RawPoint>& s)
|
||||
{
|
||||
using Unit = TCoord<RawPoint>;
|
||||
auto x = PointLike::x(p), y = PointLike::y(p);
|
||||
auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first());
|
||||
auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second());
|
||||
|
||||
TCoord<RawPoint> ret;
|
||||
|
||||
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
|
||||
return {0, false};
|
||||
else if ((y == y1 && y == y2) && (x > x1 && x > x2))
|
||||
ret = std::min( x-x1, x -x2);
|
||||
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
|
||||
ret = -std::min(x1 - x, x2 - x);
|
||||
else if(std::abs(y - y1) <= std::numeric_limits<Unit>::epsilon() &&
|
||||
std::abs(y - y2) <= std::numeric_limits<Unit>::epsilon())
|
||||
ret = 0;
|
||||
else
|
||||
ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2);
|
||||
|
||||
return {ret, true};
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
static std::pair<TCoord<RawPoint>, bool> verticalDistance(
|
||||
const RawPoint& p, const _Segment<RawPoint>& s)
|
||||
{
|
||||
using Unit = TCoord<RawPoint>;
|
||||
auto x = PointLike::x(p), y = PointLike::y(p);
|
||||
auto x1 = PointLike::x(s.first()), y1 = PointLike::y(s.first());
|
||||
auto x2 = PointLike::x(s.second()), y2 = PointLike::y(s.second());
|
||||
|
||||
TCoord<RawPoint> ret;
|
||||
|
||||
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
|
||||
return {0, false};
|
||||
else if ((x == x1 && x == x2) && (y > y1 && y > y2))
|
||||
ret = std::min( y-y1, y -y2);
|
||||
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
|
||||
ret = -std::min(y1 - y, y2 - y);
|
||||
else if(std::abs(x - x1) <= std::numeric_limits<Unit>::epsilon() &&
|
||||
std::abs(x - x2) <= std::numeric_limits<Unit>::epsilon())
|
||||
ret = 0;
|
||||
else
|
||||
ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2);
|
||||
|
||||
return {ret, true};
|
||||
}
|
||||
};
|
||||
|
||||
template<class RawPoint>
|
||||
TCoord<RawPoint> _Box<RawPoint>::width() const BP2D_NOEXCEPT {
|
||||
return PointLike::x(maxCorner()) - PointLike::x(minCorner());
|
||||
}
|
||||
|
||||
|
||||
template<class RawPoint>
|
||||
TCoord<RawPoint> _Box<RawPoint>::height() const BP2D_NOEXCEPT {
|
||||
return PointLike::y(maxCorner()) - PointLike::y(minCorner());
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
TCoord<RawPoint> getX(const RawPoint& p) { return PointLike::x<RawPoint>(p); }
|
||||
|
||||
template<class RawPoint>
|
||||
TCoord<RawPoint> getY(const RawPoint& p) { return PointLike::y<RawPoint>(p); }
|
||||
|
||||
template<class RawPoint>
|
||||
void setX(RawPoint& p, const TCoord<RawPoint>& val) {
|
||||
PointLike::x<RawPoint>(p) = val;
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
void setY(RawPoint& p, const TCoord<RawPoint>& val) {
|
||||
PointLike::y<RawPoint>(p) = val;
|
||||
}
|
||||
|
||||
template<class RawPoint>
|
||||
inline Radians _Segment<RawPoint>::angleToXaxis() const
|
||||
{
|
||||
TCoord<RawPoint> dx = getX(second()) - getX(first());
|
||||
TCoord<RawPoint> dy = getY(second()) - getY(first());
|
||||
|
||||
if(dx == 0 && dy >= 0) return Pi/2;
|
||||
if(dx == 0 && dy < 0) return 3*Pi/2;
|
||||
if(dy == 0 && dx >= 0) return 0;
|
||||
if(dy == 0 && dx < 0) return Pi;
|
||||
|
||||
double ddx = static_cast<double>(dx);
|
||||
auto s = std::signbit(ddx);
|
||||
double a = std::atan(ddx/dy);
|
||||
if(s) a += Pi;
|
||||
return a;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
struct HolesContainer {
|
||||
using Type = std::vector<RawShape>;
|
||||
};
|
||||
|
||||
template<class RawShape>
|
||||
using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type;
|
||||
|
||||
template<class RawShape>
|
||||
struct CountourType {
|
||||
using Type = RawShape;
|
||||
};
|
||||
|
||||
template<class RawShape>
|
||||
using TCountour = typename CountourType<remove_cvref_t<RawShape>>::Type;
|
||||
|
||||
enum class Orientation {
|
||||
CLOCKWISE,
|
||||
COUNTER_CLOCKWISE
|
||||
};
|
||||
|
||||
template<class RawShape>
|
||||
struct OrientationType {
|
||||
|
||||
// Default Polygon orientation that the library expects
|
||||
static const Orientation Value = Orientation::CLOCKWISE;
|
||||
};
|
||||
|
||||
enum class Formats {
|
||||
WKT,
|
||||
SVG
|
||||
};
|
||||
|
||||
struct ShapeLike {
|
||||
|
||||
template<class RawShape>
|
||||
static RawShape create( std::initializer_list< TPoint<RawShape> > il)
|
||||
{
|
||||
return RawShape(il);
|
||||
}
|
||||
|
||||
// Optional, does nothing by default
|
||||
template<class RawShape>
|
||||
static void reserve(RawShape& /*sh*/, unsigned long /*vertex_capacity*/) {}
|
||||
|
||||
template<class RawShape, class...Args>
|
||||
static void addVertex(RawShape& sh, Args...args)
|
||||
{
|
||||
return getContour(sh).emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TVertexIterator<RawShape> begin(RawShape& sh)
|
||||
{
|
||||
return sh.begin();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TVertexIterator<RawShape> end(RawShape& sh)
|
||||
{
|
||||
return sh.end();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TVertexConstIterator<RawShape> cbegin(const RawShape& sh)
|
||||
{
|
||||
return sh.cbegin();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TVertexConstIterator<RawShape> cend(const RawShape& sh)
|
||||
{
|
||||
return sh.cend();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TPoint<RawShape>& vertex(RawShape& sh, unsigned long idx)
|
||||
{
|
||||
return *(begin(sh) + idx);
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TPoint<RawShape>& vertex(const RawShape& sh,
|
||||
unsigned long idx)
|
||||
{
|
||||
return *(cbegin(sh) + idx);
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static size_t contourVertexCount(const RawShape& sh)
|
||||
{
|
||||
return cend(sh) - cbegin(sh);
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static std::string toString(const RawShape& /*sh*/)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
template<Formats, class RawShape>
|
||||
static std::string serialize(const RawShape& /*sh*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::serialize() unimplemented");
|
||||
return "";
|
||||
}
|
||||
|
||||
template<Formats, class RawShape>
|
||||
static void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::unserialize() unimplemented");
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static double area(const RawShape& /*sh*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::area() unimplemented");
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static double area(const _Box<TPoint<RawShape>>& box)
|
||||
{
|
||||
return box.width() * box.height();
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::intersects() unimplemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const TPoint<RawShape>& /*point*/,
|
||||
const RawShape& /*shape*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::isInside(point, shape) unimplemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool isInside(const RawShape& /*shape*/,
|
||||
const RawShape& /*shape*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::isInside(shape, shape) unimplemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static bool touches( const RawShape& /*shape*/,
|
||||
const RawShape& /*shape*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::touches(shape, shape) unimplemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::boundingBox(shape) unimplemented");
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static _Box<TPoint<RawShape>> boundingBox(const _Box<TPoint<RawShape>>& box)
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
|
||||
{
|
||||
static THolesContainer<RawShape> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
|
||||
{
|
||||
static THolesContainer<RawShape> empty;
|
||||
return empty;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TCountour<RawShape>& getHole(RawShape& sh, unsigned long idx)
|
||||
{
|
||||
return holes(sh)[idx];
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TCountour<RawShape>& getHole(const RawShape& sh,
|
||||
unsigned long idx)
|
||||
{
|
||||
return holes(sh)[idx];
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static size_t holeCount(const RawShape& sh)
|
||||
{
|
||||
return holes(sh).size();
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static TCountour<RawShape>& getContour(RawShape& sh)
|
||||
{
|
||||
return sh;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static const TCountour<RawShape>& getContour(const RawShape& sh)
|
||||
{
|
||||
return sh;
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::rotate() unimplemented");
|
||||
}
|
||||
|
||||
template<class RawShape, class RawPoint>
|
||||
static void translate(RawShape& /*sh*/, const RawPoint& /*offs*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::translate() unimplemented");
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static void offset(RawShape& /*sh*/, TCoord<TPoint<RawShape>> /*distance*/)
|
||||
{
|
||||
static_assert(always_false<RawShape>::value,
|
||||
"ShapeLike::offset() unimplemented!");
|
||||
}
|
||||
|
||||
template<class RawShape>
|
||||
static std::pair<bool, std::string> isValid(const RawShape& /*sh*/) {
|
||||
return {false, "ShapeLike::isValid() unimplemented"};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // GEOMETRY_TRAITS_HPP
|
802
xs/src/libnest2d/libnest2d/libnest2d.hpp
Normal file
802
xs/src/libnest2d/libnest2d/libnest2d.hpp
Normal file
@ -0,0 +1,802 @@
|
||||
#ifndef LIBNEST2D_HPP
|
||||
#define LIBNEST2D_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "geometry_traits.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
|
||||
/**
|
||||
* \brief An item to be placed on a bin.
|
||||
*
|
||||
* It holds a copy of the original shape object but supports move construction
|
||||
* from the shape objects if its an rvalue reference. This way we can construct
|
||||
* the items without the cost of copying a potentially large amount of input.
|
||||
*
|
||||
* The results of some calculations are cached for maintaining fast run times.
|
||||
* For this reason, memory demands are much higher but this should pay off.
|
||||
*/
|
||||
template<class RawShape>
|
||||
class _Item {
|
||||
using Coord = TCoord<TPoint<RawShape>>;
|
||||
using Vertex = TPoint<RawShape>;
|
||||
using Box = _Box<Vertex>;
|
||||
|
||||
// The original shape that gets encapsulated.
|
||||
RawShape sh_;
|
||||
|
||||
// Transformation data
|
||||
Vertex translation_;
|
||||
Radians rotation_;
|
||||
Coord offset_distance_;
|
||||
|
||||
// Info about whether the tranformations will have to take place
|
||||
// This is needed because if floating point is used, it is hard to say
|
||||
// that a zero angle is not a rotation because of testing for equality.
|
||||
bool has_rotation_ = false, has_translation_ = false, has_offset_ = false;
|
||||
|
||||
// For caching the calculations as they can get pretty expensive.
|
||||
mutable RawShape tr_cache_;
|
||||
mutable bool tr_cache_valid_ = false;
|
||||
mutable double area_cache_ = 0;
|
||||
mutable bool area_cache_valid_ = false;
|
||||
mutable RawShape offset_cache_;
|
||||
mutable bool offset_cache_valid_ = false;
|
||||
public:
|
||||
|
||||
/// The type of the shape which was handed over as the template argument.
|
||||
using ShapeType = RawShape;
|
||||
|
||||
/**
|
||||
* \brief Iterator type for the outer vertices.
|
||||
*
|
||||
* Only const iterators can be used. The _Item type is not intended to
|
||||
* modify the carried shapes from the outside. The main purpose of this type
|
||||
* is to cache the calculation results from the various operators it
|
||||
* supports. Giving out a non const iterator would make it impossible to
|
||||
* perform correct cache invalidation.
|
||||
*/
|
||||
using Iterator = TVertexConstIterator<RawShape>;
|
||||
|
||||
/**
|
||||
* @brief Get the orientation of the polygon.
|
||||
*
|
||||
* The orientation have to be specified as a specialization of the
|
||||
* OrientationType struct which has a Value constant.
|
||||
*
|
||||
* @return The orientation type identifier for the _Item type.
|
||||
*/
|
||||
static BP2D_CONSTEXPR Orientation orientation() {
|
||||
return OrientationType<RawShape>::Value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructing an _Item form an existing raw shape. The shape will
|
||||
* be copied into the _Item object.
|
||||
* @param sh The original shape object.
|
||||
*/
|
||||
explicit inline _Item(const RawShape& sh): sh_(sh) {}
|
||||
|
||||
/**
|
||||
* @brief Construction of an item by moving the content of the raw shape,
|
||||
* assuming that it supports move semantics.
|
||||
* @param sh The original shape object.
|
||||
*/
|
||||
explicit inline _Item(RawShape&& sh): sh_(std::move(sh)) {}
|
||||
|
||||
/**
|
||||
* @brief Create an item from an initilizer list.
|
||||
* @param il The initializer list of vertices.
|
||||
*/
|
||||
inline _Item(const std::initializer_list< Vertex >& il):
|
||||
sh_(ShapeLike::create<RawShape>(il)) {}
|
||||
|
||||
/**
|
||||
* @brief Convert the polygon to string representation. The format depends
|
||||
* on the implementation of the polygon.
|
||||
* @return
|
||||
*/
|
||||
inline std::string toString() const
|
||||
{
|
||||
return ShapeLike::toString(sh_);
|
||||
}
|
||||
|
||||
/// Iterator tho the first vertex in the polygon.
|
||||
inline Iterator begin() const
|
||||
{
|
||||
return ShapeLike::cbegin(sh_);
|
||||
}
|
||||
|
||||
/// Alias to begin()
|
||||
inline Iterator cbegin() const
|
||||
{
|
||||
return ShapeLike::cbegin(sh_);
|
||||
}
|
||||
|
||||
/// Iterator to the last element.
|
||||
inline Iterator end() const
|
||||
{
|
||||
return ShapeLike::cend(sh_);
|
||||
}
|
||||
|
||||
/// Alias to end()
|
||||
inline Iterator cend() const
|
||||
{
|
||||
return ShapeLike::cend(sh_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a copy of an outer vertex whithin the carried shape.
|
||||
*
|
||||
* Note that the vertex considered here is taken from the original shape
|
||||
* that this item is constructed from. This means that no transformation is
|
||||
* applied to the shape in this call.
|
||||
*
|
||||
* @param idx The index of the requested vertex.
|
||||
* @return A copy of the requested vertex.
|
||||
*/
|
||||
inline Vertex vertex(unsigned long idx) const
|
||||
{
|
||||
return ShapeLike::vertex(sh_, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Modify a vertex.
|
||||
*
|
||||
* Note that this method will invalidate every cached calculation result
|
||||
* including polygon offset and transformations.
|
||||
*
|
||||
* @param idx The index of the requested vertex.
|
||||
* @param v The new vertex data.
|
||||
*/
|
||||
inline void setVertex(unsigned long idx,
|
||||
const Vertex& v )
|
||||
{
|
||||
invalidateCache();
|
||||
ShapeLike::vertex(sh_, idx) = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the shape area.
|
||||
*
|
||||
* The method returns absolute value and does not reflect polygon
|
||||
* orientation. The result is cached, subsequent calls will have very little
|
||||
* cost.
|
||||
* @return The shape area in floating point double precision.
|
||||
*/
|
||||
inline double area() const {
|
||||
double ret ;
|
||||
if(area_cache_valid_) ret = area_cache_;
|
||||
else {
|
||||
ret = std::abs(ShapeLike::area(offsettedShape()));
|
||||
area_cache_ = ret;
|
||||
area_cache_valid_ = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// The number of the outer ring vertices.
|
||||
inline unsigned long vertexCount() const {
|
||||
return ShapeLike::contourVertexCount(sh_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief isPointInside
|
||||
* @param p
|
||||
* @return
|
||||
*/
|
||||
inline bool isPointInside(const Vertex& p)
|
||||
{
|
||||
return ShapeLike::isInside(p, sh_);
|
||||
}
|
||||
|
||||
inline bool isInside(const _Item& sh) const
|
||||
{
|
||||
return ShapeLike::isInside(transformedShape(), sh.transformedShape());
|
||||
}
|
||||
|
||||
inline void translate(const Vertex& d) BP2D_NOEXCEPT
|
||||
{
|
||||
translation_ += d; has_translation_ = true;
|
||||
tr_cache_valid_ = false;
|
||||
}
|
||||
|
||||
inline void rotate(const Radians& rads) BP2D_NOEXCEPT
|
||||
{
|
||||
rotation_ += rads;
|
||||
has_rotation_ = true;
|
||||
tr_cache_valid_ = false;
|
||||
}
|
||||
|
||||
inline void addOffset(Coord distance) BP2D_NOEXCEPT
|
||||
{
|
||||
offset_distance_ = distance;
|
||||
has_offset_ = true;
|
||||
offset_cache_valid_ = false;
|
||||
}
|
||||
|
||||
inline void removeOffset() BP2D_NOEXCEPT {
|
||||
has_offset_ = false;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
inline Radians rotation() const BP2D_NOEXCEPT
|
||||
{
|
||||
return rotation_;
|
||||
}
|
||||
|
||||
inline TPoint<RawShape> translation() const BP2D_NOEXCEPT
|
||||
{
|
||||
return translation_;
|
||||
}
|
||||
|
||||
inline void rotation(Radians rot) BP2D_NOEXCEPT
|
||||
{
|
||||
if(rotation_ != rot) {
|
||||
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
inline void translation(const TPoint<RawShape>& tr) BP2D_NOEXCEPT
|
||||
{
|
||||
if(translation_ != tr) {
|
||||
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
inline RawShape transformedShape() const
|
||||
{
|
||||
if(tr_cache_valid_) return tr_cache_;
|
||||
|
||||
RawShape cpy = offsettedShape();
|
||||
if(has_rotation_) ShapeLike::rotate(cpy, rotation_);
|
||||
if(has_translation_) ShapeLike::translate(cpy, translation_);
|
||||
tr_cache_ = cpy; tr_cache_valid_ = true;
|
||||
|
||||
return cpy;
|
||||
}
|
||||
|
||||
inline operator RawShape() const
|
||||
{
|
||||
return transformedShape();
|
||||
}
|
||||
|
||||
inline const RawShape& rawShape() const BP2D_NOEXCEPT
|
||||
{
|
||||
return sh_;
|
||||
}
|
||||
|
||||
inline void resetTransformation() BP2D_NOEXCEPT
|
||||
{
|
||||
has_translation_ = false; has_rotation_ = false; has_offset_ = false;
|
||||
}
|
||||
|
||||
inline Box boundingBox() const {
|
||||
return ShapeLike::boundingBox(transformedShape());
|
||||
}
|
||||
|
||||
//Static methods:
|
||||
|
||||
inline static bool intersects(const _Item& sh1, const _Item& sh2)
|
||||
{
|
||||
return ShapeLike::intersects(sh1.transformedShape(),
|
||||
sh2.transformedShape());
|
||||
}
|
||||
|
||||
inline static bool touches(const _Item& sh1, const _Item& sh2)
|
||||
{
|
||||
return ShapeLike::touches(sh1.transformedShape(),
|
||||
sh2.transformedShape());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
inline const RawShape& offsettedShape() const {
|
||||
if(has_offset_ ) {
|
||||
if(offset_cache_valid_) return offset_cache_;
|
||||
else {
|
||||
offset_cache_ = sh_;
|
||||
ShapeLike::offset(offset_cache_, offset_distance_);
|
||||
offset_cache_valid_ = true;
|
||||
return offset_cache_;
|
||||
}
|
||||
}
|
||||
return sh_;
|
||||
}
|
||||
|
||||
inline void invalidateCache() const BP2D_NOEXCEPT
|
||||
{
|
||||
tr_cache_valid_ = false;
|
||||
area_cache_valid_ = false;
|
||||
offset_cache_valid_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Subclass of _Item for regular rectangle items.
|
||||
*/
|
||||
template<class RawShape>
|
||||
class _Rectangle: public _Item<RawShape> {
|
||||
RawShape sh_;
|
||||
using _Item<RawShape>::vertex;
|
||||
using TO = Orientation;
|
||||
public:
|
||||
|
||||
using Unit = TCoord<RawShape>;
|
||||
|
||||
template<TO o = OrientationType<RawShape>::Value>
|
||||
inline _Rectangle(Unit width, Unit height,
|
||||
// disable this ctor if o != CLOCKWISE
|
||||
enable_if_t< o == TO::CLOCKWISE, int> = 0 ):
|
||||
_Item<RawShape>( ShapeLike::create<RawShape>( {
|
||||
{0, 0},
|
||||
{0, height},
|
||||
{width, height},
|
||||
{width, 0},
|
||||
{0, 0}
|
||||
} ))
|
||||
{
|
||||
}
|
||||
|
||||
template<TO o = OrientationType<RawShape>::Value>
|
||||
inline _Rectangle(Unit width, Unit height,
|
||||
// disable this ctor if o != COUNTER_CLOCKWISE
|
||||
enable_if_t< o == TO::COUNTER_CLOCKWISE, int> = 0 ):
|
||||
_Item<RawShape>( ShapeLike::create<RawShape>( {
|
||||
{0, 0},
|
||||
{width, 0},
|
||||
{width, height},
|
||||
{0, height},
|
||||
{0, 0}
|
||||
} ))
|
||||
{
|
||||
}
|
||||
|
||||
inline Unit width() const BP2D_NOEXCEPT {
|
||||
return getX(vertex(2));
|
||||
}
|
||||
|
||||
inline Unit height() const BP2D_NOEXCEPT {
|
||||
return getY(vertex(2));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief A wrapper interface (trait) class for any placement strategy provider.
|
||||
*
|
||||
* If a client want's to use its own placement algorithm, all it has to do is to
|
||||
* specialize this class template and define all the ten methods it has. It can
|
||||
* use the strategies::PlacerBoilerplace class for creating a new placement
|
||||
* strategy where only the constructor and the trypack method has to be provided
|
||||
* and it will work out of the box.
|
||||
*/
|
||||
template<class PlacementStrategy>
|
||||
class PlacementStrategyLike {
|
||||
PlacementStrategy impl_;
|
||||
public:
|
||||
|
||||
/// The item type that the placer works with.
|
||||
using Item = typename PlacementStrategy::Item;
|
||||
|
||||
/// The placer's config type. Should be a simple struct but can be anything.
|
||||
using Config = typename PlacementStrategy::Config;
|
||||
|
||||
/**
|
||||
* \brief The type of the bin that the placer works with.
|
||||
*
|
||||
* Can be a box or an arbitrary shape or just a width or height without a
|
||||
* second dimension if an infinite bin is considered.
|
||||
*/
|
||||
using BinType = typename PlacementStrategy::BinType;
|
||||
|
||||
/**
|
||||
* \brief Pack result that can be used to accept or discard it. See trypack
|
||||
* method.
|
||||
*/
|
||||
using PackResult = typename PlacementStrategy::PackResult;
|
||||
|
||||
using ItemRef = std::reference_wrapper<Item>;
|
||||
using ItemGroup = std::vector<ItemRef>;
|
||||
|
||||
/**
|
||||
* @brief Constructor taking the bin and an optional configuration.
|
||||
* @param bin The bin object whose type is defined by the placement strategy.
|
||||
* @param config The configuration for the particular placer.
|
||||
*/
|
||||
explicit PlacementStrategyLike(const BinType& bin,
|
||||
const Config& config = Config()):
|
||||
impl_(bin)
|
||||
{
|
||||
configure(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provide a different configuration for the placer.
|
||||
*
|
||||
* Note that it depends on the particular placer implementation how it
|
||||
* reacts to config changes in the middle of a calculation.
|
||||
*
|
||||
* @param config The configuration object defined by the placement startegy.
|
||||
*/
|
||||
inline void configure(const Config& config) { impl_.configure(config); }
|
||||
|
||||
/**
|
||||
* @brief A method that tries to pack an item and returns an object
|
||||
* describing the pack result.
|
||||
*
|
||||
* The result can be casted to bool and used as an argument to the accept
|
||||
* method to accept a succesfully packed item. This way the next packing
|
||||
* will consider the accepted item as well. The PackResult should carry the
|
||||
* transformation info so that if the tried item is later modified or tried
|
||||
* multiple times, the result object should set it to the originally
|
||||
* determied position. An implementation can be found in the
|
||||
* strategies::PlacerBoilerplate::PackResult class.
|
||||
*
|
||||
* @param item Ithe item to be packed.
|
||||
* @return The PackResult object that can be implicitly casted to bool.
|
||||
*/
|
||||
inline PackResult trypack(Item& item) { return impl_.trypack(item); }
|
||||
|
||||
/**
|
||||
* @brief A method to accept a previously tried item.
|
||||
*
|
||||
* If the pack result is a failure the method should ignore it.
|
||||
* @param r The result of a previous trypack call.
|
||||
*/
|
||||
inline void accept(PackResult& r) { impl_.accept(r); }
|
||||
|
||||
/**
|
||||
* @brief pack Try to pack an item and immediately accept it on success.
|
||||
*
|
||||
* A default implementation would be to call
|
||||
* { auto&& r = trypack(item); accept(r); return r; } but we should let the
|
||||
* implementor of the placement strategy to harvest any optimizations from
|
||||
* the absence of an intermadiate step. The above version can still be used
|
||||
* in the implementation.
|
||||
*
|
||||
* @param item The item to pack.
|
||||
* @return Returns true if the item was packed or false if it could not be
|
||||
* packed.
|
||||
*/
|
||||
inline bool pack(Item& item) { return impl_.pack(item); }
|
||||
|
||||
/// Unpack the last element (remove it from the list of packed items).
|
||||
inline void unpackLast() { impl_.unpackLast(); }
|
||||
|
||||
/// Get the bin object.
|
||||
inline const BinType& bin() const { return impl_.bin(); }
|
||||
|
||||
/// Set a new bin object.
|
||||
inline void bin(const BinType& bin) { impl_.bin(bin); }
|
||||
|
||||
/// Get the packed items.
|
||||
inline ItemGroup getItems() { return impl_.getItems(); }
|
||||
|
||||
/// Clear the packed items so a new session can be started.
|
||||
inline void clearItems() { impl_.clearItems(); }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper interface (trait) class for any selections strategy provider.
|
||||
*/
|
||||
template<class SelectionStrategy>
|
||||
class SelectionStrategyLike {
|
||||
SelectionStrategy impl_;
|
||||
public:
|
||||
using Item = typename SelectionStrategy::Item;
|
||||
using Config = typename SelectionStrategy::Config;
|
||||
|
||||
using ItemRef = std::reference_wrapper<Item>;
|
||||
using ItemGroup = std::vector<ItemRef>;
|
||||
|
||||
/**
|
||||
* @brief Provide a different configuration for the selection strategy.
|
||||
*
|
||||
* Note that it depends on the particular placer implementation how it
|
||||
* reacts to config changes in the middle of a calculation.
|
||||
*
|
||||
* @param config The configuration object defined by the selection startegy.
|
||||
*/
|
||||
inline void configure(const Config& config) {
|
||||
impl_.configure(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief A method to start the calculation on the input sequence.
|
||||
*
|
||||
* \tparam TPlacer The only mandatory template parameter is the type of
|
||||
* placer compatible with the PlacementStrategyLike interface.
|
||||
*
|
||||
* \param first, last The first and last iterator if the input sequence. It
|
||||
* can be only an iterator of a type converitible to Item.
|
||||
* \param bin. The shape of the bin. It has to be supported by the placement
|
||||
* strategy.
|
||||
* \param An optional config object for the placer.
|
||||
*/
|
||||
template<class TPlacer, class TIterator,
|
||||
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
|
||||
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
|
||||
inline void packItems(
|
||||
TIterator first,
|
||||
TIterator last,
|
||||
TBin&& bin,
|
||||
PConfig&& config = PConfig() )
|
||||
{
|
||||
impl_.template packItems<TPlacer>(first, last,
|
||||
std::forward<TBin>(bin),
|
||||
std::forward<PConfig>(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the number of bins opened by the selection algorithm.
|
||||
*
|
||||
* Initially it is zero and after the call to packItems it will return
|
||||
* the number of bins opened by the packing procedure.
|
||||
*
|
||||
* \return The number of bins opened.
|
||||
*/
|
||||
inline size_t binCount() const { return impl_.binCount(); }
|
||||
|
||||
/**
|
||||
* @brief Get the items for a particular bin.
|
||||
* @param binIndex The index of the requested bin.
|
||||
* @return Returns a list of allitems packed into the requested bin.
|
||||
*/
|
||||
inline ItemGroup itemsForBin(size_t binIndex) {
|
||||
return impl_.itemsForBin(binIndex);
|
||||
}
|
||||
|
||||
/// Same as itemsForBin but for a const context.
|
||||
inline const ItemGroup itemsForBin(size_t binIndex) const {
|
||||
return impl_.itemsForBin(binIndex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \brief A list of packed item vectors. Each vector represents a bin.
|
||||
*/
|
||||
template<class RawShape>
|
||||
using _PackGroup = std::vector<
|
||||
std::vector<
|
||||
std::reference_wrapper<_Item<RawShape>>
|
||||
>
|
||||
>;
|
||||
|
||||
/**
|
||||
* \brief A list of packed (index, item) pair vectors. Each vector represents a
|
||||
* bin.
|
||||
*
|
||||
* The index is points to the position of the item in the original input
|
||||
* sequence. This way the caller can use the items as a transformation data
|
||||
* carrier and transform the original objects manually.
|
||||
*/
|
||||
template<class RawShape>
|
||||
using _IndexedPackGroup = std::vector<
|
||||
std::vector<
|
||||
std::pair<
|
||||
unsigned,
|
||||
std::reference_wrapper<_Item<RawShape>>
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
/**
|
||||
* The Arranger is the frontend class for the binpack2d library. It takes the
|
||||
* input items and outputs the items with the proper transformations to be
|
||||
* inside the provided bin.
|
||||
*/
|
||||
template<class PlacementStrategy, class SelectionStrategy >
|
||||
class Arranger {
|
||||
using TSel = SelectionStrategyLike<SelectionStrategy>;
|
||||
TSel selector_;
|
||||
|
||||
public:
|
||||
using Item = typename PlacementStrategy::Item;
|
||||
using ItemRef = std::reference_wrapper<Item>;
|
||||
using TPlacer = PlacementStrategyLike<PlacementStrategy>;
|
||||
using BinType = typename TPlacer::BinType;
|
||||
using PlacementConfig = typename TPlacer::Config;
|
||||
using SelectionConfig = typename TSel::Config;
|
||||
|
||||
using Unit = TCoord<TPoint<typename Item::ShapeType>>;
|
||||
|
||||
using IndexedPackGroup = _IndexedPackGroup<typename Item::ShapeType>;
|
||||
using PackGroup = _PackGroup<typename Item::ShapeType>;
|
||||
|
||||
private:
|
||||
BinType bin_;
|
||||
PlacementConfig pconfig_;
|
||||
TCoord<typename Item::ShapeType> min_obj_distance_;
|
||||
|
||||
using SItem = typename SelectionStrategy::Item;
|
||||
using TPItem = remove_cvref_t<Item>;
|
||||
using TSItem = remove_cvref_t<SItem>;
|
||||
|
||||
std::vector<TPItem> item_cache_;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* \brief Constructor taking the bin as the only mandatory parameter.
|
||||
*
|
||||
* \param bin The bin shape that will be used by the placers. The type
|
||||
* of the bin should be one that is supported by the placer type.
|
||||
*/
|
||||
template<class TBinType = BinType,
|
||||
class PConf = PlacementConfig,
|
||||
class SConf = SelectionConfig>
|
||||
Arranger( TBinType&& bin,
|
||||
Unit min_obj_distance = 0,
|
||||
PConf&& pconfig = PConf(),
|
||||
SConf&& sconfig = SConf()):
|
||||
bin_(std::forward<TBinType>(bin)),
|
||||
pconfig_(std::forward<PlacementConfig>(pconfig)),
|
||||
min_obj_distance_(min_obj_distance)
|
||||
{
|
||||
static_assert( std::is_same<TPItem, TSItem>::value,
|
||||
"Incompatible placement and selection strategy!");
|
||||
|
||||
selector_.configure(std::forward<SelectionConfig>(sconfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Arrange an input sequence and return a PackGroup object with
|
||||
* the packed groups corresponding to the bins.
|
||||
*
|
||||
* The number of groups in the pack group is the number of bins opened by
|
||||
* the selection algorithm.
|
||||
*/
|
||||
template<class TIterator>
|
||||
inline PackGroup arrange(TIterator from, TIterator to)
|
||||
{
|
||||
return _arrange(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of the arrange method returning an IndexedPackGroup with
|
||||
* the item indexes into the original input sequence.
|
||||
*
|
||||
* Takes a little longer to collect the indices. Scales linearly with the
|
||||
* input sequence size.
|
||||
*/
|
||||
template<class TIterator>
|
||||
inline IndexedPackGroup arrangeIndexed(TIterator from, TIterator to)
|
||||
{
|
||||
return _arrangeIndexed(from, to);
|
||||
}
|
||||
|
||||
/// Shorthand to normal arrange method.
|
||||
template<class TIterator>
|
||||
inline PackGroup operator() (TIterator from, TIterator to)
|
||||
{
|
||||
return _arrange(from, to);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<class TIterator,
|
||||
class IT = remove_cvref_t<typename TIterator::value_type>,
|
||||
|
||||
// This funtion will be used only if the iterators are pointing to
|
||||
// a type compatible with the binpack2d::_Item template.
|
||||
// This way we can use references to input elements as they will
|
||||
// have to exist for the lifetime of this call.
|
||||
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
|
||||
>
|
||||
inline PackGroup _arrange(TIterator from, TIterator to, bool = false)
|
||||
{
|
||||
__arrange(from, to);
|
||||
|
||||
PackGroup ret;
|
||||
for(size_t i = 0; i < selector_.binCount(); i++) {
|
||||
auto items = selector_.itemsForBin(i);
|
||||
ret.push_back(items);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class TIterator,
|
||||
class IT = remove_cvref_t<typename TIterator::value_type>,
|
||||
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
|
||||
>
|
||||
inline PackGroup _arrange(TIterator from, TIterator to, int = false)
|
||||
{
|
||||
item_cache_ = {from, to};
|
||||
|
||||
__arrange(item_cache_.begin(), item_cache_.end());
|
||||
|
||||
PackGroup ret;
|
||||
for(size_t i = 0; i < selector_.binCount(); i++) {
|
||||
auto items = selector_.itemsForBin(i);
|
||||
ret.push_back(items);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class TIterator,
|
||||
class IT = remove_cvref_t<typename TIterator::value_type>,
|
||||
|
||||
// This funtion will be used only if the iterators are pointing to
|
||||
// a type compatible with the binpack2d::_Item template.
|
||||
// This way we can use references to input elements as they will
|
||||
// have to exist for the lifetime of this call.
|
||||
class T = enable_if_t< std::is_convertible<IT, TPItem>::value, IT>
|
||||
>
|
||||
inline IndexedPackGroup _arrangeIndexed(TIterator from,
|
||||
TIterator to,
|
||||
bool = false)
|
||||
{
|
||||
__arrange(from, to);
|
||||
return createIndexedPackGroup(from, to, selector_);
|
||||
}
|
||||
|
||||
template<class TIterator,
|
||||
class IT = remove_cvref_t<typename TIterator::value_type>,
|
||||
class T = enable_if_t<!std::is_convertible<IT, TPItem>::value, IT>
|
||||
>
|
||||
inline IndexedPackGroup _arrangeIndexed(TIterator from,
|
||||
TIterator to,
|
||||
int = false)
|
||||
{
|
||||
item_cache_ = {from, to};
|
||||
__arrange(item_cache_.begin(), item_cache_.end());
|
||||
return createIndexedPackGroup(from, to, selector_);
|
||||
}
|
||||
|
||||
template<class TIterator>
|
||||
static IndexedPackGroup createIndexedPackGroup(TIterator from,
|
||||
TIterator to,
|
||||
TSel& selector)
|
||||
{
|
||||
IndexedPackGroup pg;
|
||||
pg.reserve(selector.binCount());
|
||||
|
||||
for(size_t i = 0; i < selector.binCount(); i++) {
|
||||
auto items = selector.itemsForBin(i);
|
||||
pg.push_back({});
|
||||
pg[i].reserve(items.size());
|
||||
|
||||
for(Item& itemA : items) {
|
||||
auto it = from;
|
||||
unsigned idx = 0;
|
||||
while(it != to) {
|
||||
Item& itemB = *it;
|
||||
if(&itemB == &itemA) break;
|
||||
it++; idx++;
|
||||
}
|
||||
pg[i].emplace_back(idx, itemA);
|
||||
}
|
||||
}
|
||||
|
||||
return pg;
|
||||
}
|
||||
|
||||
template<class TIter> inline void __arrange(TIter from, TIter to)
|
||||
{
|
||||
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
|
||||
item.addOffset(std::ceil(min_obj_distance_/2.0));
|
||||
});
|
||||
|
||||
selector_.template packItems<PlacementStrategy>(
|
||||
from, to, bin_, pconfig_);
|
||||
|
||||
if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) {
|
||||
item.removeOffset();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // LIBNEST2D_HPP
|
391
xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp
Normal file
391
xs/src/libnest2d/libnest2d/placers/bottomleftplacer.hpp
Normal file
@ -0,0 +1,391 @@
|
||||
#ifndef BOTTOMLEFT_HPP
|
||||
#define BOTTOMLEFT_HPP
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "placer_boilerplate.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
template<class RawShape>
|
||||
struct BLConfig {
|
||||
TCoord<TPoint<RawShape>> min_obj_distance = 0;
|
||||
bool allow_rotations = false;
|
||||
};
|
||||
|
||||
template<class RawShape>
|
||||
class _BottomLeftPlacer: public PlacerBoilerplate<
|
||||
_BottomLeftPlacer<RawShape>,
|
||||
RawShape, _Box<TPoint<RawShape>>,
|
||||
BLConfig<RawShape> >
|
||||
{
|
||||
using Base = PlacerBoilerplate<_BottomLeftPlacer<RawShape>, RawShape,
|
||||
_Box<TPoint<RawShape>>, BLConfig<RawShape>>;
|
||||
DECLARE_PLACER(Base)
|
||||
|
||||
public:
|
||||
|
||||
explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
|
||||
|
||||
PackResult trypack(Item& item) {
|
||||
auto r = _trypack(item);
|
||||
if(!r && Base::config_.allow_rotations) {
|
||||
item.rotate(Degrees(90));
|
||||
r =_trypack(item);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
enum class Dir {
|
||||
LEFT,
|
||||
DOWN
|
||||
};
|
||||
|
||||
inline RawShape leftPoly(const Item& item) const {
|
||||
return toWallPoly(item, Dir::LEFT);
|
||||
}
|
||||
|
||||
inline RawShape downPoly(const Item& item) const {
|
||||
return toWallPoly(item, Dir::DOWN);
|
||||
}
|
||||
|
||||
inline Unit availableSpaceLeft(const Item& item) {
|
||||
return availableSpace(item, Dir::LEFT);
|
||||
}
|
||||
|
||||
inline Unit availableSpaceDown(const Item& item) {
|
||||
return availableSpace(item, Dir::DOWN);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
PackResult _trypack(Item& item) {
|
||||
|
||||
// Get initial position for item in the top right corner
|
||||
setInitialPosition(item);
|
||||
|
||||
Unit d = availableSpaceDown(item);
|
||||
bool can_move = d > 1 /*std::numeric_limits<Unit>::epsilon()*/;
|
||||
bool can_be_packed = can_move;
|
||||
bool left = true;
|
||||
|
||||
while(can_move) {
|
||||
if(left) { // write previous down move and go down
|
||||
item.translate({0, -d+1});
|
||||
d = availableSpaceLeft(item);
|
||||
can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
|
||||
left = false;
|
||||
} else { // write previous left move and go down
|
||||
item.translate({-d+1, 0});
|
||||
d = availableSpaceDown(item);
|
||||
can_move = d > 1/*std::numeric_limits<Unit>::epsilon()*/;
|
||||
left = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(can_be_packed) {
|
||||
Item trsh(item.transformedShape());
|
||||
for(auto& v : trsh) can_be_packed = can_be_packed &&
|
||||
getX(v) < bin_.width() &&
|
||||
getY(v) < bin_.height();
|
||||
}
|
||||
|
||||
return can_be_packed? PackResult(item) : PackResult();
|
||||
}
|
||||
|
||||
void setInitialPosition(Item& item) {
|
||||
auto bb = item.boundingBox();
|
||||
|
||||
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
|
||||
|
||||
|
||||
Coord dx = getX(bin_.maxCorner()) - getX(v);
|
||||
Coord dy = getY(bin_.maxCorner()) - getY(v);
|
||||
|
||||
item.translate({dx, dy});
|
||||
}
|
||||
|
||||
template<class C = Coord>
|
||||
static enable_if_t<std::is_floating_point<C>::value, bool>
|
||||
isInTheWayOf( const Item& item,
|
||||
const Item& other,
|
||||
const RawShape& scanpoly)
|
||||
{
|
||||
auto tsh = other.transformedShape();
|
||||
return ( ShapeLike::intersects(tsh, scanpoly) ||
|
||||
ShapeLike::isInside(tsh, scanpoly) ) &&
|
||||
( !ShapeLike::intersects(tsh, item.rawShape()) &&
|
||||
!ShapeLike::isInside(tsh, item.rawShape()) );
|
||||
}
|
||||
|
||||
template<class C = Coord>
|
||||
static enable_if_t<std::is_integral<C>::value, bool>
|
||||
isInTheWayOf( const Item& item,
|
||||
const Item& other,
|
||||
const RawShape& scanpoly)
|
||||
{
|
||||
auto tsh = other.transformedShape();
|
||||
|
||||
bool inters_scanpoly = ShapeLike::intersects(tsh, scanpoly) &&
|
||||
!ShapeLike::touches(tsh, scanpoly);
|
||||
bool inters_item = ShapeLike::intersects(tsh, item.rawShape()) &&
|
||||
!ShapeLike::touches(tsh, item.rawShape());
|
||||
|
||||
return ( inters_scanpoly ||
|
||||
ShapeLike::isInside(tsh, scanpoly)) &&
|
||||
( !inters_item &&
|
||||
!ShapeLike::isInside(tsh, item.rawShape())
|
||||
);
|
||||
}
|
||||
|
||||
Container itemsInTheWayOf(const Item& item, const Dir dir) {
|
||||
// Get the left or down polygon, that has the same area as the shadow
|
||||
// of input item reflected to the left or downwards
|
||||
auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) :
|
||||
downPoly(item);
|
||||
|
||||
Container ret; // packed items 'in the way' of item
|
||||
ret.reserve(items_.size());
|
||||
|
||||
// Predicate to find items that are 'in the way' for left (down) move
|
||||
auto predicate = [&scanpoly, &item](const Item& it) {
|
||||
return isInTheWayOf(item, it, scanpoly);
|
||||
};
|
||||
|
||||
// Get the items that are in the way for the left (or down) movement
|
||||
std::copy_if(items_.begin(), items_.end(),
|
||||
std::back_inserter(ret), predicate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Unit availableSpace(const Item& _item, const Dir dir) {
|
||||
|
||||
Item item (_item.transformedShape());
|
||||
|
||||
|
||||
std::function<Coord(const Vertex&)> getCoord;
|
||||
std::function< std::pair<Coord, bool>(const Segment&, const Vertex&) >
|
||||
availableDistanceSV;
|
||||
|
||||
std::function< std::pair<Coord, bool>(const Vertex&, const Segment&) >
|
||||
availableDistance;
|
||||
|
||||
if(dir == Dir::LEFT) {
|
||||
getCoord = [](const Vertex& v) { return getX(v); };
|
||||
availableDistance = PointLike::horizontalDistance<Vertex>;
|
||||
availableDistanceSV = [](const Segment& s, const Vertex& v) {
|
||||
auto ret = PointLike::horizontalDistance<Vertex>(v, s);
|
||||
if(ret.second) ret.first = -ret.first;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
else {
|
||||
getCoord = [](const Vertex& v) { return getY(v); };
|
||||
availableDistance = PointLike::verticalDistance<Vertex>;
|
||||
availableDistanceSV = [](const Segment& s, const Vertex& v) {
|
||||
auto ret = PointLike::verticalDistance<Vertex>(v, s);
|
||||
if(ret.second) ret.first = -ret.first;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
auto&& items_in_the_way = itemsInTheWayOf(item, dir);
|
||||
|
||||
// Comparison function for finding min vertex
|
||||
auto cmp = [&getCoord](const Vertex& v1, const Vertex& v2) {
|
||||
return getCoord(v1) < getCoord(v2);
|
||||
};
|
||||
|
||||
// find minimum left or down coordinate of item
|
||||
auto minvertex_it = std::min_element(item.begin(),
|
||||
item.end(),
|
||||
cmp);
|
||||
|
||||
// Get the initial distance in floating point
|
||||
Unit m = getCoord(*minvertex_it);
|
||||
|
||||
// Check available distance for every vertex of item to the objects
|
||||
// in the way for the nearest intersection
|
||||
if(!items_in_the_way.empty()) { // This is crazy, should be optimized...
|
||||
for(Item& pleft : items_in_the_way) {
|
||||
// For all segments in items_to_left
|
||||
|
||||
assert(pleft.vertexCount() > 0);
|
||||
|
||||
auto trpleft = pleft.transformedShape();
|
||||
auto first = ShapeLike::begin(trpleft);
|
||||
auto next = first + 1;
|
||||
auto endit = ShapeLike::end(trpleft);
|
||||
|
||||
while(next != endit) {
|
||||
Segment seg(*(first++), *(next++));
|
||||
for(auto& v : item) { // For all vertices in item
|
||||
|
||||
auto d = availableDistance(v, seg);
|
||||
|
||||
if(d.second && d.first < m) m = d.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto first = item.begin();
|
||||
auto next = first + 1;
|
||||
auto endit = item.end();
|
||||
|
||||
// For all edges in item:
|
||||
while(next != endit) {
|
||||
Segment seg(*(first++), *(next++));
|
||||
|
||||
// for all shapes in items_to_left
|
||||
for(Item& sh : items_in_the_way) {
|
||||
assert(sh.vertexCount() > 0);
|
||||
|
||||
Item tsh(sh.transformedShape());
|
||||
for(auto& v : tsh) { // For all vertices in item
|
||||
|
||||
auto d = availableDistanceSV(seg, v);
|
||||
|
||||
if(d.second && d.first < m) m = d.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the left (and down) polygon as described by
|
||||
* [López-Camacho et al. 2013]\
|
||||
* (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
|
||||
* see algorithm 8 for details...
|
||||
*/
|
||||
RawShape toWallPoly(const Item& _item, const Dir dir) const {
|
||||
// The variable names reflect the case of left polygon calculation.
|
||||
//
|
||||
// We will iterate through the item's vertices and search for the top
|
||||
// and bottom vertices (or right and left if dir==Dir::DOWN).
|
||||
// Save the relevant vertices and their indices into `bottom` and
|
||||
// `top` vectors. In case of left polygon construction these will
|
||||
// contain the top and bottom polygons which have the same vertical
|
||||
// coordinates (in case there is more of them).
|
||||
//
|
||||
// We get the leftmost (or downmost) vertex from the `bottom` and `top`
|
||||
// vectors and construct the final polygon.
|
||||
|
||||
Item item (_item.transformedShape());
|
||||
|
||||
auto getCoord = [dir](const Vertex& v) {
|
||||
return dir == Dir::LEFT? getY(v) : getX(v);
|
||||
};
|
||||
|
||||
Coord max_y = std::numeric_limits<Coord>::min();
|
||||
Coord min_y = std::numeric_limits<Coord>::max();
|
||||
|
||||
using El = std::pair<size_t, std::reference_wrapper<const Vertex>>;
|
||||
|
||||
std::function<bool(const El&, const El&)> cmp;
|
||||
|
||||
if(dir == Dir::LEFT)
|
||||
cmp = [](const El& e1, const El& e2) {
|
||||
return getX(e1.second.get()) < getX(e2.second.get());
|
||||
};
|
||||
else
|
||||
cmp = [](const El& e1, const El& e2) {
|
||||
return getY(e1.second.get()) < getY(e2.second.get());
|
||||
};
|
||||
|
||||
std::vector< El > top;
|
||||
std::vector< El > bottom;
|
||||
|
||||
size_t idx = 0;
|
||||
for(auto& v : item) { // Find the bottom and top vertices and save them
|
||||
auto vref = std::cref(v);
|
||||
auto vy = getCoord(v);
|
||||
|
||||
if( vy > max_y ) {
|
||||
max_y = vy;
|
||||
top.clear();
|
||||
top.emplace_back(idx, vref);
|
||||
}
|
||||
else if(vy == max_y) { top.emplace_back(idx, vref); }
|
||||
|
||||
if(vy < min_y) {
|
||||
min_y = vy;
|
||||
bottom.clear();
|
||||
bottom.emplace_back(idx, vref);
|
||||
}
|
||||
else if(vy == min_y) { bottom.emplace_back(idx, vref); }
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Get the top and bottom leftmost vertices, or the right and left
|
||||
// downmost vertices (if dir == Dir::DOWN)
|
||||
auto topleft_it = std::min_element(top.begin(), top.end(), cmp);
|
||||
auto bottomleft_it =
|
||||
std::min_element(bottom.begin(), bottom.end(), cmp);
|
||||
|
||||
auto& topleft_vertex = topleft_it->second.get();
|
||||
auto& bottomleft_vertex = bottomleft_it->second.get();
|
||||
|
||||
// Start and finish positions for the vertices that will be part of the
|
||||
// new polygon
|
||||
auto start = std::min(topleft_it->first, bottomleft_it->first);
|
||||
auto finish = std::max(topleft_it->first, bottomleft_it->first);
|
||||
|
||||
// the return shape
|
||||
RawShape rsh;
|
||||
|
||||
// reserve for all vertices plus 2 for the left horizontal wall, 2 for
|
||||
// the additional vertices for maintaning min object distance
|
||||
ShapeLike::reserve(rsh, finish-start+4);
|
||||
|
||||
/*auto addOthers = [&rsh, finish, start, &item](){
|
||||
for(size_t i = start+1; i < finish; i++)
|
||||
ShapeLike::addVertex(rsh, item.vertex(i));
|
||||
};*/
|
||||
|
||||
auto reverseAddOthers = [&rsh, finish, start, &item](){
|
||||
for(size_t i = finish-1; i > start; i--)
|
||||
ShapeLike::addVertex(rsh, item.vertex(i));
|
||||
};
|
||||
|
||||
// Final polygon construction...
|
||||
|
||||
static_assert(OrientationType<RawShape>::Value ==
|
||||
Orientation::CLOCKWISE,
|
||||
"Counter clockwise toWallPoly() Unimplemented!");
|
||||
|
||||
// Clockwise polygon construction
|
||||
|
||||
ShapeLike::addVertex(rsh, topleft_vertex);
|
||||
|
||||
if(dir == Dir::LEFT) reverseAddOthers();
|
||||
else {
|
||||
ShapeLike::addVertex(rsh, getX(topleft_vertex), 0);
|
||||
ShapeLike::addVertex(rsh, getX(bottomleft_vertex), 0);
|
||||
}
|
||||
|
||||
ShapeLike::addVertex(rsh, bottomleft_vertex);
|
||||
|
||||
if(dir == Dir::LEFT) {
|
||||
ShapeLike::addVertex(rsh, 0, getY(bottomleft_vertex));
|
||||
ShapeLike::addVertex(rsh, 0, getY(topleft_vertex));
|
||||
}
|
||||
else reverseAddOthers();
|
||||
|
||||
|
||||
// Close the polygon
|
||||
ShapeLike::addVertex(rsh, topleft_vertex);
|
||||
|
||||
return rsh;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BOTTOMLEFT_HPP
|
31
xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
Normal file
31
xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#ifndef NOFITPOLY_HPP
|
||||
#define NOFITPOLY_HPP
|
||||
|
||||
#include "placer_boilerplate.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
template<class RawShape>
|
||||
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
||||
RawShape, _Box<TPoint<RawShape>>> {
|
||||
|
||||
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
|
||||
RawShape, _Box<TPoint<RawShape>>>;
|
||||
|
||||
DECLARE_PLACER(Base)
|
||||
|
||||
public:
|
||||
|
||||
inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin) {}
|
||||
|
||||
PackResult trypack(Item& item) {
|
||||
|
||||
return PackResult();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // NOFITPOLY_H
|
102
xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp
Normal file
102
xs/src/libnest2d/libnest2d/placers/placer_boilerplate.hpp
Normal file
@ -0,0 +1,102 @@
|
||||
#ifndef PLACER_BOILERPLATE_HPP
|
||||
#define PLACER_BOILERPLATE_HPP
|
||||
|
||||
#include "../libnest2d.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
struct EmptyConfig {};
|
||||
|
||||
template<class Subclass, class RawShape, class TBin,
|
||||
class Cfg = EmptyConfig,
|
||||
class Store = std::vector<std::reference_wrapper<_Item<RawShape>>>
|
||||
>
|
||||
class PlacerBoilerplate {
|
||||
public:
|
||||
using Item = _Item<RawShape>;
|
||||
using Vertex = TPoint<RawShape>;
|
||||
using Segment = _Segment<Vertex>;
|
||||
using BinType = TBin;
|
||||
using Coord = TCoord<Vertex>;
|
||||
using Unit = Coord;
|
||||
using Config = Cfg;
|
||||
using Container = Store;
|
||||
|
||||
class PackResult {
|
||||
Item *item_ptr_;
|
||||
Vertex move_;
|
||||
Radians rot_;
|
||||
friend class PlacerBoilerplate;
|
||||
friend Subclass;
|
||||
PackResult(Item& item):
|
||||
item_ptr_(&item),
|
||||
move_(item.translation()),
|
||||
rot_(item.rotation()) {}
|
||||
PackResult(): item_ptr_(nullptr) {}
|
||||
public:
|
||||
operator bool() { return item_ptr_ != nullptr; }
|
||||
};
|
||||
|
||||
using ItemGroup = const Container&;
|
||||
|
||||
inline PlacerBoilerplate(const BinType& bin): bin_(bin) {}
|
||||
|
||||
inline const BinType& bin() const BP2D_NOEXCEPT { return bin_; }
|
||||
|
||||
template<class TB> inline void bin(TB&& b) {
|
||||
bin_ = std::forward<BinType>(b);
|
||||
}
|
||||
|
||||
inline void configure(const Config& config) BP2D_NOEXCEPT {
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
bool pack(Item& item) {
|
||||
auto&& r = static_cast<Subclass*>(this)->trypack(item);
|
||||
if(r) items_.push_back(*(r.item_ptr_));
|
||||
return r;
|
||||
}
|
||||
|
||||
void accept(PackResult& r) {
|
||||
if(r) {
|
||||
r.item_ptr_->translation(r.move_);
|
||||
r.item_ptr_->rotation(r.rot_);
|
||||
items_.push_back(*(r.item_ptr_));
|
||||
}
|
||||
}
|
||||
|
||||
void unpackLast() { items_.pop_back(); }
|
||||
|
||||
inline ItemGroup getItems() { return items_; }
|
||||
|
||||
inline void clearItems() { items_.clear(); }
|
||||
|
||||
protected:
|
||||
|
||||
BinType bin_;
|
||||
Container items_;
|
||||
Cfg config_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#define DECLARE_PLACER(Base) \
|
||||
using Base::bin_; \
|
||||
using Base::items_; \
|
||||
using Base::config_; \
|
||||
public: \
|
||||
using typename Base::Item; \
|
||||
using typename Base::BinType; \
|
||||
using typename Base::Config; \
|
||||
using typename Base::Vertex; \
|
||||
using typename Base::Segment; \
|
||||
using typename Base::PackResult; \
|
||||
using typename Base::Coord; \
|
||||
using typename Base::Unit; \
|
||||
using typename Base::Container; \
|
||||
private:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PLACER_BOILERPLATE_HPP
|
514
xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp
Normal file
514
xs/src/libnest2d/libnest2d/selections/djd_heuristic.hpp
Normal file
@ -0,0 +1,514 @@
|
||||
#ifndef DJD_HEURISTIC_HPP
|
||||
#define DJD_HEURISTIC_HPP
|
||||
|
||||
#include <list>
|
||||
#include "selection_boilerplate.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
/**
|
||||
* Selection heuristic based on [López-Camacho]\
|
||||
* (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
|
||||
*/
|
||||
template<class RawShape>
|
||||
class _DJDHeuristic: public SelectionBoilerplate<RawShape> {
|
||||
using Base = SelectionBoilerplate<RawShape>;
|
||||
public:
|
||||
using typename Base::Item;
|
||||
using typename Base::ItemRef;
|
||||
|
||||
/**
|
||||
* @brief The Config for DJD heuristic.
|
||||
*/
|
||||
struct Config {
|
||||
/// Max number of bins.
|
||||
unsigned max_bins = 0;
|
||||
|
||||
/**
|
||||
* If true, the algorithm will try to place pair and driplets in all
|
||||
* possible order.
|
||||
*/
|
||||
bool try_reverse_order = true;
|
||||
};
|
||||
|
||||
private:
|
||||
using Base::packed_bins_;
|
||||
using ItemGroup = typename Base::ItemGroup;
|
||||
|
||||
using Container = ItemGroup;//typename std::vector<Item>;
|
||||
Container store_;
|
||||
Config config_;
|
||||
|
||||
// The initial fill proportion of the bin area that will be filled before
|
||||
// trying items one by one, or pairs or triplets.
|
||||
static const double INITIAL_FILL_PROPORTION;
|
||||
|
||||
public:
|
||||
|
||||
inline void configure(const Config& config) {
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
template<class TPlacer, class TIterator,
|
||||
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
|
||||
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
|
||||
void packItems( TIterator first,
|
||||
TIterator last,
|
||||
const TBin& bin,
|
||||
PConfig&& pconfig = PConfig() )
|
||||
{
|
||||
using Placer = PlacementStrategyLike<TPlacer>;
|
||||
using ItemList = std::list<ItemRef>;
|
||||
|
||||
const double bin_area = ShapeLike::area<RawShape>(bin);
|
||||
const double w = bin_area * 0.1;
|
||||
const double INITIAL_FILL_AREA = bin_area*INITIAL_FILL_PROPORTION;
|
||||
|
||||
store_.clear();
|
||||
store_.reserve(last-first);
|
||||
packed_bins_.clear();
|
||||
|
||||
std::copy(first, last, std::back_inserter(store_));
|
||||
|
||||
std::sort(store_.begin(), store_.end(), [](Item& i1, Item& i2) {
|
||||
return i1.area() > i2.area();
|
||||
});
|
||||
|
||||
ItemList not_packed(store_.begin(), store_.end());
|
||||
|
||||
std::vector<Placer> placers;
|
||||
|
||||
double free_area = 0;
|
||||
double filled_area = 0;
|
||||
double waste = 0;
|
||||
bool try_reverse = config_.try_reverse_order;
|
||||
|
||||
// Will use a subroutine to add a new bin
|
||||
auto addBin = [&placers, &free_area, &filled_area, &bin, &pconfig]()
|
||||
{
|
||||
placers.emplace_back(bin);
|
||||
placers.back().configure(pconfig);
|
||||
free_area = ShapeLike::area<RawShape>(bin);
|
||||
filled_area = 0;
|
||||
};
|
||||
|
||||
// Types for pairs and triplets
|
||||
using TPair = std::tuple<ItemRef, ItemRef>;
|
||||
using TTriplet = std::tuple<ItemRef, ItemRef, ItemRef>;
|
||||
|
||||
|
||||
// Method for checking a pair whether it was a pack failure.
|
||||
auto check_pair = [](const std::vector<TPair>& wrong_pairs,
|
||||
ItemRef i1, ItemRef i2)
|
||||
{
|
||||
return std::any_of(wrong_pairs.begin(), wrong_pairs.end(),
|
||||
[&i1, &i2](const TPair& pair)
|
||||
{
|
||||
Item& pi1 = std::get<0>(pair), pi2 = std::get<1>(pair);
|
||||
Item& ri1 = i1, ri2 = i2;
|
||||
return (&pi1 == &ri1 && &pi2 == &ri2) ||
|
||||
(&pi1 == &ri2 && &pi2 == &ri1);
|
||||
});
|
||||
};
|
||||
|
||||
// Method for checking if a triplet was a pack failure
|
||||
auto check_triplet = [](
|
||||
const std::vector<TTriplet>& wrong_triplets,
|
||||
ItemRef i1,
|
||||
ItemRef i2,
|
||||
ItemRef i3)
|
||||
{
|
||||
return std::any_of(wrong_triplets.begin(),
|
||||
wrong_triplets.end(),
|
||||
[&i1, &i2, &i3](const TTriplet& tripl)
|
||||
{
|
||||
Item& pi1 = std::get<0>(tripl);
|
||||
Item& pi2 = std::get<1>(tripl);
|
||||
Item& pi3 = std::get<2>(tripl);
|
||||
Item& ri1 = i1, ri2 = i2, ri3 = i3;
|
||||
return (&pi1 == &ri1 && &pi2 == &ri2 && &pi3 == &ri3) ||
|
||||
(&pi1 == &ri1 && &pi2 == &ri3 && &pi3 == &ri2) ||
|
||||
(&pi1 == &ri2 && &pi2 == &ri1 && &pi3 == &ri3) ||
|
||||
(&pi1 == &ri3 && &pi2 == &ri2 && &pi3 == &ri1);
|
||||
});
|
||||
};
|
||||
|
||||
auto tryOneByOne = // Subroutine to try adding items one by one.
|
||||
[¬_packed, &bin_area, &free_area, &filled_area]
|
||||
(Placer& placer, double waste)
|
||||
{
|
||||
double item_area = 0;
|
||||
bool ret = false;
|
||||
auto it = not_packed.begin();
|
||||
|
||||
while(it != not_packed.end() && !ret &&
|
||||
free_area - (item_area = it->get().area()) <= waste)
|
||||
{
|
||||
if(item_area <= free_area && placer.pack(*it) ) {
|
||||
free_area -= item_area;
|
||||
filled_area = bin_area - free_area;
|
||||
ret = true;
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
|
||||
if(ret) not_packed.erase(it);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto tryGroupsOfTwo = // Try adding groups of two items into the bin.
|
||||
[¬_packed, &bin_area, &free_area, &filled_area, &check_pair,
|
||||
try_reverse]
|
||||
(Placer& placer, double waste)
|
||||
{
|
||||
double item_area = 0, largest_area = 0, smallest_area = 0;
|
||||
double second_largest = 0, second_smallest = 0;
|
||||
|
||||
const auto endit = not_packed.end();
|
||||
|
||||
if(not_packed.size() < 2)
|
||||
return false; // No group of two items
|
||||
else {
|
||||
largest_area = not_packed.front().get().area();
|
||||
auto itmp = not_packed.begin(); itmp++;
|
||||
second_largest = itmp->get().area();
|
||||
if( free_area - second_largest - largest_area > waste)
|
||||
return false; // If even the largest two items do not fill
|
||||
// the bin to the desired waste than we can end here.
|
||||
|
||||
smallest_area = not_packed.back().get().area();
|
||||
itmp = endit; std::advance(itmp, -2);
|
||||
second_smallest = itmp->get().area();
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
auto it = not_packed.begin();
|
||||
auto it2 = it;
|
||||
|
||||
std::vector<TPair> wrong_pairs;
|
||||
|
||||
double largest = second_largest;
|
||||
double smallest= smallest_area;
|
||||
while(it != endit && !ret && free_area -
|
||||
(item_area = it->get().area()) - largest <= waste )
|
||||
{
|
||||
// if this is the last element, the next smallest is the
|
||||
// previous item
|
||||
auto itmp = it; std::advance(itmp, 1);
|
||||
if(itmp == endit) smallest = second_smallest;
|
||||
|
||||
if(item_area + smallest > free_area ) { it++; continue; }
|
||||
|
||||
auto pr = placer.trypack(*it);
|
||||
|
||||
// First would fit
|
||||
it2 = not_packed.begin();
|
||||
double item2_area = 0;
|
||||
while(it2 != endit && pr && !ret && free_area -
|
||||
(item2_area = it2->get().area()) - item_area <= waste)
|
||||
{
|
||||
double area_sum = item_area + item2_area;
|
||||
|
||||
if(it == it2 || area_sum > free_area ||
|
||||
check_pair(wrong_pairs, *it, *it2)) {
|
||||
it2++; continue;
|
||||
}
|
||||
|
||||
placer.accept(pr);
|
||||
auto pr2 = placer.trypack(*it2);
|
||||
if(!pr2) {
|
||||
placer.unpackLast(); // remove first
|
||||
if(try_reverse) {
|
||||
pr2 = placer.trypack(*it2);
|
||||
if(pr2) {
|
||||
placer.accept(pr2);
|
||||
auto pr12 = placer.trypack(*it);
|
||||
if(pr12) {
|
||||
placer.accept(pr12);
|
||||
ret = true;
|
||||
} else {
|
||||
placer.unpackLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
placer.accept(pr2); ret = true;
|
||||
}
|
||||
|
||||
if(ret)
|
||||
{ // Second fits as well
|
||||
free_area -= area_sum;
|
||||
filled_area = bin_area - free_area;
|
||||
} else {
|
||||
wrong_pairs.emplace_back(*it, *it2);
|
||||
it2++;
|
||||
}
|
||||
}
|
||||
|
||||
if(!ret) it++;
|
||||
|
||||
largest = largest_area;
|
||||
}
|
||||
|
||||
if(ret) { not_packed.erase(it); not_packed.erase(it2); }
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto tryGroupsOfThree = // Try adding groups of three items.
|
||||
[¬_packed, &bin_area, &free_area, &filled_area,
|
||||
&check_pair, &check_triplet, try_reverse]
|
||||
(Placer& placer, double waste)
|
||||
{
|
||||
|
||||
if(not_packed.size() < 3) return false;
|
||||
|
||||
auto it = not_packed.begin(); // from
|
||||
const auto endit = not_packed.end(); // to
|
||||
auto it2 = it, it3 = it;
|
||||
|
||||
// Containers for pairs and triplets that were tried before and
|
||||
// do not work.
|
||||
std::vector<TPair> wrong_pairs;
|
||||
std::vector<TTriplet> wrong_triplets;
|
||||
|
||||
// Will be true if a succesfull pack can be made.
|
||||
bool ret = false;
|
||||
|
||||
while (it != endit && !ret) { // drill down 1st level
|
||||
|
||||
// We need to determine in each iteration the largest, second
|
||||
// largest, smallest and second smallest item in terms of area.
|
||||
|
||||
auto first = not_packed.begin();
|
||||
Item& largest = it == first? *std::next(it) : *first;
|
||||
|
||||
auto second = std::next(first);
|
||||
Item& second_largest = it == second ? *std::next(it) : *second;
|
||||
|
||||
double area_of_two_largest =
|
||||
largest.area() + second_largest.area();
|
||||
|
||||
// Check if there is enough free area for the item and the two
|
||||
// largest item
|
||||
if(free_area - it->get().area() - area_of_two_largest > waste)
|
||||
break;
|
||||
|
||||
// Determine the area of the two smallest item.
|
||||
auto last = std::prev(endit);
|
||||
Item& smallest = it == last? *std::prev(it) : *last;
|
||||
auto second_last = std::prev(last);
|
||||
Item& second_smallest = it == second_last? *std::prev(it) :
|
||||
*second_last;
|
||||
|
||||
// Check if there is enough free area for the item and the two
|
||||
// smallest item.
|
||||
double area_of_two_smallest =
|
||||
smallest.area() + second_smallest.area();
|
||||
|
||||
auto pr = placer.trypack(*it);
|
||||
|
||||
// Check for free area and try to pack the 1st item...
|
||||
if(!pr || it->get().area() + area_of_two_smallest > free_area) {
|
||||
it++; continue;
|
||||
}
|
||||
|
||||
it2 = not_packed.begin();
|
||||
double rem2_area = free_area - largest.area();
|
||||
double a2_sum = it->get().area() + it2->get().area();
|
||||
|
||||
while(it2 != endit && !ret &&
|
||||
rem2_area - a2_sum <= waste) { // Drill down level 2
|
||||
|
||||
if(it == it2 || check_pair(wrong_pairs, *it, *it2)) {
|
||||
it2++; continue;
|
||||
}
|
||||
|
||||
a2_sum = it->get().area() + it2->get().area();
|
||||
if(a2_sum + smallest.area() > free_area) {
|
||||
it2++; continue;
|
||||
}
|
||||
|
||||
bool can_pack2 = false;
|
||||
|
||||
placer.accept(pr);
|
||||
auto pr2 = placer.trypack(*it2);
|
||||
auto pr12 = pr;
|
||||
if(!pr2) {
|
||||
placer.unpackLast(); // remove first
|
||||
if(try_reverse) {
|
||||
pr2 = placer.trypack(*it2);
|
||||
if(pr2) {
|
||||
placer.accept(pr2);
|
||||
pr12 = placer.trypack(*it);
|
||||
if(pr12) can_pack2 = true;
|
||||
placer.unpackLast();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
placer.unpackLast();
|
||||
can_pack2 = true;
|
||||
}
|
||||
|
||||
if(!can_pack2) {
|
||||
wrong_pairs.emplace_back(*it, *it2);
|
||||
it2++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we have packed a group of 2 items.
|
||||
// The 'smallest' variable now could be identical with
|
||||
// it2 but we don't bother with that
|
||||
|
||||
if(!can_pack2) { it2++; continue; }
|
||||
|
||||
it3 = not_packed.begin();
|
||||
|
||||
double a3_sum = a2_sum + it3->get().area();
|
||||
|
||||
while(it3 != endit && !ret &&
|
||||
free_area - a3_sum <= waste) { // 3rd level
|
||||
|
||||
if(it3 == it || it3 == it2 ||
|
||||
check_triplet(wrong_triplets, *it, *it2, *it3))
|
||||
{ it3++; continue; }
|
||||
|
||||
placer.accept(pr12); placer.accept(pr2);
|
||||
bool can_pack3 = placer.pack(*it3);
|
||||
|
||||
if(!can_pack3) {
|
||||
placer.unpackLast();
|
||||
placer.unpackLast();
|
||||
}
|
||||
|
||||
if(!can_pack3 && try_reverse) {
|
||||
|
||||
std::array<size_t, 3> indices = {0, 1, 2};
|
||||
std::array<ItemRef, 3>
|
||||
candidates = {*it, *it2, *it3};
|
||||
|
||||
auto tryPack = [&placer, &candidates](
|
||||
const decltype(indices)& idx)
|
||||
{
|
||||
std::array<bool, 3> packed = {false};
|
||||
|
||||
for(auto id : idx) packed[id] =
|
||||
placer.pack(candidates[id]);
|
||||
|
||||
bool check =
|
||||
std::all_of(packed.begin(),
|
||||
packed.end(),
|
||||
[](bool b) { return b; });
|
||||
|
||||
if(!check) for(bool b : packed) if(b)
|
||||
placer.unpackLast();
|
||||
|
||||
return check;
|
||||
};
|
||||
|
||||
while (!can_pack3 && std::next_permutation(
|
||||
indices.begin(),
|
||||
indices.end())){
|
||||
can_pack3 = tryPack(indices);
|
||||
};
|
||||
}
|
||||
|
||||
if(can_pack3) {
|
||||
// finishit
|
||||
free_area -= a3_sum;
|
||||
filled_area = bin_area - free_area;
|
||||
ret = true;
|
||||
} else {
|
||||
wrong_triplets.emplace_back(*it, *it2, *it3);
|
||||
it3++;
|
||||
}
|
||||
|
||||
} // 3rd while
|
||||
|
||||
if(!ret) it2++;
|
||||
|
||||
} // Second while
|
||||
|
||||
if(!ret) it++;
|
||||
|
||||
} // First while
|
||||
|
||||
if(ret) { // If we eventually succeeded, remove all the packed ones.
|
||||
not_packed.erase(it);
|
||||
not_packed.erase(it2);
|
||||
not_packed.erase(it3);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
addBin();
|
||||
|
||||
// Safety test: try to pack each item into an empty bin. If it fails
|
||||
// then it should be removed from the not_packed list
|
||||
{ auto it = not_packed.begin();
|
||||
while (it != not_packed.end()) {
|
||||
Placer p(bin);
|
||||
if(!p.pack(*it)) {
|
||||
auto itmp = it++;
|
||||
not_packed.erase(itmp);
|
||||
} else it++;
|
||||
}
|
||||
}
|
||||
|
||||
while(!not_packed.empty()) {
|
||||
|
||||
auto& placer = placers.back();
|
||||
|
||||
{// Fill the bin up to INITIAL_FILL_PROPORTION of its capacity
|
||||
auto it = not_packed.begin();
|
||||
|
||||
while(it != not_packed.end() &&
|
||||
filled_area < INITIAL_FILL_AREA)
|
||||
{
|
||||
if(placer.pack(*it)) {
|
||||
filled_area += it->get().area();
|
||||
free_area = bin_area - filled_area;
|
||||
auto itmp = it++;
|
||||
not_packed.erase(itmp);
|
||||
} else it++;
|
||||
}
|
||||
}
|
||||
|
||||
// try pieses one by one
|
||||
while(tryOneByOne(placer, waste))
|
||||
waste = 0;
|
||||
|
||||
// try groups of 2 pieses
|
||||
while(tryGroupsOfTwo(placer, waste))
|
||||
waste = 0;
|
||||
|
||||
// try groups of 3 pieses
|
||||
while(tryGroupsOfThree(placer, waste))
|
||||
waste = 0;
|
||||
|
||||
if(waste < free_area) waste += w;
|
||||
else if(!not_packed.empty()) addBin();
|
||||
}
|
||||
|
||||
std::for_each(placers.begin(), placers.end(),
|
||||
[this](Placer& placer){
|
||||
packed_bins_.push_back(placer.getItems());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* The initial fill proportion suggested by
|
||||
* [López-Camacho]\
|
||||
* (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
|
||||
* is one third of the area of bin.
|
||||
*/
|
||||
template<class RawShape>
|
||||
const double _DJDHeuristic<RawShape>::INITIAL_FILL_PROPORTION = 1.0/3.0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DJD_HEURISTIC_HPP
|
71
xs/src/libnest2d/libnest2d/selections/filler.hpp
Normal file
71
xs/src/libnest2d/libnest2d/selections/filler.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#ifndef FILLER_HPP
|
||||
#define FILLER_HPP
|
||||
|
||||
#include "selection_boilerplate.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
template<class RawShape>
|
||||
class _FillerSelection: public SelectionBoilerplate<RawShape> {
|
||||
using Base = SelectionBoilerplate<RawShape>;
|
||||
public:
|
||||
using typename Base::Item;
|
||||
using Config = int; //dummy
|
||||
|
||||
private:
|
||||
using Base::packed_bins_;
|
||||
using typename Base::ItemGroup;
|
||||
using Container = ItemGroup;
|
||||
Container store_;
|
||||
|
||||
public:
|
||||
|
||||
void configure(const Config& /*config*/) { }
|
||||
|
||||
template<class TPlacer, class TIterator,
|
||||
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
|
||||
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
|
||||
void packItems(TIterator first,
|
||||
TIterator last,
|
||||
TBin&& bin,
|
||||
PConfig&& pconfig = PConfig())
|
||||
{
|
||||
|
||||
store_.clear();
|
||||
store_.reserve(last-first);
|
||||
packed_bins_.clear();
|
||||
|
||||
std::copy(first, last, std::back_inserter(store_));
|
||||
|
||||
auto sortfunc = [](Item& i1, Item& i2) {
|
||||
return i1.area() > i2.area();
|
||||
};
|
||||
|
||||
std::sort(store_.begin(), store_.end(), sortfunc);
|
||||
|
||||
// Container a = {store_[0], store_[1], store_[4], store_[5] };
|
||||
//// a.insert(a.end(), store_.end()-10, store_.end());
|
||||
// store_ = a;
|
||||
|
||||
PlacementStrategyLike<TPlacer> placer(bin);
|
||||
placer.configure(pconfig);
|
||||
|
||||
bool was_packed = false;
|
||||
for(auto& item : store_ ) {
|
||||
if(!placer.pack(item)) {
|
||||
packed_bins_.push_back(placer.getItems());
|
||||
placer.clearItems();
|
||||
was_packed = placer.pack(item);
|
||||
} else was_packed = true;
|
||||
}
|
||||
|
||||
if(was_packed) {
|
||||
packed_bins_.push_back(placer.getItems());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BOTTOMLEFT_HPP
|
77
xs/src/libnest2d/libnest2d/selections/firstfit.hpp
Normal file
77
xs/src/libnest2d/libnest2d/selections/firstfit.hpp
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef FIRSTFIT_HPP
|
||||
#define FIRSTFIT_HPP
|
||||
|
||||
#include "../libnest2d.hpp"
|
||||
#include "selection_boilerplate.hpp"
|
||||
|
||||
namespace libnest2d { namespace strategies {
|
||||
|
||||
template<class RawShape>
|
||||
class _FirstFitSelection: public SelectionBoilerplate<RawShape> {
|
||||
using Base = SelectionBoilerplate<RawShape>;
|
||||
public:
|
||||
using typename Base::Item;
|
||||
using Config = int; //dummy
|
||||
|
||||
private:
|
||||
using Base::packed_bins_;
|
||||
using typename Base::ItemGroup;
|
||||
using Container = ItemGroup;//typename std::vector<_Item<RawShape>>;
|
||||
|
||||
Container store_;
|
||||
|
||||
public:
|
||||
|
||||
void configure(const Config& /*config*/) { }
|
||||
|
||||
template<class TPlacer, class TIterator,
|
||||
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
|
||||
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
|
||||
void packItems(TIterator first,
|
||||
TIterator last,
|
||||
TBin&& bin,
|
||||
PConfig&& pconfig = PConfig())
|
||||
{
|
||||
|
||||
using Placer = PlacementStrategyLike<TPlacer>;
|
||||
|
||||
store_.clear();
|
||||
store_.reserve(last-first);
|
||||
packed_bins_.clear();
|
||||
|
||||
std::vector<Placer> placers;
|
||||
|
||||
std::copy(first, last, std::back_inserter(store_));
|
||||
|
||||
auto sortfunc = [](Item& i1, Item& i2) {
|
||||
return i1.area() > i2.area();
|
||||
};
|
||||
|
||||
std::sort(store_.begin(), store_.end(), sortfunc);
|
||||
|
||||
for(auto& item : store_ ) {
|
||||
bool was_packed = false;
|
||||
while(!was_packed) {
|
||||
|
||||
for(size_t j = 0; j < placers.size() && !was_packed; j++)
|
||||
was_packed = placers[j].pack(item);
|
||||
|
||||
if(!was_packed) {
|
||||
placers.emplace_back(bin);
|
||||
placers.back().configure(pconfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::for_each(placers.begin(), placers.end(),
|
||||
[this](Placer& placer){
|
||||
packed_bins_.push_back(placer.getItems());
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FIRSTFIT_HPP
|
@ -0,0 +1,36 @@
|
||||
#ifndef SELECTION_BOILERPLATE_HPP
|
||||
#define SELECTION_BOILERPLATE_HPP
|
||||
|
||||
#include "../libnest2d.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
namespace strategies {
|
||||
|
||||
template<class RawShape>
|
||||
class SelectionBoilerplate {
|
||||
public:
|
||||
using Item = _Item<RawShape>;
|
||||
using ItemRef = std::reference_wrapper<Item>;
|
||||
using ItemGroup = std::vector<ItemRef>;
|
||||
using PackGroup = std::vector<ItemGroup>;
|
||||
|
||||
size_t binCount() const { return packed_bins_.size(); }
|
||||
|
||||
ItemGroup itemsForBin(size_t binIndex) {
|
||||
assert(binIndex < packed_bins_.size());
|
||||
return packed_bins_[binIndex];
|
||||
}
|
||||
|
||||
inline const ItemGroup itemsForBin(size_t binIndex) const {
|
||||
assert(binIndex < packed_bins_.size());
|
||||
return packed_bins_[binIndex];
|
||||
}
|
||||
|
||||
protected:
|
||||
PackGroup packed_bins_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SELECTION_BOILERPLATE_HPP
|
48
xs/src/libnest2d/tests/CMakeLists.txt
Normal file
48
xs/src/libnest2d/tests/CMakeLists.txt
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
# Try to find existing GTest installation
|
||||
find_package(GTest QUIET)
|
||||
|
||||
if(NOT GTEST_FOUND)
|
||||
# Go and download google test framework, integrate it with the build
|
||||
set(GTEST_LIBRARIES gtest gmock)
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.2)
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "")
|
||||
else()
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
|
||||
endif()
|
||||
|
||||
include(DownloadProject)
|
||||
download_project(PROJ googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG release-1.8.0
|
||||
${UPDATE_DISCONNECTED_IF_AVAILABLE}
|
||||
)
|
||||
|
||||
# Prevent GoogleTest from overriding our compiler/linker options
|
||||
# when building with Visual Studio
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(${googletest_SOURCE_DIR}
|
||||
${googletest_BINARY_DIR}
|
||||
)
|
||||
|
||||
else()
|
||||
include_directories(${GTEST_INCLUDE_DIRS} )
|
||||
endif()
|
||||
|
||||
include_directories(BEFORE ${LIBNEST2D_HEADERS})
|
||||
add_executable(bp2d_tests test.cpp printer_parts.h printer_parts.cpp)
|
||||
target_link_libraries(bp2d_tests libnest2d
|
||||
${GTEST_LIBRARIES}
|
||||
)
|
||||
|
||||
if(DEFINED LIBNEST2D_TEST_LIBRARIES)
|
||||
target_link_libraries(bp2d_tests ${LIBNEST2D_TEST_LIBRARIES})
|
||||
endif()
|
||||
|
||||
add_test(gtests bp2d_tests)
|
||||
|
||||
add_executable(main EXCLUDE_FROM_ALL main.cpp printer_parts.cpp printer_parts.h)
|
||||
target_link_libraries(main libnest2d)
|
||||
target_include_directories(main PUBLIC ${CMAKE_SOURCE_DIR})
|
58
xs/src/libnest2d/tests/benchmark.h
Normal file
58
xs/src/libnest2d/tests/benchmark.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) Tamás Mészáros
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* 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 General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU 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 INCLUDE_BENCHMARK_H_
|
||||
#define INCLUDE_BENCHMARK_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
/**
|
||||
* A class for doing benchmarks.
|
||||
*/
|
||||
class Benchmark {
|
||||
typedef std::chrono::high_resolution_clock Clock;
|
||||
typedef Clock::duration Duration;
|
||||
typedef Clock::time_point TimePoint;
|
||||
|
||||
TimePoint t1, t2;
|
||||
Duration d;
|
||||
|
||||
inline double to_sec(Duration d) {
|
||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Measure time from the moment of this call.
|
||||
*/
|
||||
void start() { t1 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Measure time to the moment of this call.
|
||||
*/
|
||||
void stop() { t2 = Clock::now(); }
|
||||
|
||||
/**
|
||||
* Get the time elapsed between a start() end a stop() call.
|
||||
* @return Returns the elapsed time in seconds.
|
||||
*/
|
||||
double getElapsedSec() { d = t2 - t1; return to_sec(d); }
|
||||
};
|
||||
|
||||
|
||||
#endif /* INCLUDE_BENCHMARK_H_ */
|
260
xs/src/libnest2d/tests/main.cpp
Normal file
260
xs/src/libnest2d/tests/main.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <libnest2d.h>
|
||||
#include <libnest2d/geometries_io.hpp>
|
||||
|
||||
#include "printer_parts.h"
|
||||
#include "benchmark.h"
|
||||
|
||||
namespace {
|
||||
using namespace libnest2d;
|
||||
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
||||
//using PackGroup = std::vector<ItemGroup>;
|
||||
|
||||
template<int SCALE, class Bin >
|
||||
void exportSVG(PackGroup& result, const Bin& bin) {
|
||||
|
||||
std::string loc = "out";
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)raw";
|
||||
|
||||
int i = 0;
|
||||
for(auto r : result) {
|
||||
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
|
||||
if(out.is_open()) {
|
||||
out << svg_header;
|
||||
Item rbin( Rectangle(bin.width(), bin.height()) );
|
||||
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
|
||||
auto v = rbin.vertex(i);
|
||||
setY(v, -getY(v)/SCALE + 500 );
|
||||
setX(v, getX(v)/SCALE);
|
||||
rbin.setVertex(i, v);
|
||||
}
|
||||
out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
|
||||
for(Item& sh : r) {
|
||||
Item tsh(sh.transformedShape());
|
||||
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
|
||||
auto v = tsh.vertex(i);
|
||||
setY(v, -getY(v)/SCALE + 500);
|
||||
setX(v, getX(v)/SCALE);
|
||||
tsh.setVertex(i, v);
|
||||
}
|
||||
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
|
||||
}
|
||||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
template< int SCALE, class Bin>
|
||||
void exportSVG(ItemGroup& result, const Bin& bin, int idx) {
|
||||
|
||||
std::string loc = "out";
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)raw";
|
||||
|
||||
int i = idx;
|
||||
auto r = result;
|
||||
// for(auto r : result) {
|
||||
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
|
||||
if(out.is_open()) {
|
||||
out << svg_header;
|
||||
Item rbin( Rectangle(bin.width(), bin.height()) );
|
||||
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
|
||||
auto v = rbin.vertex(i);
|
||||
setY(v, -getY(v)/SCALE + 500 );
|
||||
setX(v, getX(v)/SCALE);
|
||||
rbin.setVertex(i, v);
|
||||
}
|
||||
out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
|
||||
for(Item& sh : r) {
|
||||
Item tsh(sh.transformedShape());
|
||||
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
|
||||
auto v = tsh.vertex(i);
|
||||
setY(v, -getY(v)/SCALE + 500);
|
||||
setX(v, getX(v)/SCALE);
|
||||
tsh.setVertex(i, v);
|
||||
}
|
||||
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
|
||||
}
|
||||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
// i++;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void findDegenerateCase() {
|
||||
using namespace libnest2d;
|
||||
|
||||
auto input = PRINTER_PART_POLYGONS;
|
||||
|
||||
auto scaler = [](Item& item) {
|
||||
for(unsigned i = 0; i < item.vertexCount(); i++) {
|
||||
auto v = item.vertex(i);
|
||||
setX(v, 100*getX(v)); setY(v, 100*getY(v));
|
||||
item.setVertex(i, v);
|
||||
}
|
||||
};
|
||||
|
||||
auto cmp = [](const Item& t1, const Item& t2) {
|
||||
return t1.area() > t2.area();
|
||||
};
|
||||
|
||||
std::for_each(input.begin(), input.end(), scaler);
|
||||
|
||||
std::sort(input.begin(), input.end(), cmp);
|
||||
|
||||
Box bin(210*100, 250*100);
|
||||
BottomLeftPlacer placer(bin);
|
||||
|
||||
auto it = input.begin();
|
||||
auto next = it;
|
||||
int i = 0;
|
||||
while(it != input.end() && ++next != input.end()) {
|
||||
placer.pack(*it);
|
||||
placer.pack(*next);
|
||||
|
||||
auto result = placer.getItems();
|
||||
bool valid = true;
|
||||
|
||||
if(result.size() == 2) {
|
||||
Item& r1 = result[0];
|
||||
Item& r2 = result[1];
|
||||
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
|
||||
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
|
||||
if(!valid) {
|
||||
std::cout << "error index: " << i << std::endl;
|
||||
exportSVG<100>(result, bin, i);
|
||||
}
|
||||
} else {
|
||||
std::cout << "something went terribly wrong!" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
placer.clearItems();
|
||||
it++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void arrangeRectangles() {
|
||||
using namespace libnest2d;
|
||||
|
||||
|
||||
// std::vector<Rectangle> input = {
|
||||
// {80, 80},
|
||||
// {110, 10},
|
||||
// {200, 5},
|
||||
// {80, 30},
|
||||
// {60, 90},
|
||||
// {70, 30},
|
||||
// {80, 60},
|
||||
// {60, 60},
|
||||
// {60, 40},
|
||||
// {40, 40},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {20, 20},
|
||||
// {80, 80},
|
||||
// {110, 10},
|
||||
// {200, 5},
|
||||
// {80, 30},
|
||||
// {60, 90},
|
||||
// {70, 30},
|
||||
// {80, 60},
|
||||
// {60, 60},
|
||||
// {60, 40},
|
||||
// {40, 40},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {10, 10},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {5, 5},
|
||||
// {20, 20}
|
||||
// };
|
||||
|
||||
auto input = PRINTER_PART_POLYGONS;
|
||||
|
||||
const int SCALE = 1000000;
|
||||
// const int SCALE = 1;
|
||||
|
||||
Box bin(210*SCALE, 250*SCALE);
|
||||
|
||||
auto scaler = [&SCALE, &bin](Item& item) {
|
||||
// double max_area = 0;
|
||||
for(unsigned i = 0; i < item.vertexCount(); i++) {
|
||||
auto v = item.vertex(i);
|
||||
setX(v, SCALE*getX(v)); setY(v, SCALE*getY(v));
|
||||
item.setVertex(i, v);
|
||||
// double area = item.area();
|
||||
// if(max_area < area) {
|
||||
// max_area = area;
|
||||
// bin = item.boundingBox();
|
||||
// }
|
||||
}
|
||||
};
|
||||
|
||||
Coord min_obj_distance = 2*SCALE;
|
||||
|
||||
std::for_each(input.begin(), input.end(), scaler);
|
||||
|
||||
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(bin, min_obj_distance);
|
||||
|
||||
Benchmark bench;
|
||||
|
||||
bench.start();
|
||||
auto result = arrange(input.begin(),
|
||||
input.end());
|
||||
|
||||
bench.stop();
|
||||
|
||||
std::cout << bench.getElapsedSec() << std::endl;
|
||||
|
||||
for(auto& it : input) {
|
||||
auto ret = ShapeLike::isValid(it.transformedShape());
|
||||
std::cout << ret.second << std::endl;
|
||||
}
|
||||
|
||||
exportSVG<SCALE>(result, bin);
|
||||
|
||||
}
|
||||
|
||||
int main(void /*int argc, char **argv*/) {
|
||||
arrangeRectangles();
|
||||
// findDegenerateCase();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
339
xs/src/libnest2d/tests/printer_parts.cpp
Normal file
339
xs/src/libnest2d/tests/printer_parts.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
#include "printer_parts.h"
|
||||
|
||||
const std::vector<libnest2d::Item> PRINTER_PART_POLYGONS = {
|
||||
{
|
||||
{120, 114},
|
||||
{130, 114},
|
||||
{130, 103},
|
||||
{128, 96},
|
||||
{122, 96},
|
||||
{120, 103},
|
||||
{120, 114}
|
||||
},
|
||||
{
|
||||
{61, 97},
|
||||
{70, 151},
|
||||
{176, 151},
|
||||
{189, 138},
|
||||
{189, 59},
|
||||
{70, 59},
|
||||
{61, 77},
|
||||
{61, 97}
|
||||
},
|
||||
{
|
||||
{72, 147},
|
||||
{94, 151},
|
||||
{178, 151},
|
||||
{178, 59},
|
||||
{72, 59},
|
||||
{72, 147}
|
||||
},
|
||||
{
|
||||
{121, 119},
|
||||
{123, 119},
|
||||
{129, 109},
|
||||
{129, 107},
|
||||
{128, 100},
|
||||
{127, 98},
|
||||
{123, 91},
|
||||
{121, 91},
|
||||
{121, 119},
|
||||
},
|
||||
{
|
||||
{93, 104},
|
||||
{100, 146},
|
||||
{107, 152},
|
||||
{136, 152},
|
||||
{142, 146},
|
||||
{157, 68},
|
||||
{157, 61},
|
||||
{154, 58},
|
||||
{104, 58},
|
||||
{93, 101},
|
||||
{93, 104},
|
||||
},
|
||||
{
|
||||
{90, 91},
|
||||
{114, 130},
|
||||
{158, 130},
|
||||
{163, 126},
|
||||
{163, 123},
|
||||
{152, 80},
|
||||
{116, 80},
|
||||
{90, 81},
|
||||
{87, 86},
|
||||
{90, 91},
|
||||
},
|
||||
{
|
||||
{111, 114},
|
||||
{114, 122},
|
||||
{139, 122},
|
||||
{139, 88},
|
||||
{114, 88},
|
||||
{111, 97},
|
||||
{111, 114},
|
||||
},
|
||||
{
|
||||
{120, 107},
|
||||
{125, 110},
|
||||
{130, 110},
|
||||
{130, 100},
|
||||
{120, 100},
|
||||
{120, 107},
|
||||
},
|
||||
{
|
||||
{113, 123},
|
||||
{137, 123},
|
||||
{137, 87},
|
||||
{113, 87},
|
||||
{113, 123},
|
||||
},
|
||||
{
|
||||
{107, 104},
|
||||
{110, 127},
|
||||
{114, 131},
|
||||
{136, 131},
|
||||
{140, 127},
|
||||
{143, 104},
|
||||
{143, 79},
|
||||
{107, 79},
|
||||
{107, 104},
|
||||
},
|
||||
{
|
||||
{48, 135},
|
||||
{50, 138},
|
||||
{52, 140},
|
||||
{198, 140},
|
||||
{202, 135},
|
||||
{202, 72},
|
||||
{200, 70},
|
||||
{50, 70},
|
||||
{48, 72},
|
||||
{48, 135},
|
||||
},
|
||||
{
|
||||
{115, 104},
|
||||
{116, 106},
|
||||
{123, 119},
|
||||
{127, 119},
|
||||
{134, 106},
|
||||
{135, 104},
|
||||
{135, 98},
|
||||
{134, 96},
|
||||
{132, 93},
|
||||
{128, 91},
|
||||
{122, 91},
|
||||
{118, 93},
|
||||
{116, 96},
|
||||
{115, 98},
|
||||
{115, 104},
|
||||
},
|
||||
{
|
||||
{91, 100},
|
||||
{94, 144},
|
||||
{117, 153},
|
||||
{118, 153},
|
||||
{159, 112},
|
||||
{159, 110},
|
||||
{156, 66},
|
||||
{133, 57},
|
||||
{132, 57},
|
||||
{91, 98},
|
||||
{91, 100},
|
||||
},
|
||||
{
|
||||
{101, 90},
|
||||
{103, 98},
|
||||
{107, 113},
|
||||
{114, 125},
|
||||
{115, 126},
|
||||
{135, 126},
|
||||
{136, 125},
|
||||
{144, 114},
|
||||
{149, 90},
|
||||
{149, 89},
|
||||
{148, 87},
|
||||
{145, 84},
|
||||
{105, 84},
|
||||
{102, 87},
|
||||
{101, 89},
|
||||
{101, 90},
|
||||
},
|
||||
{
|
||||
{93, 116},
|
||||
{94, 118},
|
||||
{141, 121},
|
||||
{151, 121},
|
||||
{156, 118},
|
||||
{157, 116},
|
||||
{157, 91},
|
||||
{156, 89},
|
||||
{94, 89},
|
||||
{93, 91},
|
||||
{93, 116},
|
||||
},
|
||||
{
|
||||
{89, 60},
|
||||
{91, 66},
|
||||
{134, 185},
|
||||
{139, 198},
|
||||
{140, 200},
|
||||
{141, 201},
|
||||
{159, 201},
|
||||
{161, 199},
|
||||
{161, 195},
|
||||
{157, 179},
|
||||
{114, 26},
|
||||
{110, 12},
|
||||
{108, 10},
|
||||
{106, 9},
|
||||
{92, 9},
|
||||
{89, 50},
|
||||
{89, 60},
|
||||
},
|
||||
{
|
||||
{99, 130},
|
||||
{101, 133},
|
||||
{118, 150},
|
||||
{142, 150},
|
||||
{145, 148},
|
||||
{151, 142},
|
||||
{151, 80},
|
||||
{142, 62},
|
||||
{139, 60},
|
||||
{111, 60},
|
||||
{108, 62},
|
||||
{102, 80},
|
||||
{99, 95},
|
||||
{99, 130},
|
||||
},
|
||||
{
|
||||
{99, 122},
|
||||
{108, 140},
|
||||
{110, 142},
|
||||
{139, 142},
|
||||
{151, 122},
|
||||
{151, 102},
|
||||
{142, 70},
|
||||
{139, 68},
|
||||
{111, 68},
|
||||
{108, 70},
|
||||
{99, 102},
|
||||
{99, 122},
|
||||
},
|
||||
{
|
||||
{107, 124},
|
||||
{128, 125},
|
||||
{133, 125},
|
||||
{136, 124},
|
||||
{140, 121},
|
||||
{142, 119},
|
||||
{143, 116},
|
||||
{143, 109},
|
||||
{141, 93},
|
||||
{139, 89},
|
||||
{136, 86},
|
||||
{134, 85},
|
||||
{108, 85},
|
||||
{107, 86},
|
||||
{107, 124},
|
||||
},
|
||||
{
|
||||
{107, 146},
|
||||
{124, 146},
|
||||
{141, 96},
|
||||
{143, 79},
|
||||
{143, 73},
|
||||
{142, 70},
|
||||
{140, 68},
|
||||
{136, 65},
|
||||
{134, 64},
|
||||
{127, 64},
|
||||
{107, 65},
|
||||
{107, 146},
|
||||
},
|
||||
{
|
||||
{113, 118},
|
||||
{115, 120},
|
||||
{129, 129},
|
||||
{137, 129},
|
||||
{137, 81},
|
||||
{129, 81},
|
||||
{115, 90},
|
||||
{113, 92},
|
||||
{113, 118},
|
||||
},
|
||||
{
|
||||
{112, 122},
|
||||
{138, 122},
|
||||
{138, 88},
|
||||
{112, 88},
|
||||
{112, 122},
|
||||
},
|
||||
{
|
||||
{102, 116},
|
||||
{111, 126},
|
||||
{114, 126},
|
||||
{144, 106},
|
||||
{148, 100},
|
||||
{148, 85},
|
||||
{147, 84},
|
||||
{102, 84},
|
||||
{102, 116},
|
||||
},
|
||||
{
|
||||
{112, 110},
|
||||
{121, 112},
|
||||
{129, 112},
|
||||
{138, 110},
|
||||
{138, 106},
|
||||
{134, 98},
|
||||
{117, 98},
|
||||
{114, 102},
|
||||
{112, 106},
|
||||
{112, 110},
|
||||
},
|
||||
{
|
||||
{100, 156},
|
||||
{102, 158},
|
||||
{104, 159},
|
||||
{143, 159},
|
||||
{150, 152},
|
||||
{150, 58},
|
||||
{143, 51},
|
||||
{104, 51},
|
||||
{102, 52},
|
||||
{100, 54},
|
||||
{100, 156}
|
||||
},
|
||||
{
|
||||
{106, 151},
|
||||
{108, 151},
|
||||
{139, 139},
|
||||
{144, 134},
|
||||
{144, 76},
|
||||
{139, 71},
|
||||
{108, 59},
|
||||
{106, 59},
|
||||
{106, 151}
|
||||
},
|
||||
{
|
||||
{117, 107},
|
||||
{118, 109},
|
||||
{120, 112},
|
||||
{122, 113},
|
||||
{128, 113},
|
||||
{130, 112},
|
||||
{132, 109},
|
||||
{133, 107},
|
||||
{133, 103},
|
||||
{132, 101},
|
||||
{130, 98},
|
||||
{128, 97},
|
||||
{122, 97},
|
||||
{120, 98},
|
||||
{118, 101},
|
||||
{117, 103},
|
||||
{117, 107}
|
||||
}
|
||||
};
|
9
xs/src/libnest2d/tests/printer_parts.h
Normal file
9
xs/src/libnest2d/tests/printer_parts.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef PRINTER_PARTS_H
|
||||
#define PRINTER_PARTS_H
|
||||
|
||||
#include <vector>
|
||||
#include <libnest2d.h>
|
||||
|
||||
extern const std::vector<libnest2d::Item> PRINTER_PART_POLYGONS;
|
||||
|
||||
#endif // PRINTER_PARTS_H
|
474
xs/src/libnest2d/tests/test.cpp
Normal file
474
xs/src/libnest2d/tests/test.cpp
Normal file
@ -0,0 +1,474 @@
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include <fstream>
|
||||
|
||||
#include <libnest2d.h>
|
||||
#include "printer_parts.h"
|
||||
#include <libnest2d/geometries_io.hpp>
|
||||
#include <libnest2d/geometries_nfp.hpp>
|
||||
|
||||
TEST(BasicFunctionality, Angles)
|
||||
{
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
Degrees deg(180);
|
||||
Radians rad(deg);
|
||||
Degrees deg2(rad);
|
||||
|
||||
ASSERT_DOUBLE_EQ(rad, Pi);
|
||||
ASSERT_DOUBLE_EQ(deg, 180);
|
||||
ASSERT_DOUBLE_EQ(deg2, 180);
|
||||
ASSERT_DOUBLE_EQ(rad, (Radians) deg);
|
||||
ASSERT_DOUBLE_EQ( (Degrees) rad, deg);
|
||||
|
||||
ASSERT_TRUE(rad == deg);
|
||||
|
||||
}
|
||||
|
||||
// Simple test, does not use gmock
|
||||
TEST(BasicFunctionality, creationAndDestruction)
|
||||
{
|
||||
using namespace libnest2d;
|
||||
|
||||
Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
|
||||
|
||||
ASSERT_EQ(sh.vertexCount(), 4);
|
||||
|
||||
Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} });
|
||||
|
||||
ASSERT_EQ(sh2.vertexCount(), 4);
|
||||
|
||||
// copy
|
||||
Item sh3 = sh2;
|
||||
|
||||
ASSERT_EQ(sh3.vertexCount(), 4);
|
||||
|
||||
sh2 = {};
|
||||
|
||||
ASSERT_EQ(sh2.vertexCount(), 0);
|
||||
ASSERT_EQ(sh3.vertexCount(), 4);
|
||||
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, Distance) {
|
||||
using namespace libnest2d;
|
||||
|
||||
Point p1 = {0, 0};
|
||||
|
||||
Point p2 = {10, 0};
|
||||
Point p3 = {10, 10};
|
||||
|
||||
ASSERT_DOUBLE_EQ(PointLike::distance(p1, p2), 10);
|
||||
ASSERT_DOUBLE_EQ(PointLike::distance(p1, p3), sqrt(200));
|
||||
|
||||
Segment seg(p1, p3);
|
||||
|
||||
ASSERT_DOUBLE_EQ(PointLike::distance(p2, seg), 7.0710678118654755);
|
||||
|
||||
auto result = PointLike::horizontalDistance(p2, seg);
|
||||
|
||||
auto check = [](Coord val, Coord expected) {
|
||||
if(std::is_floating_point<Coord>::value)
|
||||
ASSERT_DOUBLE_EQ(static_cast<double>(val), expected);
|
||||
else
|
||||
ASSERT_EQ(val, expected);
|
||||
};
|
||||
|
||||
ASSERT_TRUE(result.second);
|
||||
check(result.first, 10);
|
||||
|
||||
result = PointLike::verticalDistance(p2, seg);
|
||||
ASSERT_TRUE(result.second);
|
||||
check(result.first, -10);
|
||||
|
||||
result = PointLike::verticalDistance(Point{10, 20}, seg);
|
||||
ASSERT_TRUE(result.second);
|
||||
check(result.first, 10);
|
||||
|
||||
|
||||
Point p4 = {80, 0};
|
||||
Segment seg2 = { {0, 0}, {0, 40} };
|
||||
|
||||
result = PointLike::horizontalDistance(p4, seg2);
|
||||
|
||||
ASSERT_TRUE(result.second);
|
||||
check(result.first, 80);
|
||||
|
||||
result = PointLike::verticalDistance(p4, seg2);
|
||||
// Point should not be related to the segment
|
||||
ASSERT_FALSE(result.second);
|
||||
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, Area) {
|
||||
using namespace libnest2d;
|
||||
|
||||
Rectangle rect(10, 10);
|
||||
|
||||
ASSERT_EQ(rect.area(), 100);
|
||||
|
||||
Rectangle rect2 = {100, 100};
|
||||
|
||||
ASSERT_EQ(rect2.area(), 10000);
|
||||
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, IsPointInsidePolygon) {
|
||||
using namespace libnest2d;
|
||||
|
||||
Rectangle rect(10, 10);
|
||||
|
||||
Point p = {1, 1};
|
||||
|
||||
ASSERT_TRUE(rect.isPointInside(p));
|
||||
|
||||
p = {11, 11};
|
||||
|
||||
ASSERT_FALSE(rect.isPointInside(p));
|
||||
|
||||
|
||||
p = {11, 12};
|
||||
|
||||
ASSERT_FALSE(rect.isPointInside(p));
|
||||
|
||||
|
||||
p = {3, 3};
|
||||
|
||||
ASSERT_TRUE(rect.isPointInside(p));
|
||||
|
||||
}
|
||||
|
||||
//TEST(GeometryAlgorithms, Intersections) {
|
||||
// using namespace binpack2d;
|
||||
|
||||
// Rectangle rect(70, 30);
|
||||
|
||||
// rect.translate({80, 60});
|
||||
|
||||
// Rectangle rect2(80, 60);
|
||||
// rect2.translate({80, 0});
|
||||
|
||||
//// ASSERT_FALSE(Item::intersects(rect, rect2));
|
||||
|
||||
// Segment s1({0, 0}, {10, 10});
|
||||
// Segment s2({1, 1}, {11, 11});
|
||||
// ASSERT_FALSE(ShapeLike::intersects(s1, s1));
|
||||
// ASSERT_FALSE(ShapeLike::intersects(s1, s2));
|
||||
//}
|
||||
|
||||
// Simple test, does not use gmock
|
||||
TEST(GeometryAlgorithms, LeftAndDownPolygon)
|
||||
{
|
||||
using namespace libnest2d;
|
||||
using namespace libnest2d;
|
||||
|
||||
Box bin(100, 100);
|
||||
BottomLeftPlacer placer(bin);
|
||||
|
||||
Item item = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, {42, 20},
|
||||
{35, 35}, {35, 55}, {40, 75}, {70, 75}};
|
||||
|
||||
Item leftControl = { {40, 75},
|
||||
{35, 55},
|
||||
{35, 35},
|
||||
{42, 20},
|
||||
{0, 20},
|
||||
{0, 75},
|
||||
{40, 75}};
|
||||
|
||||
Item downControl = {{88, 60},
|
||||
{88, 0},
|
||||
{35, 0},
|
||||
{35, 35},
|
||||
{42, 20},
|
||||
{80, 20},
|
||||
{60, 30},
|
||||
{65, 50},
|
||||
{88, 60}};
|
||||
|
||||
Item leftp(placer.leftPoly(item));
|
||||
|
||||
ASSERT_TRUE(ShapeLike::isValid(leftp.rawShape()).first);
|
||||
ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount());
|
||||
|
||||
for(size_t i = 0; i < leftControl.vertexCount(); i++) {
|
||||
ASSERT_EQ(getX(leftp.vertex(i)), getX(leftControl.vertex(i)));
|
||||
ASSERT_EQ(getY(leftp.vertex(i)), getY(leftControl.vertex(i)));
|
||||
}
|
||||
|
||||
Item downp(placer.downPoly(item));
|
||||
|
||||
ASSERT_TRUE(ShapeLike::isValid(downp.rawShape()).first);
|
||||
ASSERT_EQ(downp.vertexCount(), downControl.vertexCount());
|
||||
|
||||
for(size_t i = 0; i < downControl.vertexCount(); i++) {
|
||||
ASSERT_EQ(getX(downp.vertex(i)), getX(downControl.vertex(i)));
|
||||
ASSERT_EQ(getY(downp.vertex(i)), getY(downControl.vertex(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Simple test, does not use gmock
|
||||
TEST(GeometryAlgorithms, ArrangeRectanglesTight)
|
||||
{
|
||||
using namespace libnest2d;
|
||||
|
||||
std::vector<Rectangle> rects = {
|
||||
{80, 80},
|
||||
{60, 90},
|
||||
{70, 30},
|
||||
{80, 60},
|
||||
{60, 60},
|
||||
{60, 40},
|
||||
{40, 40},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{20, 20} };
|
||||
|
||||
|
||||
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250));
|
||||
|
||||
auto groups = arrange(rects.begin(), rects.end());
|
||||
|
||||
ASSERT_EQ(groups.size(), 1);
|
||||
ASSERT_EQ(groups[0].size(), rects.size());
|
||||
|
||||
// check for no intersections, no containment:
|
||||
|
||||
for(auto result : groups) {
|
||||
bool valid = true;
|
||||
for(Item& r1 : result) {
|
||||
for(Item& r2 : result) {
|
||||
if(&r1 != &r2 ) {
|
||||
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
|
||||
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
|
||||
ASSERT_TRUE(valid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
|
||||
{
|
||||
using namespace libnest2d;
|
||||
|
||||
// std::vector<Rectangle> rects = { {40, 40}, {10, 10}, {20, 20} };
|
||||
std::vector<Rectangle> rects = {
|
||||
{80, 80},
|
||||
{60, 90},
|
||||
{70, 30},
|
||||
{80, 60},
|
||||
{60, 60},
|
||||
{60, 40},
|
||||
{40, 40},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{10, 10},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{5, 5},
|
||||
{20, 20} };
|
||||
|
||||
Coord min_obj_distance = 5;
|
||||
|
||||
Arranger<BottomLeftPlacer, DJDHeuristic> arrange(Box(210, 250),
|
||||
min_obj_distance);
|
||||
|
||||
auto groups = arrange(rects.begin(), rects.end());
|
||||
|
||||
ASSERT_EQ(groups.size(), 1);
|
||||
ASSERT_EQ(groups[0].size(), rects.size());
|
||||
|
||||
// check for no intersections, no containment:
|
||||
auto result = groups[0];
|
||||
bool valid = true;
|
||||
for(Item& r1 : result) {
|
||||
for(Item& r2 : result) {
|
||||
if(&r1 != &r2 ) {
|
||||
valid = !Item::intersects(r1, r2);
|
||||
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
|
||||
ASSERT_TRUE(valid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
using namespace libnest2d;
|
||||
|
||||
template<unsigned long SCALE = 1, class Bin>
|
||||
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
|
||||
|
||||
|
||||
std::string loc = "out";
|
||||
|
||||
static std::string svg_header =
|
||||
R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
)raw";
|
||||
|
||||
int i = idx;
|
||||
auto r = result;
|
||||
// for(auto r : result) {
|
||||
std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out);
|
||||
if(out.is_open()) {
|
||||
out << svg_header;
|
||||
Item rbin( Rectangle(bin.width(), bin.height()) );
|
||||
for(unsigned i = 0; i < rbin.vertexCount(); i++) {
|
||||
auto v = rbin.vertex(i);
|
||||
setY(v, -getY(v)/SCALE + 500 );
|
||||
setX(v, getX(v)/SCALE);
|
||||
rbin.setVertex(i, v);
|
||||
}
|
||||
out << ShapeLike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
|
||||
for(Item& sh : r) {
|
||||
Item tsh(sh.transformedShape());
|
||||
for(unsigned i = 0; i < tsh.vertexCount(); i++) {
|
||||
auto v = tsh.vertex(i);
|
||||
setY(v, -getY(v)/SCALE + 500);
|
||||
setX(v, getX(v)/SCALE);
|
||||
tsh.setVertex(i, v);
|
||||
}
|
||||
out << ShapeLike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
|
||||
}
|
||||
out << "\n</svg>" << std::endl;
|
||||
}
|
||||
out.close();
|
||||
|
||||
// i++;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, BottomLeftStressTest) {
|
||||
using namespace libnest2d;
|
||||
|
||||
auto input = PRINTER_PART_POLYGONS;
|
||||
|
||||
Box bin(210, 250);
|
||||
BottomLeftPlacer placer(bin);
|
||||
|
||||
auto it = input.begin();
|
||||
auto next = it;
|
||||
int i = 0;
|
||||
while(it != input.end() && ++next != input.end()) {
|
||||
placer.pack(*it);
|
||||
placer.pack(*next);
|
||||
|
||||
auto result = placer.getItems();
|
||||
bool valid = true;
|
||||
|
||||
if(result.size() == 2) {
|
||||
Item& r1 = result[0];
|
||||
Item& r2 = result[1];
|
||||
valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
|
||||
valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
|
||||
if(!valid) {
|
||||
std::cout << "error index: " << i << std::endl;
|
||||
exportSVG(result, bin, i);
|
||||
}
|
||||
// ASSERT_TRUE(valid);
|
||||
} else {
|
||||
std::cout << "something went terribly wrong!" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
placer.clearItems();
|
||||
it++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GeometryAlgorithms, nfpConvexConvex) {
|
||||
using namespace libnest2d;
|
||||
|
||||
const unsigned long SCALE = 1;
|
||||
|
||||
Box bin(210*SCALE, 250*SCALE);
|
||||
|
||||
Item stationary = {
|
||||
{120, 114},
|
||||
{130, 114},
|
||||
{130, 103},
|
||||
{128, 96},
|
||||
{122, 96},
|
||||
{120, 103},
|
||||
{120, 114}
|
||||
};
|
||||
|
||||
Item orbiter = {
|
||||
{72, 147},
|
||||
{94, 151},
|
||||
{178, 151},
|
||||
{178, 59},
|
||||
{72, 59},
|
||||
{72, 147}
|
||||
};
|
||||
|
||||
orbiter.translate({210*SCALE, 0});
|
||||
|
||||
auto&& nfp = Nfp::noFitPolygon(stationary.rawShape(),
|
||||
orbiter.transformedShape());
|
||||
|
||||
auto v = ShapeLike::isValid(nfp);
|
||||
|
||||
if(!v.first) {
|
||||
std::cout << v.second << std::endl;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(v.first);
|
||||
|
||||
Item infp(nfp);
|
||||
|
||||
int i = 0;
|
||||
auto rorbiter = orbiter.transformedShape();
|
||||
auto vo = *(ShapeLike::begin(rorbiter));
|
||||
for(auto v : infp) {
|
||||
auto dx = getX(v) - getX(vo);
|
||||
auto dy = getY(v) - getY(vo);
|
||||
|
||||
Item tmp = orbiter;
|
||||
|
||||
tmp.translate({dx, dy});
|
||||
|
||||
bool notinside = !tmp.isInside(stationary);
|
||||
bool notintersecting = !Item::intersects(tmp, stationary);
|
||||
|
||||
if(!(notinside && notintersecting)) {
|
||||
std::vector<std::reference_wrapper<Item>> inp = {
|
||||
std::ref(stationary), std::ref(tmp), std::ref(infp)
|
||||
};
|
||||
|
||||
exportSVG<SCALE>(inp, bin, i++);
|
||||
}
|
||||
|
||||
//ASSERT_TRUE(notintersecting);
|
||||
ASSERT_TRUE(notinside);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
@ -470,9 +470,9 @@ static bool prepare_infill_hatching_segments(
|
||||
int ir = std::min<int>(int(out.segs.size()) - 1, (r - x0) / line_spacing);
|
||||
// The previous tests were done with floating point arithmetics over an epsilon-extended interval.
|
||||
// Now do the same tests with exact arithmetics over the exact interval.
|
||||
while (il <= ir && Int128::orient(out.segs[il].pos, out.segs[il].pos + out.direction, *pl) < 0)
|
||||
while (il <= ir && int128::orient(out.segs[il].pos, out.segs[il].pos + out.direction, *pl) < 0)
|
||||
++ il;
|
||||
while (il <= ir && Int128::orient(out.segs[ir].pos, out.segs[ir].pos + out.direction, *pr) > 0)
|
||||
while (il <= ir && int128::orient(out.segs[ir].pos, out.segs[ir].pos + out.direction, *pr) > 0)
|
||||
-- ir;
|
||||
// Here it is ensured, that
|
||||
// 1) out.seg is not parallel to (pl, pr)
|
||||
@ -489,8 +489,8 @@ static bool prepare_infill_hatching_segments(
|
||||
is.iSegment = iSegment;
|
||||
// Test whether the calculated intersection point falls into the bounding box of the input segment.
|
||||
// +-1 to take rounding into account.
|
||||
assert(Int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pl) >= 0);
|
||||
assert(Int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pr) <= 0);
|
||||
assert(int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pl) >= 0);
|
||||
assert(int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pr) <= 0);
|
||||
assert(is.pos().x + 1 >= std::min(pl->x, pr->x));
|
||||
assert(is.pos().y + 1 >= std::min(pl->y, pr->y));
|
||||
assert(is.pos().x <= std::max(pl->x, pr->x) + 1);
|
||||
@ -527,7 +527,7 @@ static bool prepare_infill_hatching_segments(
|
||||
const Points &contour = poly_with_offset.contour(iContour).points;
|
||||
size_t iSegment = sil.intersections[i].iSegment;
|
||||
size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1;
|
||||
int dir = Int128::cross(contour[iSegment] - contour[iPrev], sil.dir);
|
||||
int dir = int128::cross(contour[iSegment] - contour[iPrev], sil.dir);
|
||||
bool low = dir > 0;
|
||||
sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ?
|
||||
(low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) :
|
||||
|
@ -48,7 +48,6 @@
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include "Point.hpp"
|
||||
|
||||
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
|
||||
#define HAS_INTRINSIC_128_TYPE
|
||||
@ -288,20 +287,4 @@ public:
|
||||
}
|
||||
return sign_determinant_2x2(p1, q1, p2, q2) * invert;
|
||||
}
|
||||
|
||||
// Exact orientation predicate,
|
||||
// returns +1: CCW, 0: collinear, -1: CW.
|
||||
static int orient(const Slic3r::Point &p1, const Slic3r::Point &p2, const Slic3r::Point &p3)
|
||||
{
|
||||
Slic3r::Vector v1(p2 - p1);
|
||||
Slic3r::Vector v2(p3 - p1);
|
||||
return sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y);
|
||||
}
|
||||
|
||||
// Exact orientation predicate,
|
||||
// returns +1: CCW, 0: collinear, -1: CW.
|
||||
static int cross(const Slic3r::Point &v1, const Slic3r::Point &v2)
|
||||
{
|
||||
return sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y);
|
||||
}
|
||||
};
|
||||
|
@ -7,6 +7,12 @@
|
||||
#include "Format/STL.hpp"
|
||||
#include "Format/3mf.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <libnest2d.h>
|
||||
#include <libnest2d/geometries_io.hpp>
|
||||
#include <ClipperUtils.hpp>
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
@ -296,35 +302,224 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace arr {
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
// A container which stores a pointer to the 3D object and its projected
|
||||
// 2D shape from top view.
|
||||
using ShapeData2D =
|
||||
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||
|
||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||
ShapeData2D ret;
|
||||
|
||||
auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
|
||||
[](size_t s, ModelObject* o){
|
||||
return s + o->instances.size();
|
||||
});
|
||||
|
||||
ret.reserve(s);
|
||||
|
||||
for(auto objptr : model.objects) {
|
||||
if(objptr) {
|
||||
|
||||
auto rmesh = objptr->raw_mesh();
|
||||
|
||||
for(auto objinst : objptr->instances) {
|
||||
if(objinst) {
|
||||
Slic3r::TriangleMesh tmpmesh = rmesh;
|
||||
objinst->transform_mesh(&tmpmesh);
|
||||
ClipperLib::PolyNode pn;
|
||||
auto p = tmpmesh.convex_hull();
|
||||
p.make_clockwise();
|
||||
p.append(p.first_point());
|
||||
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
|
||||
|
||||
ret.emplace_back(objinst, Item(std::move(pn)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Arranges the model objects on the screen.
|
||||
*
|
||||
* The arrangement considers multiple bins (aka. print beds) for placing all
|
||||
* the items provided in the model argument. If the items don't fit on one
|
||||
* print bed, the remaining will be placed onto newly created print beds.
|
||||
* The first_bin_only parameter, if set to true, disables this behaviour and
|
||||
* makes sure that only one print bed is filled and the remaining items will be
|
||||
* untouched. When set to false, the items which could not fit onto the
|
||||
* print bed will be placed next to the print bed so the user should see a
|
||||
* pile of items on the print bed and some other piles outside the print
|
||||
* area that can be dragged later onto the print bed as a group.
|
||||
*
|
||||
* \param model The model object with the 3D content.
|
||||
* \param dist The minimum distance which is allowed for any pair of items
|
||||
* on the print bed in any direction.
|
||||
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
|
||||
* for bin packing.
|
||||
* \param first_bin_only This parameter controls whether to place the
|
||||
* remaining items which do not fit onto the print area next to the print
|
||||
* bed or leave them untouched (let the user arrange them by hand or remove
|
||||
* them).
|
||||
*/
|
||||
bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
|
||||
bool first_bin_only)
|
||||
{
|
||||
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
|
||||
|
||||
bool ret = true;
|
||||
|
||||
// Create the arranger config
|
||||
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
|
||||
|
||||
// Get the 2D projected shapes with their 3D model instance pointers
|
||||
auto shapemap = arr::projectModelFromTop(model);
|
||||
|
||||
double area = 0;
|
||||
double area_max = 0;
|
||||
Item *biggest = nullptr;
|
||||
|
||||
// Copy the references for the shapes only as the arranger expects a
|
||||
// sequence of objects convertible to Item or ClipperPolygon
|
||||
std::vector<std::reference_wrapper<Item>> shapes;
|
||||
shapes.reserve(shapemap.size());
|
||||
std::for_each(shapemap.begin(), shapemap.end(),
|
||||
[&shapes, &area, min_obj_distance, &area_max, &biggest]
|
||||
(ShapeData2D::value_type& it)
|
||||
{
|
||||
Item& item = it.second;
|
||||
item.addOffset(min_obj_distance);
|
||||
auto b = ShapeLike::boundingBox(item.transformedShape());
|
||||
auto a = b.width()*b.height();
|
||||
if(area_max < a) {
|
||||
area_max = static_cast<double>(a);
|
||||
biggest = &item;
|
||||
}
|
||||
area += b.width()*b.height();
|
||||
shapes.push_back(std::ref(it.second));
|
||||
});
|
||||
|
||||
Box bin;
|
||||
|
||||
if(bb != nullptr && bb->defined) {
|
||||
// Scale up the bounding box to clipper scale.
|
||||
BoundingBoxf bbb = *bb;
|
||||
bbb.scale(1.0/SCALING_FACTOR);
|
||||
|
||||
bin = Box({
|
||||
static_cast<libnest2d::Coord>(bbb.min.x),
|
||||
static_cast<libnest2d::Coord>(bbb.min.y)
|
||||
},
|
||||
{
|
||||
static_cast<libnest2d::Coord>(bbb.max.x),
|
||||
static_cast<libnest2d::Coord>(bbb.max.y)
|
||||
});
|
||||
} else {
|
||||
// Just take the biggest item as bin... ?
|
||||
bin = ShapeLike::boundingBox(biggest->transformedShape());
|
||||
}
|
||||
|
||||
// Will use the DJD selection heuristic with the BottomLeft placement
|
||||
// strategy
|
||||
using Arranger = Arranger<BottomLeftPlacer, DJDHeuristic>;
|
||||
|
||||
Arranger arranger(bin, min_obj_distance);
|
||||
|
||||
// Arrange and return the items with their respective indices within the
|
||||
// input sequence.
|
||||
ArrangeResult result =
|
||||
arranger.arrangeIndexed(shapes.begin(), shapes.end());
|
||||
|
||||
|
||||
auto applyResult = [&shapemap](ArrangeResult::value_type& group,
|
||||
Coord batch_offset)
|
||||
{
|
||||
for(auto& r : group) {
|
||||
auto idx = r.first; // get the original item index
|
||||
Item& item = r.second; // get the item itself
|
||||
|
||||
// Get the model instance from the shapemap using the index
|
||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||
|
||||
// Get the tranformation data from the item object and scale it
|
||||
// appropriately
|
||||
Radians rot = item.rotation();
|
||||
auto off = item.translation();
|
||||
Pointf foff(off.X*SCALING_FACTOR + batch_offset,
|
||||
off.Y*SCALING_FACTOR);
|
||||
|
||||
// write the tranformation data into the model instance
|
||||
inst_ptr->rotation += rot;
|
||||
inst_ptr->offset += foff;
|
||||
|
||||
// Debug
|
||||
/*std::cout << "item " << idx << ": \n" << "\toffset_x: "
|
||||
* << foff.x << "\n\toffset_y: " << foff.y << std::endl;*/
|
||||
}
|
||||
};
|
||||
|
||||
if(first_bin_only) {
|
||||
applyResult(result.front(), 0);
|
||||
} else {
|
||||
Coord batch_offset = 0;
|
||||
for(auto& group : result) {
|
||||
applyResult(group, batch_offset);
|
||||
|
||||
// Only the first pack group can be placed onto the print bed. The
|
||||
// other objects which could not fit will be placed next to the
|
||||
// print bed
|
||||
batch_offset += static_cast<Coord>(2*bin.width()*SCALING_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
|
||||
|
||||
return ret && result.size() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* arrange objects preserving their instance count
|
||||
but altering their instance positions */
|
||||
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
|
||||
{
|
||||
// get the (transformed) size of each instance so that we take
|
||||
// into account their different transformations when packing
|
||||
Pointfs instance_sizes;
|
||||
Pointfs instance_centers;
|
||||
for (const ModelObject *o : this->objects)
|
||||
for (size_t i = 0; i < o->instances.size(); ++ i) {
|
||||
// an accurate snug bounding box around the transformed mesh.
|
||||
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
|
||||
instance_sizes.push_back(bbox.size());
|
||||
instance_centers.push_back(bbox.center());
|
||||
}
|
||||
bool ret = false;
|
||||
if(bb != nullptr && bb->defined) {
|
||||
const bool FIRST_BIN_ONLY = true;
|
||||
ret = arr::arrange(*this, dist, bb, FIRST_BIN_ONLY);
|
||||
} else {
|
||||
// get the (transformed) size of each instance so that we take
|
||||
// into account their different transformations when packing
|
||||
Pointfs instance_sizes;
|
||||
Pointfs instance_centers;
|
||||
for (const ModelObject *o : this->objects)
|
||||
for (size_t i = 0; i < o->instances.size(); ++ i) {
|
||||
// an accurate snug bounding box around the transformed mesh.
|
||||
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
|
||||
instance_sizes.push_back(bbox.size());
|
||||
instance_centers.push_back(bbox.center());
|
||||
}
|
||||
|
||||
Pointfs positions;
|
||||
if (! _arrange(instance_sizes, dist, bb, positions))
|
||||
return false;
|
||||
|
||||
size_t idx = 0;
|
||||
for (ModelObject *o : this->objects) {
|
||||
for (ModelInstance *i : o->instances) {
|
||||
i->offset = positions[idx] - instance_centers[idx];
|
||||
++ idx;
|
||||
Pointfs positions;
|
||||
if (! _arrange(instance_sizes, dist, bb, positions))
|
||||
return false;
|
||||
|
||||
size_t idx = 0;
|
||||
for (ModelObject *o : this->objects) {
|
||||
for (ModelInstance *i : o->instances) {
|
||||
i->offset = positions[idx] - instance_centers[idx];
|
||||
++ idx;
|
||||
}
|
||||
o->invalidate_bounding_box();
|
||||
}
|
||||
o->invalidate_bounding_box();
|
||||
}
|
||||
return true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Duplicate the entire model preserving instance relative positions.
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "MultiPoint.hpp"
|
||||
#include "Int128.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
@ -375,4 +376,20 @@ Pointf3::vector_to(const Pointf3 &point) const
|
||||
return Vectorf3(point.x - this->x, point.y - this->y, point.z - this->z);
|
||||
}
|
||||
|
||||
namespace int128 {
|
||||
|
||||
int orient(const Point &p1, const Point &p2, const Point &p3)
|
||||
{
|
||||
Slic3r::Vector v1(p2 - p1);
|
||||
Slic3r::Vector v2(p3 - p1);
|
||||
return Int128::sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y);
|
||||
}
|
||||
|
||||
int cross(const Point &v1, const Point &v2)
|
||||
{
|
||||
return Int128::sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,6 +81,17 @@ inline Point operator*(double scalar, const Point& point2) { return Point(scalar
|
||||
inline int64_t cross(const Point &v1, const Point &v2) { return int64_t(v1.x) * int64_t(v2.y) - int64_t(v1.y) * int64_t(v2.x); }
|
||||
inline int64_t dot(const Point &v1, const Point &v2) { return int64_t(v1.x) * int64_t(v2.x) + int64_t(v1.y) * int64_t(v2.y); }
|
||||
|
||||
namespace int128 {
|
||||
|
||||
// Exact orientation predicate,
|
||||
// returns +1: CCW, 0: collinear, -1: CW.
|
||||
int orient(const Point &p1, const Point &p2, const Point &p3);
|
||||
|
||||
// Exact orientation predicate,
|
||||
// returns +1: CCW, 0: collinear, -1: CW.
|
||||
int cross(const Point &v1, const Slic3r::Point &v2);
|
||||
}
|
||||
|
||||
// To be used by std::unordered_map, std::unordered_multimap and friends.
|
||||
struct PointHash {
|
||||
size_t operator()(const Point &pt) const {
|
||||
|
Loading…
Reference in New Issue
Block a user