Merge branch 'tm_rotcalipers'
This commit is contained in:
commit
1e7b5c5a81
@ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES
|
|||||||
${SRC_DIR}/libnest2d/optimizer.hpp
|
${SRC_DIR}/libnest2d/optimizer.hpp
|
||||||
${SRC_DIR}/libnest2d/utils/metaloop.hpp
|
${SRC_DIR}/libnest2d/utils/metaloop.hpp
|
||||||
${SRC_DIR}/libnest2d/utils/rotfinder.hpp
|
${SRC_DIR}/libnest2d/utils/rotfinder.hpp
|
||||||
|
${SRC_DIR}/libnest2d/utils/rotcalipers.hpp
|
||||||
|
${SRC_DIR}/libnest2d/utils/bigint.hpp
|
||||||
|
${SRC_DIR}/libnest2d/utils/rational.hpp
|
||||||
${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp
|
${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp
|
||||||
${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp
|
${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp
|
||||||
${SRC_DIR}/libnest2d/placers/nfpplacer.hpp
|
${SRC_DIR}/libnest2d/placers/nfpplacer.hpp
|
||||||
@ -70,12 +73,10 @@ if(TBB_FOUND)
|
|||||||
# The Intel TBB library will use the std::exception_ptr feature of C++11.
|
# The Intel TBB library will use the std::exception_ptr feature of C++11.
|
||||||
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
|
target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||||
|
|
||||||
target_link_libraries(libnest2d INTERFACE tbb)
|
find_package(Threads REQUIRED)
|
||||||
# The following breaks compilation on Visual Studio in Debug mode.
|
target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
|
||||||
#find_package(Threads REQUIRED)
|
Threads::Threads
|
||||||
#target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS}
|
)
|
||||||
# Threads::Threads
|
|
||||||
# )
|
|
||||||
else()
|
else()
|
||||||
find_package(OpenMP QUIET)
|
find_package(OpenMP QUIET)
|
||||||
|
|
||||||
@ -92,10 +93,11 @@ endif()
|
|||||||
|
|
||||||
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
|
add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES})
|
||||||
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend)
|
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend)
|
||||||
|
|
||||||
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
|
add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER})
|
||||||
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer)
|
target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer)
|
||||||
|
|
||||||
#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
|
# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES})
|
||||||
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
|
target_include_directories(libnest2d INTERFACE ${SRC_DIR})
|
||||||
|
|
||||||
if(NOT LIBNEST2D_HEADER_ONLY)
|
if(NOT LIBNEST2D_HEADER_ONLY)
|
||||||
|
@ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer<Box>;
|
|||||||
// This supports only box shaped bins
|
// This supports only box shaped bins
|
||||||
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
|
using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
|
||||||
|
|
||||||
|
#ifdef LIBNEST2D_STATIC
|
||||||
|
|
||||||
|
extern template class Nester<NfpPlacer, FirstFitSelection>;
|
||||||
|
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
||||||
|
extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute(
|
||||||
|
std::vector<Item>::iterator, std::vector<Item>::iterator);
|
||||||
|
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute(
|
||||||
|
std::vector<Item>::iterator, std::vector<Item>::iterator);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
template<class Placer = NfpPlacer,
|
||||||
class Selector = FirstFitSelection,
|
class Selector = FirstFitSelection,
|
||||||
class Iterator = std::vector<Item>::iterator>
|
class Iterator = std::vector<Item>::iterator>
|
||||||
@ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to,
|
|||||||
return nester.execute(from, to);
|
return nester.execute(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
|
||||||
class Selector = FirstFitSelection,
|
|
||||||
class Container = std::vector<Item>>
|
|
||||||
PackGroup nest(Container&& cont,
|
|
||||||
const typename Placer::BinType& bin,
|
|
||||||
Coord dist = 0,
|
|
||||||
const typename Placer::Config& pconf = {},
|
|
||||||
const typename Selector::Config& sconf = {})
|
|
||||||
{
|
|
||||||
return nest<Placer, Selector>(cont.begin(), cont.end(),
|
|
||||||
bin, dist, pconf, sconf);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
template<class Placer = NfpPlacer,
|
||||||
class Selector = FirstFitSelection,
|
class Selector = FirstFitSelection,
|
||||||
class Iterator = std::vector<Item>::iterator>
|
class Iterator = std::vector<Item>::iterator>
|
||||||
@ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to,
|
|||||||
return nester.execute(from, to);
|
return nester.execute(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef LIBNEST2D_STATIC
|
||||||
|
|
||||||
|
extern template class Nester<NfpPlacer, FirstFitSelection>;
|
||||||
|
extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
||||||
|
|
||||||
|
extern template PackGroup nest(std::vector<Item>::iterator from,
|
||||||
|
std::vector<Item>::iterator to,
|
||||||
|
const Box& bin,
|
||||||
|
Coord dist = 0,
|
||||||
|
const NfpPlacer::Config& pconf,
|
||||||
|
const FirstFitSelection::Config& sconf);
|
||||||
|
|
||||||
|
extern template PackGroup nest(std::vector<Item>::iterator from,
|
||||||
|
std::vector<Item>::iterator to,
|
||||||
|
const Box& bin,
|
||||||
|
ProgressFunction prg,
|
||||||
|
StopCondition scond,
|
||||||
|
Coord dist = 0,
|
||||||
|
const NfpPlacer::Config& pconf,
|
||||||
|
const FirstFitSelection::Config& sconf);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class Placer = NfpPlacer,
|
||||||
|
class Selector = FirstFitSelection,
|
||||||
|
class Container = std::vector<Item>>
|
||||||
|
PackGroup nest(Container&& cont,
|
||||||
|
const typename Placer::BinType& bin,
|
||||||
|
Coord dist = 0,
|
||||||
|
const typename Placer::Config& pconf = {},
|
||||||
|
const typename Selector::Config& sconf = {})
|
||||||
|
{
|
||||||
|
return nest<Placer, Selector>(cont.begin(), cont.end(),
|
||||||
|
bin, dist, pconf, sconf);
|
||||||
|
}
|
||||||
|
|
||||||
template<class Placer = NfpPlacer,
|
template<class Placer = NfpPlacer,
|
||||||
class Selector = FirstFitSelection,
|
class Selector = FirstFitSelection,
|
||||||
class Container = std::vector<Item>>
|
class Container = std::vector<Item>>
|
||||||
@ -105,71 +139,6 @@ PackGroup nest(Container&& cont,
|
|||||||
bin, prg, scond, dist, pconf, sconf);
|
bin, prg, scond, dist, pconf, sconf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LIBNEST2D_STATIC
|
|
||||||
extern template
|
|
||||||
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
|
|
||||||
std::vector<Item>& cont,
|
|
||||||
const Box& bin,
|
|
||||||
Coord dist,
|
|
||||||
const NfpPlacer::Config& pcfg,
|
|
||||||
const FirstFitSelection::Config& scfg
|
|
||||||
);
|
|
||||||
|
|
||||||
extern template
|
|
||||||
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>&>(
|
|
||||||
std::vector<Item>& cont,
|
|
||||||
const Box& bin,
|
|
||||||
ProgressFunction prg,
|
|
||||||
StopCondition scond,
|
|
||||||
Coord dist,
|
|
||||||
const NfpPlacer::Config& pcfg,
|
|
||||||
const FirstFitSelection::Config& scfg
|
|
||||||
);
|
|
||||||
|
|
||||||
extern template
|
|
||||||
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
|
|
||||||
std::vector<Item>&& cont,
|
|
||||||
const Box& bin,
|
|
||||||
Coord dist,
|
|
||||||
const NfpPlacer::Config& pcfg,
|
|
||||||
const FirstFitSelection::Config& scfg
|
|
||||||
);
|
|
||||||
|
|
||||||
extern template
|
|
||||||
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>>(
|
|
||||||
std::vector<Item>&& cont,
|
|
||||||
const Box& bin,
|
|
||||||
ProgressFunction prg,
|
|
||||||
StopCondition scond,
|
|
||||||
Coord dist,
|
|
||||||
const NfpPlacer::Config& pcfg,
|
|
||||||
const FirstFitSelection::Config& scfg
|
|
||||||
);
|
|
||||||
|
|
||||||
extern template
|
|
||||||
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
|
|
||||||
std::vector<Item>::iterator from,
|
|
||||||
std::vector<Item>::iterator to,
|
|
||||||
const Box& bin,
|
|
||||||
Coord dist,
|
|
||||||
const NfpPlacer::Config& pcfg,
|
|
||||||
const FirstFitSelection::Config& scfg
|
|
||||||
);
|
|
||||||
|
|
||||||
extern template
|
|
||||||
PackGroup nest<NfpPlacer, FirstFitSelection, std::vector<Item>::iterator>(
|
|
||||||
std::vector<Item>::iterator from,
|
|
||||||
std::vector<Item>::iterator to,
|
|
||||||
const Box& bin,
|
|
||||||
ProgressFunction prg,
|
|
||||||
StopCondition scond,
|
|
||||||
Coord dist,
|
|
||||||
const NfpPlacer::Config& pcfg,
|
|
||||||
const FirstFitSelection::Config& scfg
|
|
||||||
);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // LIBNEST2D_H
|
#endif // LIBNEST2D_H
|
||||||
|
@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a
|
|||||||
# ${clipper_library_BINARY_DIR}
|
# ${clipper_library_BINARY_DIR}
|
||||||
# )
|
# )
|
||||||
|
|
||||||
add_library(ClipperBackend STATIC
|
add_library(clipperBackend STATIC
|
||||||
${clipper_library_SOURCE_DIR}/clipper.cpp
|
${clipper_library_SOURCE_DIR}/clipper.cpp
|
||||||
${clipper_library_SOURCE_DIR}/clipper.hpp)
|
${clipper_library_SOURCE_DIR}/clipper.hpp)
|
||||||
|
|
||||||
target_include_directories(ClipperBackend INTERFACE
|
target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR})
|
||||||
${clipper_library_SOURCE_DIR})
|
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Can't find clipper library and no SVN client found to download.
|
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.")
|
You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.")
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
add_library(ClipperBackend INTERFACE)
|
add_library(clipperBackend INTERFACE)
|
||||||
target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper)
|
target_link_libraries(clipperBackend INTERFACE Clipper::Clipper)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
# set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE)
|
# set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE)
|
||||||
@ -69,6 +68,6 @@ target_link_libraries(clipperBackend INTERFACE Boost::boost )
|
|||||||
|
|
||||||
target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
|
target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER)
|
||||||
|
|
||||||
# And finally plug the ClipperBackend into libnest2d
|
# And finally plug the clipperBackend into libnest2d
|
||||||
#target_link_libraries(libnest2d INTERFACE ClipperBackend)
|
# target_link_libraries(libnest2d INTERFACE clipperBackend)
|
||||||
|
|
||||||
|
@ -12,13 +12,13 @@ struct Polygon {
|
|||||||
inline Polygon() = default;
|
inline Polygon() = default;
|
||||||
|
|
||||||
inline explicit Polygon(const Path& cont): Contour(cont) {}
|
inline explicit Polygon(const Path& cont): Contour(cont) {}
|
||||||
inline explicit Polygon(const Paths& holes):
|
// inline explicit Polygon(const Paths& holes):
|
||||||
Holes(holes) {}
|
// Holes(holes) {}
|
||||||
inline Polygon(const Path& cont, const Paths& holes):
|
inline Polygon(const Path& cont, const Paths& holes):
|
||||||
Contour(cont), Holes(holes) {}
|
Contour(cont), Holes(holes) {}
|
||||||
|
|
||||||
inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
|
inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {}
|
||||||
inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
|
// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {}
|
||||||
inline Polygon(Path&& cont, Paths&& holes):
|
inline Polygon(Path&& cont, Paths&& holes):
|
||||||
Contour(std::move(cont)), Holes(std::move(holes)) {}
|
Contour(std::move(cont)), Holes(std::move(holes)) {}
|
||||||
};
|
};
|
||||||
@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline IntPoint operator -(IntPoint& p ) {
|
inline IntPoint operator -(const IntPoint& p ) {
|
||||||
IntPoint ret = p;
|
IntPoint ret = p;
|
||||||
ret.X = -ret.X;
|
ret.X = -ret.X;
|
||||||
ret.Y = -ret.Y;
|
ret.Y = -ret.Y;
|
||||||
|
@ -20,43 +20,23 @@ using PathImpl = ClipperLib::Path;
|
|||||||
using HoleStore = ClipperLib::Paths;
|
using HoleStore = ClipperLib::Paths;
|
||||||
using PolygonImpl = ClipperLib::Polygon;
|
using PolygonImpl = ClipperLib::Polygon;
|
||||||
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<> struct PointType<PathImpl> {
|
|
||||||
using Type = PointImpl;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<> struct PointType<PointImpl> {
|
|
||||||
using Type = PointImpl;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<> struct CountourType<PolygonImpl> {
|
|
||||||
using Type = PathImpl;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
|
template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
|
||||||
template<> struct ShapeTag<PathImpl> { using Type = PathTag; };
|
template<> struct ShapeTag<PathImpl> { using Type = PathTag; };
|
||||||
template<> struct ShapeTag<PointImpl> { using Type = PointTag; };
|
template<> struct ShapeTag<PointImpl> { using Type = PointTag; };
|
||||||
|
|
||||||
template<> struct ShapeTag<TMultiShape<PolygonImpl>> {
|
// Type of coordinate units used by Clipper. Enough to specialize for point,
|
||||||
using Type = MultiPolygonTag;
|
// the rest of the types will work (Path, Polygon)
|
||||||
};
|
template<> struct CoordType<PointImpl> { using Type = ClipperLib::cInt; };
|
||||||
|
|
||||||
template<> struct PointType<TMultiShape<PolygonImpl>> {
|
// Enough to specialize for path, it will work for multishape and Polygon
|
||||||
using Type = PointImpl;
|
template<> struct PointType<PathImpl> { using Type = PointImpl; };
|
||||||
};
|
|
||||||
|
|
||||||
template<> struct HolesContainer<PolygonImpl> {
|
// This is crucial. CountourType refers to itself by default, so we don't have
|
||||||
using Type = ClipperLib::Paths;
|
// to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
|
||||||
};
|
template<> struct ContourType<PolygonImpl> { using Type = PathImpl; };
|
||||||
|
|
||||||
|
// The holes are contained in Clipper::Paths
|
||||||
|
template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; };
|
||||||
|
|
||||||
namespace pointlike {
|
namespace pointlike {
|
||||||
|
|
||||||
@ -86,39 +66,11 @@ template<> inline TCoord<PointImpl>& y(PointImpl& p)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Using the libnest2d default area implementation
|
||||||
#define DISABLE_BOOST_AREA
|
#define DISABLE_BOOST_AREA
|
||||||
|
|
||||||
namespace _smartarea {
|
|
||||||
|
|
||||||
template<Orientation o>
|
|
||||||
inline double area(const PolygonImpl& /*sh*/) {
|
|
||||||
return std::nan("");
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
inline double area<Orientation::COUNTER_CLOCKWISE>(const PolygonImpl& sh) {
|
|
||||||
return std::accumulate(sh.Holes.begin(), sh.Holes.end(),
|
|
||||||
ClipperLib::Area(sh.Contour),
|
|
||||||
[](double a, const ClipperLib::Path& pt){
|
|
||||||
return a + ClipperLib::Area(pt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
inline double area<Orientation::CLOCKWISE>(const PolygonImpl& sh) {
|
|
||||||
return -area<Orientation::COUNTER_CLOCKWISE>(sh);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace shapelike {
|
namespace shapelike {
|
||||||
|
|
||||||
// Tell libnest2d how to make string out of a ClipperPolygon object
|
|
||||||
template<> inline double area(const PolygonImpl& sh, const PolygonTag&)
|
|
||||||
{
|
|
||||||
return _smartarea::area<OrientationType<PolygonImpl>::Value>(sh);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
|
template<> inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance)
|
||||||
{
|
{
|
||||||
#define DISABLE_BOOST_OFFSET
|
#define DISABLE_BOOST_OFFSET
|
||||||
@ -200,20 +152,7 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
|
|||||||
{
|
{
|
||||||
PolygonImpl p;
|
PolygonImpl p;
|
||||||
p.Contour = path;
|
p.Contour = path;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Holes = holes;
|
p.Holes = holes;
|
||||||
for(auto& h : p.Holes) {
|
|
||||||
if(!ClipperLib::Orientation(h)) {
|
|
||||||
ClipperLib::ReversePath(h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -221,22 +160,8 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
|
|||||||
template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
|
template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
|
||||||
PolygonImpl p;
|
PolygonImpl p;
|
||||||
p.Contour.swap(path);
|
p.Contour.swap(path);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Holes.swap(holes);
|
p.Holes.swap(holes);
|
||||||
|
|
||||||
for(auto& h : p.Holes) {
|
|
||||||
if(!ClipperLib::Orientation(h)) {
|
|
||||||
ClipperLib::ReversePath(h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads)
|
|||||||
} // namespace shapelike
|
} // namespace shapelike
|
||||||
|
|
||||||
#define DISABLE_BOOST_NFP_MERGE
|
#define DISABLE_BOOST_NFP_MERGE
|
||||||
inline std::vector<PolygonImpl> clipper_execute(
|
inline TMultiShape<PolygonImpl> clipper_execute(
|
||||||
ClipperLib::Clipper& clipper,
|
ClipperLib::Clipper& clipper,
|
||||||
ClipperLib::ClipType clipType,
|
ClipperLib::ClipType clipType,
|
||||||
ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
|
ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
|
||||||
ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
|
ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
|
||||||
{
|
{
|
||||||
shapelike::Shapes<PolygonImpl> retv;
|
TMultiShape<PolygonImpl> retv;
|
||||||
|
|
||||||
ClipperLib::PolyTree result;
|
ClipperLib::PolyTree result;
|
||||||
clipper.Execute(clipType, result, subjFillType, clipFillType);
|
clipper.Execute(clipType, result, subjFillType, clipFillType);
|
||||||
@ -370,8 +295,8 @@ inline std::vector<PolygonImpl> clipper_execute(
|
|||||||
|
|
||||||
namespace nfp {
|
namespace nfp {
|
||||||
|
|
||||||
template<> inline std::vector<PolygonImpl>
|
template<> inline TMultiShape<PolygonImpl>
|
||||||
merge(const std::vector<PolygonImpl>& shapes)
|
merge(const TMultiShape<PolygonImpl>& shapes)
|
||||||
{
|
{
|
||||||
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
|
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
|
||||||
|
|
||||||
@ -394,6 +319,8 @@ merge(const std::vector<PolygonImpl>& shapes)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DISABLE_BOOST_CONVEX_HULL
|
||||||
|
|
||||||
//#define DISABLE_BOOST_SERIALIZE
|
//#define DISABLE_BOOST_SERIALIZE
|
||||||
//#define DISABLE_BOOST_UNSERIALIZE
|
//#define DISABLE_BOOST_UNSERIALIZE
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
|
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
|
||||||
#define BP2D_NOEXCEPT
|
#define BP2D_NOEXCEPT
|
||||||
@ -197,6 +198,33 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ScalarTag {};
|
||||||
|
struct BigIntTag {};
|
||||||
|
struct RationalTag {};
|
||||||
|
|
||||||
|
template<class T> struct _NumTag {
|
||||||
|
using Type =
|
||||||
|
enable_if_t<std::is_arithmetic<T>::value, ScalarTag>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T> using NumTag = typename _NumTag<remove_cvref_t<T>>::Type;
|
||||||
|
|
||||||
|
/// A local version for abs that is garanteed to work with libnest2d types
|
||||||
|
template <class T> inline T abs(const T& v, ScalarTag)
|
||||||
|
{
|
||||||
|
return std::abs(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> inline T abs(const T& v) { return abs(v, NumTag<T>()); }
|
||||||
|
|
||||||
|
template<class T2, class T1> inline T2 cast(const T1& v, ScalarTag, ScalarTag)
|
||||||
|
{
|
||||||
|
return static_cast<T2>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T2, class T1> inline T2 cast(const T1& v) {
|
||||||
|
return cast<T2, T1>(v, NumTag<T1>(), NumTag<T2>());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif // LIBNEST2D_CONFIG_HPP
|
#endif // LIBNEST2D_CONFIG_HPP
|
||||||
|
@ -7,45 +7,125 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <limits>
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "common.hpp"
|
#include <libnest2d/common.hpp>
|
||||||
|
|
||||||
namespace libnest2d {
|
namespace libnest2d {
|
||||||
|
|
||||||
|
// Meta tags for different geometry concepts.
|
||||||
|
struct PointTag {};
|
||||||
|
struct PolygonTag {};
|
||||||
|
struct PathTag {};
|
||||||
|
struct MultiPolygonTag {};
|
||||||
|
struct BoxTag {};
|
||||||
|
struct CircleTag {};
|
||||||
|
|
||||||
|
/// Meta-function to derive the tag of a shape type.
|
||||||
|
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
|
||||||
|
|
||||||
|
/// Tag<S> will be used instead of `typename ShapeTag<S>::Type`
|
||||||
|
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
|
||||||
|
|
||||||
|
/// Meta function to derive the contour type for a polygon which could be itself
|
||||||
|
template<class RawShape> struct ContourType { using Type = RawShape; };
|
||||||
|
|
||||||
|
/// TContour<RawShape> instead of `typename ContourType<RawShape>::type`
|
||||||
|
template<class RawShape>
|
||||||
|
using TContour = typename ContourType<remove_cvref_t<RawShape>>::Type;
|
||||||
|
|
||||||
|
/// Getting the type of point structure used by a shape.
|
||||||
|
template<class Sh> struct PointType {
|
||||||
|
using Type = typename PointType<TContour<Sh>>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
|
||||||
|
template<class Shape>
|
||||||
|
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
|
||||||
|
|
||||||
/// Getting the coordinate data type for a geometry class.
|
/// Getting the coordinate data type for a geometry class.
|
||||||
template<class GeomClass> struct CoordType { using Type = long; };
|
template<class GeomClass> struct CoordType {
|
||||||
|
using Type = typename CoordType<TPoint<GeomClass>>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
/// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
|
/// TCoord<GeomType> as shorthand for typename `CoordType<GeomType>::Type`.
|
||||||
template<class GeomType>
|
template<class GeomType>
|
||||||
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
|
using TCoord = typename CoordType<remove_cvref_t<GeomType>>::Type;
|
||||||
|
|
||||||
|
|
||||||
/// Getting the type of point structure used by a shape.
|
/// Getting the computation type for a certain geometry type.
|
||||||
template<class Sh> struct PointType { using Type = typename Sh::PointType; };
|
/// It is the coordinate type by default but it is advised that a type with
|
||||||
|
/// larger precision and (or) range is specified.
|
||||||
|
template<class T, bool = std::is_arithmetic<T>::value> struct ComputeType {};
|
||||||
|
|
||||||
/// TPoint<ShapeClass> as shorthand for `typename PointType<ShapeClass>::Type`.
|
/// A compute type is introduced to hold the results of computations on
|
||||||
template<class Shape>
|
/// coordinates and points. It should be larger in range than the coordinate
|
||||||
using TPoint = typename PointType<remove_cvref_t<Shape>>::Type;
|
/// type or the range of coordinates should be limited to not loose precision.
|
||||||
|
template<class GeomClass> struct ComputeType<GeomClass, false> {
|
||||||
|
using Type = typename ComputeType<TCoord<GeomClass>>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// libnest2d will choose a default compute type for various coordinate types
|
||||||
|
/// if the backend has not specified anything.
|
||||||
|
template<class T> struct DoublePrecision { using Type = T; };
|
||||||
|
template<> struct DoublePrecision<int8_t> { using Type = int16_t; };
|
||||||
|
template<> struct DoublePrecision<int16_t> { using Type = int32_t; };
|
||||||
|
template<> struct DoublePrecision<int32_t> { using Type = int64_t; };
|
||||||
|
template<> struct DoublePrecision<float> { using Type = double; };
|
||||||
|
template<> struct DoublePrecision<double> { using Type = long double; };
|
||||||
|
template<class I> struct ComputeType<I, true> {
|
||||||
|
using Type = typename DoublePrecision<I>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
template<class RawShape> struct CountourType { using Type = RawShape; };
|
/// TCompute<T> shorthand for `typename ComputeType<T>::Type`
|
||||||
|
template<class T> using TCompute = typename ComputeType<remove_cvref_t<T>>::Type;
|
||||||
template<class RawShape>
|
|
||||||
using TContour = typename CountourType<remove_cvref_t<RawShape>>::Type;
|
|
||||||
|
|
||||||
|
|
||||||
|
/// A meta function to derive a container type for holes in a polygon
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
struct HolesContainer { using Type = std::vector<TContour<RawShape>>; };
|
struct HolesContainer { using Type = std::vector<TContour<RawShape>>; };
|
||||||
|
|
||||||
|
/// Shorthand for `typename HolesContainer<RawShape>::Type`
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type;
|
using THolesContainer = typename HolesContainer<remove_cvref_t<RawShape>>::Type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TContour, TPoint, TCoord and TCompute should be usable for any type for which
|
||||||
|
* it makes sense. For example, the point type could be derived from the contour,
|
||||||
|
* the polygon and (or) the multishape as well. The coordinate type also and
|
||||||
|
* including the point type. TCoord<Polygon>, TCoord<Path>, TCoord<Point> are
|
||||||
|
* all valid types and derives the coordinate type of template argument Polygon,
|
||||||
|
* Path and Point. This is also true for TCompute, but it can also take the
|
||||||
|
* coordinate type as argument.
|
||||||
|
*/
|
||||||
|
|
||||||
template<class RawShape>
|
/*
|
||||||
struct LastPointIsFirst { static const bool Value = true; };
|
* A Multi shape concept is also introduced. A multi shape is something that
|
||||||
|
* can contain the result of an operation where the input is one polygon and
|
||||||
|
* the result could be many polygons or path -> paths. The MultiShape should be
|
||||||
|
* a container type. If the backend does not specialize the MultiShape template,
|
||||||
|
* a default multi shape container will be used.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// The default multi shape container.
|
||||||
|
template<class S> struct DefaultMultiShape: public std::vector<S> {
|
||||||
|
using Tag = MultiPolygonTag;
|
||||||
|
template<class...Args> DefaultMultiShape(Args&&...args):
|
||||||
|
std::vector<S>(std::forward<Args>(args)...) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The MultiShape Type trait which gets the container type for a geometry type.
|
||||||
|
template<class S> struct MultiShape { using Type = DefaultMultiShape<S>; };
|
||||||
|
|
||||||
|
/// use TMultiShape<S> instead of `typename MultiShape<S>::Type`
|
||||||
|
template<class S>
|
||||||
|
using TMultiShape = typename MultiShape<remove_cvref_t<S>>::Type;
|
||||||
|
|
||||||
|
// A specialization of ContourType to work with the default multishape type
|
||||||
|
template<class S> struct ContourType<DefaultMultiShape<S>> {
|
||||||
|
using Type = typename ContourType<S>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
enum class Orientation {
|
enum class Orientation {
|
||||||
CLOCKWISE,
|
CLOCKWISE,
|
||||||
@ -59,6 +139,11 @@ struct OrientationType {
|
|||||||
static const Orientation Value = Orientation::CLOCKWISE;
|
static const Orientation Value = Orientation::CLOCKWISE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class T> inline /*constexpr*/ bool is_clockwise() {
|
||||||
|
return OrientationType<TContour<T>>::Value == Orientation::CLOCKWISE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A point pair base class for other point pairs (segment, box, ...).
|
* \brief A point pair base class for other point pairs (segment, box, ...).
|
||||||
* \tparam RawPoint The actual point type to use.
|
* \tparam RawPoint The actual point type to use.
|
||||||
@ -69,21 +154,6 @@ struct PointPair {
|
|||||||
RawPoint p2;
|
RawPoint p2;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PointTag {};
|
|
||||||
struct PolygonTag {};
|
|
||||||
struct PathTag {};
|
|
||||||
struct MultiPolygonTag {};
|
|
||||||
struct BoxTag {};
|
|
||||||
struct CircleTag {};
|
|
||||||
|
|
||||||
/// Meta-functions to derive the tags
|
|
||||||
template<class Shape> struct ShapeTag { using Type = typename Shape::Tag; };
|
|
||||||
template<class S> using Tag = typename ShapeTag<remove_cvref_t<S>>::Type;
|
|
||||||
|
|
||||||
template<class S> struct MultiShape { using Type = std::vector<S>; };
|
|
||||||
template<class S>
|
|
||||||
using TMultiShape =typename MultiShape<remove_cvref_t<S>>::Type;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief An abstraction of a box;
|
* \brief An abstraction of a box;
|
||||||
*/
|
*/
|
||||||
@ -114,11 +184,16 @@ public:
|
|||||||
|
|
||||||
inline RawPoint center() const BP2D_NOEXCEPT;
|
inline RawPoint center() const BP2D_NOEXCEPT;
|
||||||
|
|
||||||
inline double area() const BP2D_NOEXCEPT {
|
template<class Unit = TCompute<RawPoint>>
|
||||||
return double(width()*height());
|
inline Unit area() const BP2D_NOEXCEPT {
|
||||||
|
return Unit(width())*height();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class S> struct PointType<_Box<S>> {
|
||||||
|
using Type = typename _Box<S>::PointType;
|
||||||
|
};
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
class _Circle {
|
class _Circle {
|
||||||
RawPoint center_;
|
RawPoint center_;
|
||||||
@ -129,7 +204,6 @@ public:
|
|||||||
using PointType = RawPoint;
|
using PointType = RawPoint;
|
||||||
|
|
||||||
_Circle() = default;
|
_Circle() = default;
|
||||||
|
|
||||||
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
|
_Circle(const RawPoint& center, double r): center_(center), radius_(r) {}
|
||||||
|
|
||||||
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
|
inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; }
|
||||||
@ -139,10 +213,14 @@ public:
|
|||||||
inline void radius(double r) { radius_ = r; }
|
inline void radius(double r) { radius_ = r; }
|
||||||
|
|
||||||
inline double area() const BP2D_NOEXCEPT {
|
inline double area() const BP2D_NOEXCEPT {
|
||||||
return 2.0*Pi*radius_*radius_;
|
return Pi_2 * radius_ * radius_;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class S> struct PointType<_Circle<S>> {
|
||||||
|
using Type = typename _Circle<S>::PointType;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief An abstraction of a directed line segment with two points.
|
* \brief An abstraction of a directed line segment with two points.
|
||||||
*/
|
*/
|
||||||
@ -185,7 +263,12 @@ public:
|
|||||||
inline Radians angleToXaxis() const;
|
inline Radians angleToXaxis() const;
|
||||||
|
|
||||||
/// The length of the segment in the measure of the coordinate system.
|
/// The length of the segment in the measure of the coordinate system.
|
||||||
inline double length();
|
template<class Unit = TCompute<RawPoint>> inline Unit sqlength() const;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class S> struct PointType<_Segment<S>> {
|
||||||
|
using Type = typename _Circle<S>::PointType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This struct serves almost as a namespace. The only difference is that is can
|
// This struct serves almost as a namespace. The only difference is that is can
|
||||||
@ -216,33 +299,56 @@ inline TCoord<RawPoint>& y(RawPoint& p)
|
|||||||
return p.y();
|
return p.y();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint, class Unit = TCompute<RawPoint>>
|
||||||
inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/)
|
inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawPoint>::value,
|
auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2));
|
||||||
"PointLike::distance(point, point) unimplemented!");
|
Unit a = (x2 - x1), b = (y2 - y1);
|
||||||
return 0;
|
return a * a + b * b;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
inline double distance(const RawPoint& /*p1*/,
|
inline double distance(const RawPoint& p1, const RawPoint& p2)
|
||||||
const _Segment<RawPoint>& /*s*/)
|
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawPoint>::value,
|
return std::sqrt(squaredDistance<RawPoint, double>(p1, p2));
|
||||||
"PointLike::distance(point, segment) unimplemented!");
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawPoint>
|
// create perpendicular vector
|
||||||
inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
|
template<class Pt> inline Pt perp(const Pt& p)
|
||||||
|
{
|
||||||
|
return Pt(y(p), -x(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Pt, class Unit = TCompute<Pt>>
|
||||||
|
inline Unit dotperp(const Pt& a, const Pt& b)
|
||||||
|
{
|
||||||
|
return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
// dot product
|
||||||
|
template<class Pt, class Unit = TCompute<Pt>>
|
||||||
|
inline Unit dot(const Pt& a, const Pt& b)
|
||||||
|
{
|
||||||
|
return Unit(x(a)) * x(b) + Unit(y(a)) * y(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// squared vector magnitude
|
||||||
|
template<class Pt, class Unit = TCompute<Pt>>
|
||||||
|
inline Unit magnsq(const Pt& p)
|
||||||
|
{
|
||||||
|
return Unit(x(p)) * x(p) + Unit(y(p)) * y(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawPoint, class Unit = TCompute<RawPoint>>
|
||||||
|
inline std::pair<Unit, bool> horizontalDistance(
|
||||||
const RawPoint& p, const _Segment<RawPoint>& s)
|
const RawPoint& p, const _Segment<RawPoint>& s)
|
||||||
{
|
{
|
||||||
using Unit = TCoord<RawPoint>;
|
namespace pl = pointlike;
|
||||||
auto x = pointlike::x(p), y = pointlike::y(p);
|
auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
|
||||||
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
|
auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
|
||||||
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
|
auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
|
||||||
|
|
||||||
TCoord<RawPoint> ret;
|
Unit ret;
|
||||||
|
|
||||||
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
|
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
|
||||||
return {0, false};
|
return {0, false};
|
||||||
@ -250,8 +356,7 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
|
|||||||
ret = std::min( x-x1, x -x2);
|
ret = std::min( x-x1, x -x2);
|
||||||
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
|
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
|
||||||
ret = -std::min(x1 - x, x2 - x);
|
ret = -std::min(x1 - x, x2 - x);
|
||||||
else if(std::abs(y - y1) <= std::numeric_limits<Unit>::epsilon() &&
|
else if(y == y1 && y == y2)
|
||||||
std::abs(y - y2) <= std::numeric_limits<Unit>::epsilon())
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
else
|
else
|
||||||
ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2);
|
ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2);
|
||||||
@ -259,16 +364,16 @@ inline std::pair<TCoord<RawPoint>, bool> horizontalDistance(
|
|||||||
return {ret, true};
|
return {ret, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint, class Unit = TCompute<RawPoint>>
|
||||||
inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
|
inline std::pair<Unit, bool> verticalDistance(
|
||||||
const RawPoint& p, const _Segment<RawPoint>& s)
|
const RawPoint& p, const _Segment<RawPoint>& s)
|
||||||
{
|
{
|
||||||
using Unit = TCoord<RawPoint>;
|
namespace pl = pointlike;
|
||||||
auto x = pointlike::x(p), y = pointlike::y(p);
|
auto x = Unit(pl::x(p)), y = Unit(pl::y(p));
|
||||||
auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first());
|
auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first()));
|
||||||
auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second());
|
auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second()));
|
||||||
|
|
||||||
TCoord<RawPoint> ret;
|
Unit ret;
|
||||||
|
|
||||||
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
|
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
|
||||||
return {0, false};
|
return {0, false};
|
||||||
@ -276,8 +381,7 @@ inline std::pair<TCoord<RawPoint>, bool> verticalDistance(
|
|||||||
ret = std::min( y-y1, y -y2);
|
ret = std::min( y-y1, y -y2);
|
||||||
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
|
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
|
||||||
ret = -std::min(y1 - y, y2 - y);
|
ret = -std::min(y1 - y, y2 - y);
|
||||||
else if(std::abs(x - x1) <= std::numeric_limits<Unit>::epsilon() &&
|
else if(x == x1 && x == x2)
|
||||||
std::abs(x - x2) <= std::numeric_limits<Unit>::epsilon())
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
else
|
else
|
||||||
ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2);
|
ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2);
|
||||||
@ -333,9 +437,10 @@ inline Radians _Segment<RawPoint>::angleToXaxis() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
inline double _Segment<RawPoint>::length()
|
template<class Unit>
|
||||||
|
inline Unit _Segment<RawPoint>::sqlength() const
|
||||||
{
|
{
|
||||||
return pointlike::distance(first(), second());
|
return pointlike::squaredDistance<RawPoint, Unit>(first(), second());
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawPoint>
|
template<class RawPoint>
|
||||||
@ -346,8 +451,8 @@ inline RawPoint _Box<RawPoint>::center() const BP2D_NOEXCEPT {
|
|||||||
using Coord = TCoord<RawPoint>;
|
using Coord = TCoord<RawPoint>;
|
||||||
|
|
||||||
RawPoint ret = { // No rounding here, we dont know if these are int coords
|
RawPoint ret = { // No rounding here, we dont know if these are int coords
|
||||||
static_cast<Coord>( (getX(minc) + getX(maxc))/2.0 ),
|
Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
|
||||||
static_cast<Coord>( (getY(minc) + getY(maxc))/2.0 )
|
Coord( (getY(minc) + getY(maxc)) / Coord(2) )
|
||||||
};
|
};
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -362,9 +467,6 @@ enum class Formats {
|
|||||||
// used in friend declarations and can be aliased at class scope.
|
// used in friend declarations and can be aliased at class scope.
|
||||||
namespace shapelike {
|
namespace shapelike {
|
||||||
|
|
||||||
template<class RawShape>
|
|
||||||
using Shapes = TMultiShape<RawShape>;
|
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
inline RawShape create(const TContour<RawShape>& contour,
|
inline RawShape create(const TContour<RawShape>& contour,
|
||||||
const THolesContainer<RawShape>& holes)
|
const THolesContainer<RawShape>& holes)
|
||||||
@ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&)
|
|||||||
template<class RawShape, class...Args>
|
template<class RawShape, class...Args>
|
||||||
inline void addVertex(RawShape& sh, const PathTag&, Args...args)
|
inline void addVertex(RawShape& sh, const PathTag&, Args...args)
|
||||||
{
|
{
|
||||||
return sh.emplace_back(std::forward<Args>(args)...);
|
sh.emplace_back(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape, class Fn>
|
template<class RawShape, class Fn>
|
||||||
@ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/)
|
|||||||
"shapelike::unserialize() unimplemented!");
|
"shapelike::unserialize() unimplemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class Cntr, class Unit = double>
|
||||||
inline double area(const RawShape& /*sh*/, const PolygonTag&)
|
inline Unit area(const Cntr& poly, const PathTag& );
|
||||||
{
|
|
||||||
static_assert(always_false<RawShape>::value,
|
|
||||||
"shapelike::area() unimplemented!");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
|
inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/)
|
||||||
@ -556,14 +653,14 @@ inline bool touches( const TPoint<RawShape>& /*point*/,
|
|||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
|
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& /*sh*/,
|
||||||
const PolygonTag&)
|
const PathTag&)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShape>::value,
|
static_assert(always_false<RawShape>::value,
|
||||||
"shapelike::boundingBox(shape) unimplemented!");
|
"shapelike::boundingBox(shape) unimplemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShapes>
|
template<class RawShapes>
|
||||||
inline _Box<TPoint<typename RawShapes::value_type>>
|
inline _Box<TPoint<RawShapes>>
|
||||||
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
|
boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
|
||||||
{
|
{
|
||||||
static_assert(always_false<RawShapes>::value,
|
static_assert(always_false<RawShapes>::value,
|
||||||
@ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&)
|
inline RawShape convexHull(const RawShape& sh, const PathTag&);
|
||||||
{
|
|
||||||
static_assert(always_false<RawShape>::value,
|
|
||||||
"shapelike::convexHull(shape) unimplemented!");
|
|
||||||
return RawShape();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShapes>
|
template<class RawShapes, class S = typename RawShapes::value_type>
|
||||||
inline typename RawShapes::value_type
|
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&);
|
||||||
convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&)
|
|
||||||
{
|
|
||||||
static_assert(always_false<RawShapes>::value,
|
|
||||||
"shapelike::convexHull(shapes) unimplemented!");
|
|
||||||
return typename RawShapes::value_type();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
|
inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
|
||||||
@ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) {
|
|||||||
template<class RawShape, class...Args>
|
template<class RawShape, class...Args>
|
||||||
inline void addVertex(RawShape& sh, const PolygonTag&, Args...args)
|
inline void addVertex(RawShape& sh, const PolygonTag&, Args...args)
|
||||||
{
|
{
|
||||||
return addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
|
addVertex(contour(sh), PathTag(), std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class RawShape, class...Args> // Tag dispatcher
|
template<class RawShape, class...Args> // Tag dispatcher
|
||||||
inline void addVertex(RawShape& sh, Args...args)
|
inline void addVertex(RawShape& sh, Args...args)
|
||||||
{
|
{
|
||||||
return addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
|
addVertex(sh, Tag<RawShape>(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
inline _Box<TPoint<RawShape>> boundingBox(const RawShape& poly, const PolygonTag&)
|
||||||
|
{
|
||||||
|
return boundingBox(contour(poly), PathTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Box>
|
template<class Box>
|
||||||
@ -786,7 +878,7 @@ inline _Box<TPoint<S>> boundingBox(const S& sh)
|
|||||||
template<class Box>
|
template<class Box>
|
||||||
inline double area(const Box& box, const BoxTag& )
|
inline double area(const Box& box, const BoxTag& )
|
||||||
{
|
{
|
||||||
return box.area();
|
return box.template area<double>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Circle>
|
template<class Circle>
|
||||||
@ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& )
|
|||||||
return circ.area();
|
return circ.area();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class Cntr, class Unit>
|
||||||
|
inline Unit area(const Cntr& poly, const PathTag& )
|
||||||
|
{
|
||||||
|
namespace sl = shapelike;
|
||||||
|
if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0;
|
||||||
|
|
||||||
|
Unit a = 0;
|
||||||
|
for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly));
|
||||||
|
i < sl::cend(poly); ++i)
|
||||||
|
{
|
||||||
|
auto xj = Unit(getX(*j)), yj = Unit(getY(*j));
|
||||||
|
auto xi = Unit(getX(*i)), yi = Unit(getY(*i));
|
||||||
|
a += (xj + xi) * (yj - yi);
|
||||||
|
j = i;
|
||||||
|
}
|
||||||
|
a /= 2;
|
||||||
|
return is_clockwise<Cntr>() ? a : -a;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class S> inline double area(const S& poly, const PolygonTag& )
|
||||||
|
{
|
||||||
|
auto hls = holes(poly);
|
||||||
|
return std::accumulate(hls.begin(), hls.end(),
|
||||||
|
area(contour(poly), PathTag()),
|
||||||
|
[](double a, const TContour<S> &h){
|
||||||
|
return a + area(h, PathTag());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
template<class RawShape> // Dispatching function
|
template<class RawShape> // Dispatching function
|
||||||
inline double area(const RawShape& sh)
|
inline double area(const RawShape& sh)
|
||||||
{
|
{
|
||||||
@ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
inline RawShape convexHull(const RawShape& sh, const PolygonTag&)
|
||||||
|
{
|
||||||
|
return create<RawShape>(convexHull(contour(sh), PathTag()));
|
||||||
|
}
|
||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
inline auto convexHull(const RawShape& sh)
|
inline auto convexHull(const RawShape& sh)
|
||||||
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
|
-> decltype(convexHull(sh, Tag<RawShape>())) // TODO: C++14 could deduce
|
||||||
@ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh)
|
|||||||
return convexHull(sh, Tag<RawShape>());
|
return convexHull(sh, Tag<RawShape>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class RawShape>
|
||||||
|
inline RawShape convexHull(const RawShape& sh, const PathTag&)
|
||||||
|
{
|
||||||
|
using Unit = TCompute<RawShape>;
|
||||||
|
using Point = TPoint<RawShape>;
|
||||||
|
namespace sl = shapelike;
|
||||||
|
|
||||||
|
size_t edges = sl::cend(sh) - sl::cbegin(sh);
|
||||||
|
if(edges <= 3) return {};
|
||||||
|
|
||||||
|
bool closed = false;
|
||||||
|
std::vector<Point> U, L;
|
||||||
|
U.reserve(1 + edges / 2); L.reserve(1 + edges / 2);
|
||||||
|
|
||||||
|
std::vector<Point> pts; pts.reserve(edges);
|
||||||
|
std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts));
|
||||||
|
|
||||||
|
auto fpt = pts.front(), lpt = pts.back();
|
||||||
|
if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) {
|
||||||
|
closed = true; pts.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(pts.begin(), pts.end(),
|
||||||
|
[](const Point& v1, const Point& v2)
|
||||||
|
{
|
||||||
|
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
|
||||||
|
return x1 == x2 ? y1 < y2 : x1 < x2;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto dir = [](const Point& p, const Point& q, const Point& r) {
|
||||||
|
return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
|
||||||
|
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ik = pts.begin();
|
||||||
|
|
||||||
|
while(ik != pts.end()) {
|
||||||
|
|
||||||
|
while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0)
|
||||||
|
U.pop_back();
|
||||||
|
while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0)
|
||||||
|
L.pop_back();
|
||||||
|
|
||||||
|
U.emplace_back(*ik);
|
||||||
|
L.emplace_back(*ik);
|
||||||
|
|
||||||
|
++ik;
|
||||||
|
}
|
||||||
|
|
||||||
|
RawShape ret; reserve(ret, U.size() + L.size());
|
||||||
|
if(is_clockwise<RawShape>()) {
|
||||||
|
for(auto it = U.begin(); it != std::prev(U.end()); ++it)
|
||||||
|
addVertex(ret, *it);
|
||||||
|
for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it)
|
||||||
|
addVertex(ret, *it);
|
||||||
|
if(closed) addVertex(ret, *std::prev(L.rend()));
|
||||||
|
} else {
|
||||||
|
for(auto it = L.begin(); it != std::prev(L.end()); ++it)
|
||||||
|
addVertex(ret, *it);
|
||||||
|
for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it)
|
||||||
|
addVertex(ret, *it);
|
||||||
|
if(closed) addVertex(ret, *std::prev(U.rend()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class RawShapes, class S>
|
||||||
|
inline S convexHull(const RawShapes& sh, const MultiPolygonTag&)
|
||||||
|
{
|
||||||
|
namespace sl = shapelike;
|
||||||
|
S cntr;
|
||||||
|
for(auto& poly : sh)
|
||||||
|
for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it)
|
||||||
|
addVertex(cntr, *it);
|
||||||
|
|
||||||
|
return convexHull(cntr, Tag<S>());
|
||||||
|
}
|
||||||
|
|
||||||
template<class TP, class TC>
|
template<class TP, class TC>
|
||||||
inline bool isInside(const TP& point, const TC& circ,
|
inline bool isInside(const TP& point, const TC& circ,
|
||||||
const PointTag&, const CircleTag&)
|
const PointTag&, const CircleTag&)
|
||||||
{
|
{
|
||||||
return pointlike::distance(point, circ.center()) < circ.radius();
|
auto r = circ.radius();
|
||||||
|
return pointlike::squaredDistance(point, circ.center()) < r * r;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class TP, class TB>
|
template<class TP, class TB>
|
||||||
@ -972,6 +1179,9 @@ template<class RawShape> inline bool isConvex(const RawShape& sh) // dispatch
|
|||||||
using Segment = _Segment<Point>; \
|
using Segment = _Segment<Point>; \
|
||||||
using Polygons = TMultiShape<T>
|
using Polygons = TMultiShape<T>
|
||||||
|
|
||||||
|
namespace sl = shapelike;
|
||||||
|
namespace pl = pointlike;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // GEOMETRY_TRAITS_HPP
|
#endif // GEOMETRY_TRAITS_HPP
|
||||||
|
@ -1,26 +1,22 @@
|
|||||||
#ifndef GEOMETRIES_NOFITPOLYGON_HPP
|
#ifndef GEOMETRIES_NOFITPOLYGON_HPP
|
||||||
#define GEOMETRIES_NOFITPOLYGON_HPP
|
#define GEOMETRIES_NOFITPOLYGON_HPP
|
||||||
|
|
||||||
#include "geometry_traits.hpp"
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
|
#include <libnest2d/geometry_traits.hpp>
|
||||||
|
|
||||||
namespace libnest2d {
|
namespace libnest2d {
|
||||||
|
|
||||||
namespace __nfp {
|
namespace __nfp {
|
||||||
// Do not specialize this...
|
// Do not specialize this...
|
||||||
template<class RawShape>
|
template<class RawShape, class Unit = TCompute<RawShape>>
|
||||||
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
|
inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
|
||||||
{
|
{
|
||||||
using Coord = TCoord<TPoint<RawShape>>;
|
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
|
||||||
Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2);
|
return y1 == y2 ? x1 < x2 : y1 < y2;
|
||||||
auto diff = y1 - y2;
|
|
||||||
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
|
|
||||||
return x1 < x2;
|
|
||||||
|
|
||||||
return diff < 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
|
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
|
||||||
@ -202,7 +198,7 @@ inline TPoint<RawShape> referenceVertex(const RawShape& sh)
|
|||||||
* convex as well in this case.
|
* convex as well in this case.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
template<class RawShape>
|
template<class RawShape, class Ratio = double>
|
||||||
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
||||||
const RawShape& other)
|
const RawShape& other)
|
||||||
{
|
{
|
||||||
@ -239,11 +235,61 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the edges by angle to X axis.
|
|
||||||
std::sort(edgelist.begin(), edgelist.end(),
|
std::sort(edgelist.begin(), edgelist.end(),
|
||||||
[](const Edge& e1, const Edge& e2)
|
[](const Edge& e1, const Edge& e2)
|
||||||
{
|
{
|
||||||
return e1.angleToXaxis() > e2.angleToXaxis();
|
Vertex ax(1, 0); // Unit vector for the X axis
|
||||||
|
|
||||||
|
// get cectors from the edges
|
||||||
|
Vertex p1 = e1.second() - e1.first();
|
||||||
|
Vertex p2 = e2.second() - e2.first();
|
||||||
|
|
||||||
|
// Quadrant mapping array. The quadrant of a vector can be determined
|
||||||
|
// from the dot product of the vector and its perpendicular pair
|
||||||
|
// with the unit vector X axis. The products will carry the values
|
||||||
|
// lcos = dot(p, ax) = l * cos(phi) and
|
||||||
|
// lsin = -dotperp(p, ax) = l * sin(phi) where
|
||||||
|
// l is the length of vector p. From the signs of these values we can
|
||||||
|
// construct an index which has the sign of lcos as MSB and the
|
||||||
|
// sign of lsin as LSB. This index can be used to retrieve the actual
|
||||||
|
// quadrant where vector p resides using the following map:
|
||||||
|
// (+ is 0, - is 1)
|
||||||
|
// cos | sin | decimal | quadrant
|
||||||
|
// + | + | 0 | 0
|
||||||
|
// + | - | 1 | 3
|
||||||
|
// - | + | 2 | 1
|
||||||
|
// - | - | 3 | 2
|
||||||
|
std::array<int, 4> quadrants {0, 3, 1, 2 };
|
||||||
|
|
||||||
|
std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
|
||||||
|
|
||||||
|
using TDots = std::array<TCompute<Vertex>, 2>;
|
||||||
|
TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) };
|
||||||
|
TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) };
|
||||||
|
|
||||||
|
// Construct the quadrant indices for p1 and p2
|
||||||
|
for(size_t i = 0; i < 2; ++i)
|
||||||
|
if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3;
|
||||||
|
else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2;
|
||||||
|
else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
|
||||||
|
|
||||||
|
if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
|
||||||
|
auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt
|
||||||
|
auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt
|
||||||
|
|
||||||
|
// We will actually compare l^2 * cos^2(phi) which saturates the
|
||||||
|
// cos function. But with the quadrant info we can get the sign back
|
||||||
|
int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
|
||||||
|
|
||||||
|
// If Ratio is an actual rational type, there is no precision loss
|
||||||
|
auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
|
||||||
|
auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
|
||||||
|
|
||||||
|
return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If in different quadrants, compare the quadrant indices only.
|
||||||
|
return q[0] > q[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
__nfp::buildPolygon(edgelist, rsh, top_nfp);
|
__nfp::buildPolygon(edgelist, rsh, top_nfp);
|
||||||
@ -253,456 +299,9 @@ inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
|
|||||||
|
|
||||||
template<class RawShape>
|
template<class RawShape>
|
||||||
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
|
NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
|
||||||
const RawShape& cother)
|
const RawShape& cother)
|
||||||
{
|
{
|
||||||
|
return {};
|
||||||
// Algorithms are from the original algorithm proposed in paper:
|
|
||||||
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
// Algorithm 1: Obtaining the minkowski sum
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// I guess this is not a full minkowski sum of the two input polygons by
|
|
||||||
// definition. This yields a subset that is compatible with the next 2
|
|
||||||
// algorithms.
|
|
||||||
|
|
||||||
using Result = NfpResult<RawShape>;
|
|
||||||
using Vertex = TPoint<RawShape>;
|
|
||||||
using Coord = TCoord<Vertex>;
|
|
||||||
using Edge = _Segment<Vertex>;
|
|
||||||
namespace sl = shapelike;
|
|
||||||
using std::signbit;
|
|
||||||
using std::sort;
|
|
||||||
using std::vector;
|
|
||||||
using std::ref;
|
|
||||||
using std::reference_wrapper;
|
|
||||||
|
|
||||||
// TODO The original algorithms expects the stationary polygon in
|
|
||||||
// counter clockwise and the orbiter in clockwise order.
|
|
||||||
// So for preventing any further complication, I will make the input
|
|
||||||
// the way it should be, than make my way around the orientations.
|
|
||||||
|
|
||||||
// Reverse the stationary contour to counter clockwise
|
|
||||||
auto stcont = sl::contour(cstationary);
|
|
||||||
{
|
|
||||||
std::reverse(sl::begin(stcont), sl::end(stcont));
|
|
||||||
stcont.pop_back();
|
|
||||||
auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
|
|
||||||
[](const Vertex& v1, const Vertex& v2) {
|
|
||||||
return getY(v1) < getY(v2);
|
|
||||||
});
|
|
||||||
std::rotate(sl::begin(stcont), it, sl::end(stcont));
|
|
||||||
sl::addVertex(stcont, sl::front(stcont));
|
|
||||||
}
|
|
||||||
RawShape stationary;
|
|
||||||
sl::contour(stationary) = stcont;
|
|
||||||
|
|
||||||
// Reverse the orbiter contour to counter clockwise
|
|
||||||
auto orbcont = sl::contour(cother);
|
|
||||||
{
|
|
||||||
std::reverse(orbcont.begin(), orbcont.end());
|
|
||||||
|
|
||||||
// Step 1: Make the orbiter reverse oriented
|
|
||||||
|
|
||||||
orbcont.pop_back();
|
|
||||||
auto it = std::min_element(orbcont.begin(), orbcont.end(),
|
|
||||||
[](const Vertex& v1, const Vertex& v2) {
|
|
||||||
return getY(v1) < getY(v2);
|
|
||||||
});
|
|
||||||
|
|
||||||
std::rotate(orbcont.begin(), it, orbcont.end());
|
|
||||||
orbcont.emplace_back(orbcont.front());
|
|
||||||
|
|
||||||
for(auto &v : orbcont) v = -v;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the orbiter (contour only), we will have to work on it
|
|
||||||
RawShape orbiter;
|
|
||||||
sl::contour(orbiter) = orbcont;
|
|
||||||
|
|
||||||
// An edge with additional data for marking it
|
|
||||||
struct MarkedEdge {
|
|
||||||
Edge e; Radians turn_angle = 0; bool is_turning_point = false;
|
|
||||||
MarkedEdge() = default;
|
|
||||||
MarkedEdge(const Edge& ed, Radians ta, bool tp):
|
|
||||||
e(ed), turn_angle(ta), is_turning_point(tp) {}
|
|
||||||
|
|
||||||
// debug
|
|
||||||
std::string label;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Container for marked edges
|
|
||||||
using EdgeList = vector<MarkedEdge>;
|
|
||||||
|
|
||||||
EdgeList A, B;
|
|
||||||
|
|
||||||
// This is how an edge list is created from the polygons
|
|
||||||
auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
|
|
||||||
auto& poly = sl::contour(ppoly);
|
|
||||||
|
|
||||||
L.reserve(sl::contourVertexCount(poly));
|
|
||||||
|
|
||||||
if(dir > 0) {
|
|
||||||
auto it = poly.begin();
|
|
||||||
auto nextit = std::next(it);
|
|
||||||
|
|
||||||
double turn_angle = 0;
|
|
||||||
bool is_turn_point = false;
|
|
||||||
|
|
||||||
while(nextit != poly.end()) {
|
|
||||||
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
|
|
||||||
it++; nextit++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto it = sl::rbegin(poly);
|
|
||||||
auto nextit = std::next(it);
|
|
||||||
|
|
||||||
double turn_angle = 0;
|
|
||||||
bool is_turn_point = false;
|
|
||||||
|
|
||||||
while(nextit != sl::rend(poly)) {
|
|
||||||
L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
|
|
||||||
it++; nextit++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
|
|
||||||
auto phi = e1.angleToXaxis();
|
|
||||||
auto phi_prev = e2.angleToXaxis();
|
|
||||||
auto turn_angle = phi-phi_prev;
|
|
||||||
if(turn_angle > Pi) turn_angle -= TwoPi;
|
|
||||||
if(turn_angle < -Pi) turn_angle += TwoPi;
|
|
||||||
return turn_angle;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto eit = L.begin();
|
|
||||||
auto enext = std::next(eit);
|
|
||||||
|
|
||||||
eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
|
|
||||||
|
|
||||||
while(enext != L.end()) {
|
|
||||||
enext->turn_angle = getTurnAngle( enext->e, eit->e);
|
|
||||||
eit->is_turning_point =
|
|
||||||
signbit(enext->turn_angle) != signbit(eit->turn_angle);
|
|
||||||
++eit; ++enext;
|
|
||||||
}
|
|
||||||
|
|
||||||
L.back().is_turning_point = signbit(L.back().turn_angle) !=
|
|
||||||
signbit(L.front().turn_angle);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 2: Fill the edgelists
|
|
||||||
fillEdgeList(A, stationary, 1);
|
|
||||||
fillEdgeList(B, orbiter, 1);
|
|
||||||
|
|
||||||
int i = 1;
|
|
||||||
for(MarkedEdge& me : A) {
|
|
||||||
std::cout << "a" << i << ":\n\t"
|
|
||||||
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
|
|
||||||
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
|
|
||||||
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
me.label = "a"; me.label += std::to_string(i);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 1;
|
|
||||||
for(MarkedEdge& me : B) {
|
|
||||||
std::cout << "b" << i << ":\n\t"
|
|
||||||
<< getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
|
|
||||||
<< getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
|
|
||||||
<< "Turning point: " << (me.is_turning_point ? "yes" : "no")
|
|
||||||
<< std::endl;
|
|
||||||
me.label = "b"; me.label += std::to_string(i);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A reference to a marked edge that also knows its container
|
|
||||||
struct MarkedEdgeRef {
|
|
||||||
reference_wrapper<MarkedEdge> eref;
|
|
||||||
reference_wrapper<vector<MarkedEdgeRef>> container;
|
|
||||||
Coord dir = 1; // Direction modifier
|
|
||||||
|
|
||||||
inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
|
|
||||||
inline const Edge& edge() const { return eref.get().e; }
|
|
||||||
inline Edge& edge() { return eref.get().e; }
|
|
||||||
inline bool isTurningPoint() const {
|
|
||||||
return eref.get().is_turning_point;
|
|
||||||
}
|
|
||||||
inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
|
|
||||||
return &(container.get()) == &cont;
|
|
||||||
}
|
|
||||||
inline bool eq(const MarkedEdgeRef& mr) {
|
|
||||||
return &(eref.get()) == &(mr.eref.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
|
|
||||||
reference_wrapper<vector<MarkedEdgeRef>> ec):
|
|
||||||
eref(er), container(ec), dir(1) {}
|
|
||||||
|
|
||||||
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
|
|
||||||
reference_wrapper<vector<MarkedEdgeRef>> ec,
|
|
||||||
Coord d):
|
|
||||||
eref(er), container(ec), dir(d) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
using EdgeRefList = vector<MarkedEdgeRef>;
|
|
||||||
|
|
||||||
// Comparing two marked edges
|
|
||||||
auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
|
|
||||||
return e1.angleX() < e2.angleX();
|
|
||||||
};
|
|
||||||
|
|
||||||
EdgeRefList Aref, Bref; // We create containers for the references
|
|
||||||
Aref.reserve(A.size()); Bref.reserve(B.size());
|
|
||||||
|
|
||||||
// Fill reference container for the stationary polygon
|
|
||||||
std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
|
|
||||||
Aref.emplace_back( ref(me), ref(Aref) );
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fill reference container for the orbiting polygon
|
|
||||||
std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
|
|
||||||
Bref.emplace_back( ref(me), ref(Bref) );
|
|
||||||
});
|
|
||||||
|
|
||||||
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
|
|
||||||
(const EdgeRefList& Q, const EdgeRefList& R, bool positive)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
|
|
||||||
// Sort the containers of edge references and merge them.
|
|
||||||
// Q could be sorted only once and be reused here but we would still
|
|
||||||
// need to merge it with sorted(R).
|
|
||||||
|
|
||||||
EdgeRefList merged;
|
|
||||||
EdgeRefList S, seq;
|
|
||||||
merged.reserve(Q.size() + R.size());
|
|
||||||
|
|
||||||
merged.insert(merged.end(), R.begin(), R.end());
|
|
||||||
std::stable_sort(merged.begin(), merged.end(), sortfn);
|
|
||||||
merged.insert(merged.end(), Q.begin(), Q.end());
|
|
||||||
std::stable_sort(merged.begin(), merged.end(), sortfn);
|
|
||||||
|
|
||||||
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
|
|
||||||
// we don't use i, instead, q is an iterator into Q. k would be an index
|
|
||||||
// into the merged sequence but we use "it" as an iterator for that
|
|
||||||
|
|
||||||
// here we obtain references for the containers for later comparisons
|
|
||||||
const auto& Rcont = R.begin()->container.get();
|
|
||||||
const auto& Qcont = Q.begin()->container.get();
|
|
||||||
|
|
||||||
// Set the initial direction
|
|
||||||
Coord dir = 1;
|
|
||||||
|
|
||||||
// roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
|
|
||||||
if(positive) {
|
|
||||||
auto q = Q.begin();
|
|
||||||
S.emplace_back(*q);
|
|
||||||
|
|
||||||
// Roughly step 3
|
|
||||||
|
|
||||||
std::cout << "merged size: " << merged.size() << std::endl;
|
|
||||||
auto mit = merged.begin();
|
|
||||||
for(bool finish = false; !finish && q != Q.end();) {
|
|
||||||
++q; // "Set i = i + 1"
|
|
||||||
|
|
||||||
while(!finish && mit != merged.end()) {
|
|
||||||
if(mit->isFrom(Rcont)) {
|
|
||||||
auto s = *mit;
|
|
||||||
s.dir = dir;
|
|
||||||
S.emplace_back(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mit->eq(*q)) {
|
|
||||||
S.emplace_back(*q);
|
|
||||||
if(mit->isTurningPoint()) dir = -dir;
|
|
||||||
if(q == Q.begin()) finish = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mit += dir;
|
|
||||||
// __nfp::advance(mit, merged, dir > 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto q = Q.rbegin();
|
|
||||||
S.emplace_back(*q);
|
|
||||||
|
|
||||||
// Roughly step 3
|
|
||||||
|
|
||||||
std::cout << "merged size: " << merged.size() << std::endl;
|
|
||||||
auto mit = merged.begin();
|
|
||||||
for(bool finish = false; !finish && q != Q.rend();) {
|
|
||||||
++q; // "Set i = i + 1"
|
|
||||||
|
|
||||||
while(!finish && mit != merged.end()) {
|
|
||||||
if(mit->isFrom(Rcont)) {
|
|
||||||
auto s = *mit;
|
|
||||||
s.dir = dir;
|
|
||||||
S.emplace_back(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mit->eq(*q)) {
|
|
||||||
S.emplace_back(*q);
|
|
||||||
S.back().dir = -1;
|
|
||||||
if(mit->isTurningPoint()) dir = -dir;
|
|
||||||
if(q == Q.rbegin()) finish = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mit += dir;
|
|
||||||
// __nfp::advance(mit, merged, dir > 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Step 4:
|
|
||||||
|
|
||||||
// "Let starting edge r1 be in position si in sequence"
|
|
||||||
// whaaat? I guess this means the following:
|
|
||||||
auto it = S.begin();
|
|
||||||
while(!it->eq(*R.begin())) ++it;
|
|
||||||
|
|
||||||
// "Set j = 1, next = 2, direction = 1, seq1 = si"
|
|
||||||
// we don't use j, seq is expanded dynamically.
|
|
||||||
dir = 1;
|
|
||||||
auto next = std::next(R.begin()); seq.emplace_back(*it);
|
|
||||||
|
|
||||||
// Step 5:
|
|
||||||
// "If all si edges have been allocated to seqj" should mean that
|
|
||||||
// we loop until seq has equal size with S
|
|
||||||
auto send = it; //it == S.begin() ? it : std::prev(it);
|
|
||||||
while(it != S.end()) {
|
|
||||||
++it; if(it == S.end()) it = S.begin();
|
|
||||||
if(it == send) break;
|
|
||||||
|
|
||||||
if(it->isFrom(Qcont)) {
|
|
||||||
seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
|
|
||||||
|
|
||||||
// "If si is a turning point in Q,
|
|
||||||
// direction = - direction, next = next + direction"
|
|
||||||
if(it->isTurningPoint()) {
|
|
||||||
dir = -dir;
|
|
||||||
next += dir;
|
|
||||||
// __nfp::advance(next, R, dir > 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
|
|
||||||
// "j = j + 1, seqj = si, next = next + direction"
|
|
||||||
seq.emplace_back(*it);
|
|
||||||
next += dir;
|
|
||||||
// __nfp::advance(next, R, dir > 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return seq;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<EdgeRefList> seqlist;
|
|
||||||
seqlist.reserve(Bref.size());
|
|
||||||
|
|
||||||
EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
|
|
||||||
|
|
||||||
// make the slope diagram of B
|
|
||||||
std::sort(Bslope.begin(), Bslope.end(), sortfn);
|
|
||||||
|
|
||||||
auto slopeit = Bslope.begin(); // search for the first turning point
|
|
||||||
while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
|
|
||||||
|
|
||||||
if(slopeit == Bslope.end()) {
|
|
||||||
// no turning point means convex polygon.
|
|
||||||
seqlist.emplace_back(mink(Aref, Bref, true));
|
|
||||||
} else {
|
|
||||||
int dir = 1;
|
|
||||||
|
|
||||||
auto firstturn = Bref.begin();
|
|
||||||
while(!firstturn->eq(*slopeit)) ++firstturn;
|
|
||||||
|
|
||||||
assert(firstturn != Bref.end());
|
|
||||||
|
|
||||||
EdgeRefList bgroup; bgroup.reserve(Bref.size());
|
|
||||||
bgroup.emplace_back(*slopeit);
|
|
||||||
|
|
||||||
auto b_it = std::next(firstturn);
|
|
||||||
while(b_it != firstturn) {
|
|
||||||
if(b_it == Bref.end()) b_it = Bref.begin();
|
|
||||||
|
|
||||||
while(!slopeit->eq(*b_it)) {
|
|
||||||
__nfp::advance(slopeit, Bslope, dir > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!slopeit->isTurningPoint()) {
|
|
||||||
bgroup.emplace_back(*slopeit);
|
|
||||||
} else {
|
|
||||||
if(!bgroup.empty()) {
|
|
||||||
if(dir > 0) bgroup.emplace_back(*slopeit);
|
|
||||||
for(auto& me : bgroup) {
|
|
||||||
std::cout << me.eref.get().label << ", ";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
|
|
||||||
bgroup.clear();
|
|
||||||
if(dir < 0) bgroup.emplace_back(*slopeit);
|
|
||||||
} else {
|
|
||||||
bgroup.emplace_back(*slopeit);
|
|
||||||
}
|
|
||||||
|
|
||||||
dir *= -1;
|
|
||||||
}
|
|
||||||
++b_it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// while(it != Bref.end()) // This is step 3 and step 4 in one loop
|
|
||||||
// if(it->isTurningPoint()) {
|
|
||||||
// R = {R.last, it++};
|
|
||||||
// auto seq = mink(Q, R, orientation);
|
|
||||||
|
|
||||||
// // TODO step 6 (should be 5 shouldn't it?): linking edges from A
|
|
||||||
// // I don't get this step
|
|
||||||
|
|
||||||
// seqlist.insert(seqlist.end(), seq.begin(), seq.end());
|
|
||||||
// orientation = !orientation;
|
|
||||||
// } else ++it;
|
|
||||||
|
|
||||||
// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
// Algorithm 2: breaking Minkowski sums into track line trips
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
// Algorithm 3: finding the boundary of the NFP from track line trips
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
for(auto& seq : seqlist) {
|
|
||||||
std::cout << "seqlist size: " << seq.size() << std::endl;
|
|
||||||
for(auto& s : seq) {
|
|
||||||
std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
|
|
||||||
}
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& seq = seqlist.front();
|
|
||||||
RawShape rsh;
|
|
||||||
Vertex top_nfp;
|
|
||||||
std::vector<Edge> edgelist; edgelist.reserve(seq.size());
|
|
||||||
for(auto& s : seq) {
|
|
||||||
edgelist.emplace_back(s.eref.get().e);
|
|
||||||
}
|
|
||||||
|
|
||||||
__nfp::buildPolygon(edgelist, rsh, top_nfp);
|
|
||||||
|
|
||||||
return Result(rsh, top_nfp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specializable NFP implementation class. Specialize it if you have a faster
|
// Specializable NFP implementation class. Specialize it if you have a faster
|
||||||
|
@ -8,13 +8,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "geometry_traits.hpp"
|
#include <libnest2d/geometry_traits.hpp>
|
||||||
|
|
||||||
namespace libnest2d {
|
namespace libnest2d {
|
||||||
|
|
||||||
namespace sl = shapelike;
|
|
||||||
namespace pl = pointlike;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief An item to be placed on a bin.
|
* \brief An item to be placed on a bin.
|
||||||
*
|
*
|
||||||
@ -422,13 +419,9 @@ private:
|
|||||||
|
|
||||||
static inline bool vsort(const Vertex& v1, const Vertex& v2)
|
static inline bool vsort(const Vertex& v1, const Vertex& v2)
|
||||||
{
|
{
|
||||||
Coord &&x1 = getX(v1), &&x2 = getX(v2);
|
TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
|
||||||
Coord &&y1 = getY(v1), &&y2 = getY(v2);
|
TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
|
||||||
auto diff = y1 - y2;
|
return y1 == y2 ? x1 < x2 : y1 < y2;
|
||||||
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
|
|
||||||
return x1 < x2;
|
|
||||||
|
|
||||||
return diff < 0;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "common.hpp"
|
|
||||||
|
#include <libnest2d/common.hpp>
|
||||||
|
|
||||||
namespace libnest2d { namespace opt {
|
namespace libnest2d { namespace opt {
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ enum class Method {
|
|||||||
L_SIMPLEX,
|
L_SIMPLEX,
|
||||||
L_SUBPLEX,
|
L_SUBPLEX,
|
||||||
G_GENETIC,
|
G_GENETIC,
|
||||||
|
G_PARTICLE_SWARM
|
||||||
//...
|
//...
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ else()
|
|||||||
target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt)
|
target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#target_sources( NloptOptimizer INTERFACE
|
#target_sources( nloptOptimizer INTERFACE
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
|
#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
|
#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp
|
||||||
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
|
#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp
|
||||||
@ -57,5 +57,5 @@ endif()
|
|||||||
|
|
||||||
target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
|
target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT)
|
||||||
|
|
||||||
# And finally plug the NloptOptimizer into libnest2d
|
# And finally plug the nloptOptimizer into libnest2d
|
||||||
#target_link_libraries(libnest2d INTERFACE NloptOptimizer)
|
#target_link_libraries(libnest2d INTERFACE nloptOptimizer)
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
find_package(Armadillo REQUIRED)
|
|
||||||
|
|
||||||
add_library(OptimlibOptimizer INTERFACE)
|
|
||||||
target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES})
|
|
@ -7,15 +7,15 @@
|
|||||||
|
|
||||||
namespace libnest2d { namespace placers {
|
namespace libnest2d { namespace placers {
|
||||||
|
|
||||||
template<class T, class = T> struct Epsilon {};
|
template<class T, class = T> struct DefaultEpsilon {};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
struct Epsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
|
struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
|
||||||
static const T Value = 1;
|
static const T Value = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
struct Epsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
|
struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
|
||||||
static const T Value = 1e-3;
|
static const T Value = 1e-3;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ struct BLConfig {
|
|||||||
DECLARE_MAIN_TYPES(RawShape);
|
DECLARE_MAIN_TYPES(RawShape);
|
||||||
|
|
||||||
Coord min_obj_distance = 0;
|
Coord min_obj_distance = 0;
|
||||||
Coord epsilon = Epsilon<Coord>::Value;
|
Coord epsilon = DefaultEpsilon<Coord>::Value;
|
||||||
bool allow_rotations = false;
|
bool allow_rotations = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,14 +103,14 @@ Key hash(const _Item<S>& item) {
|
|||||||
while(deg > N) { ms++; deg -= N; }
|
while(deg > N) { ms++; deg -= N; }
|
||||||
ls += int(deg);
|
ls += int(deg);
|
||||||
ret.push_back(char(ms)); ret.push_back(char(ls));
|
ret.push_back(char(ms)); ret.push_back(char(ls));
|
||||||
circ += seg.length();
|
circ += std::sqrt(seg.template sqlength<double>());
|
||||||
}
|
}
|
||||||
|
|
||||||
it = ctr.begin(); nx = std::next(it);
|
it = ctr.begin(); nx = std::next(it);
|
||||||
|
|
||||||
while(nx != ctr.end()) {
|
while(nx != ctr.end()) {
|
||||||
Segment seg(*it++, *nx++);
|
Segment seg(*it++, *nx++);
|
||||||
auto l = int(M * seg.length() / circ);
|
auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ);
|
||||||
int ms = 'A', ls = 'A';
|
int ms = 'A', ls = 'A';
|
||||||
while(l > N) { ms++; l -= N; }
|
while(l > N) { ms++; l -= N; }
|
||||||
ls += l;
|
ls += l;
|
||||||
@ -250,6 +250,11 @@ template<class RawShape> class EdgeCache {
|
|||||||
|
|
||||||
double accuracy_ = 1.0;
|
double accuracy_ = 1.0;
|
||||||
|
|
||||||
|
static double length(const Edge &e)
|
||||||
|
{
|
||||||
|
return std::sqrt(e.template sqlength<double>());
|
||||||
|
}
|
||||||
|
|
||||||
void createCache(const RawShape& sh) {
|
void createCache(const RawShape& sh) {
|
||||||
{ // For the contour
|
{ // For the contour
|
||||||
auto first = shapelike::cbegin(sh);
|
auto first = shapelike::cbegin(sh);
|
||||||
@ -260,7 +265,7 @@ template<class RawShape> class EdgeCache {
|
|||||||
|
|
||||||
while(next != endit) {
|
while(next != endit) {
|
||||||
contour_.emap.emplace_back(*(first++), *(next++));
|
contour_.emap.emplace_back(*(first++), *(next++));
|
||||||
contour_.full_distance += contour_.emap.back().length();
|
contour_.full_distance += length(contour_.emap.back());
|
||||||
contour_.distances.emplace_back(contour_.full_distance);
|
contour_.distances.emplace_back(contour_.full_distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,7 +280,7 @@ template<class RawShape> class EdgeCache {
|
|||||||
|
|
||||||
while(next != endit) {
|
while(next != endit) {
|
||||||
hc.emap.emplace_back(*(first++), *(next++));
|
hc.emap.emplace_back(*(first++), *(next++));
|
||||||
hc.full_distance += hc.emap.back().length();
|
hc.full_distance += length(hc.emap.back());
|
||||||
hc.distances.emplace_back(hc.full_distance);
|
hc.distances.emplace_back(hc.full_distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,19 +311,19 @@ struct range_value<bp2d::Shapes> {
|
|||||||
|
|
||||||
namespace libnest2d { // Now the algorithms that boost can provide...
|
namespace libnest2d { // Now the algorithms that boost can provide...
|
||||||
|
|
||||||
namespace pointlike {
|
//namespace pointlike {
|
||||||
template<>
|
//template<>
|
||||||
inline double distance(const PointImpl& p1, const PointImpl& p2 )
|
//inline double distance(const PointImpl& p1, const PointImpl& p2 )
|
||||||
{
|
//{
|
||||||
return boost::geometry::distance(p1, p2);
|
// return boost::geometry::distance(p1, p2);
|
||||||
}
|
//}
|
||||||
|
|
||||||
template<>
|
//template<>
|
||||||
inline double distance(const PointImpl& p, const bp2d::Segment& seg )
|
//inline double distance(const PointImpl& p, const bp2d::Segment& seg )
|
||||||
{
|
//{
|
||||||
return boost::geometry::distance(p, seg);
|
// return boost::geometry::distance(p, seg);
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
|
|
||||||
namespace shapelike {
|
namespace shapelike {
|
||||||
// Tell libnest2d how to make string out of a ClipperPolygon object
|
// Tell libnest2d how to make string out of a ClipperPolygon object
|
||||||
@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef DISABLE_BOOST_BOUNDING_BOX
|
#ifndef DISABLE_BOOST_BOUNDING_BOX
|
||||||
template<>
|
|
||||||
inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&)
|
|
||||||
{
|
|
||||||
bp2d::Box b;
|
|
||||||
boost::geometry::envelope(sh, b);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&)
|
inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&)
|
||||||
{
|
{
|
||||||
bp2d::Box b;
|
bp2d::Box b;
|
||||||
boost::geometry::envelope(sh, b);
|
boost::geometry::envelope(sh, b);
|
||||||
@ -410,9 +403,9 @@ inline bp2d::Box boundingBox<bp2d::Shapes>(const bp2d::Shapes& shapes,
|
|||||||
|
|
||||||
#ifndef DISABLE_BOOST_CONVEX_HULL
|
#ifndef DISABLE_BOOST_CONVEX_HULL
|
||||||
template<>
|
template<>
|
||||||
inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&)
|
inline PathImpl convexHull(const PathImpl& sh, const PathTag&)
|
||||||
{
|
{
|
||||||
PolygonImpl ret;
|
PathImpl ret;
|
||||||
boost::geometry::convex_hull(sh, ret);
|
boost::geometry::convex_hull(sh, ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
268
src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
Normal file
268
src/libnest2d/include/libnest2d/utils/rotcalipers.hpp
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
#ifndef ROTCALIPERS_HPP
|
||||||
|
#define ROTCALIPERS_HPP
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
#include <functional>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <libnest2d/geometry_traits.hpp>
|
||||||
|
|
||||||
|
namespace libnest2d {
|
||||||
|
|
||||||
|
template<class Pt, class Unit = TCompute<Pt>> class RotatedBox {
|
||||||
|
Pt axis_;
|
||||||
|
Unit bottom_ = Unit(0), right_ = Unit(0);
|
||||||
|
public:
|
||||||
|
|
||||||
|
RotatedBox() = default;
|
||||||
|
RotatedBox(const Pt& axis, Unit b, Unit r):
|
||||||
|
axis_(axis), bottom_(b), right_(r) {}
|
||||||
|
|
||||||
|
inline long double area() const {
|
||||||
|
long double asq = pl::magnsq<Pt, long double>(axis_);
|
||||||
|
return cast<long double>(bottom_) * cast<long double>(right_) / asq;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline long double width() const {
|
||||||
|
return abs(bottom_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline long double height() const {
|
||||||
|
return abs(right_) / std::sqrt(pl::magnsq<Pt, long double>(axis_));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Unit bottom_extent() const { return bottom_; }
|
||||||
|
inline Unit right_extent() const { return right_; }
|
||||||
|
inline const Pt& axis() const { return axis_; }
|
||||||
|
|
||||||
|
inline Radians angleToX() const {
|
||||||
|
double ret = std::atan2(getY(axis_), getX(axis_));
|
||||||
|
auto s = std::signbit(ret);
|
||||||
|
if(s) ret += Pi_2;
|
||||||
|
return -ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Poly, class Pt = TPoint<Poly>, class Unit = TCompute<Pt>>
|
||||||
|
Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0))
|
||||||
|
{
|
||||||
|
Poly ret; sl::reserve(ret, sl::contourVertexCount(sh));
|
||||||
|
|
||||||
|
Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh));
|
||||||
|
|
||||||
|
auto it = sl::cbegin(sh);
|
||||||
|
auto itx = std::next(it);
|
||||||
|
if(itx != sl::cend(sh)) while (it != sl::cend(sh))
|
||||||
|
{
|
||||||
|
Pt enext = *itx - *it;
|
||||||
|
|
||||||
|
auto dp = pl::dotperp<Pt, Unit>(eprev, enext);
|
||||||
|
if(abs(dp) > eps) sl::addVertex(ret, *it);
|
||||||
|
|
||||||
|
eprev = enext;
|
||||||
|
if (++itx == sl::cend(sh)) itx = sl::cbegin(sh);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The area of the bounding rectangle with the axis dir and support vertices
|
||||||
|
template<class Pt, class Unit = TCompute<Pt>, class R = TCompute<Pt>>
|
||||||
|
inline R rectarea(const Pt& w, // the axis
|
||||||
|
const Pt& vb, const Pt& vr,
|
||||||
|
const Pt& vt, const Pt& vl)
|
||||||
|
{
|
||||||
|
Unit a = pl::dot<Pt, Unit>(w, vr - vl);
|
||||||
|
Unit b = pl::dot<Pt, Unit>(-pl::perp(w), vt - vb);
|
||||||
|
R m = R(a) / pl::magnsq<Pt, Unit>(w);
|
||||||
|
m = m * b;
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Pt,
|
||||||
|
class Unit = TCompute<Pt>,
|
||||||
|
class R = TCompute<Pt>,
|
||||||
|
class It = typename std::vector<Pt>::const_iterator>
|
||||||
|
inline R rectarea(const Pt& w, const std::array<It, 4>& rect)
|
||||||
|
{
|
||||||
|
return rectarea<Pt, Unit, R>(w, *rect[0], *rect[1], *rect[2], *rect[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is only applicable to counter-clockwise oriented convex
|
||||||
|
// polygons where only two points can be collinear witch each other.
|
||||||
|
template <class RawShape,
|
||||||
|
class Unit = TCompute<RawShape>,
|
||||||
|
class Ratio = TCompute<RawShape>>
|
||||||
|
RotatedBox<TPoint<RawShape>, Unit> minAreaBoundingBox(const RawShape& sh)
|
||||||
|
{
|
||||||
|
using Point = TPoint<RawShape>;
|
||||||
|
using Iterator = typename TContour<RawShape>::const_iterator;
|
||||||
|
using pointlike::dot; using pointlike::magnsq; using pointlike::perp;
|
||||||
|
|
||||||
|
// Get the first and the last vertex iterator
|
||||||
|
auto first = sl::cbegin(sh);
|
||||||
|
auto last = std::prev(sl::cend(sh));
|
||||||
|
|
||||||
|
// Check conditions and return undefined box if input is not sane.
|
||||||
|
if(last == first) return {};
|
||||||
|
if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last;
|
||||||
|
if(last - first < 2) return {};
|
||||||
|
|
||||||
|
RawShape shcpy; // empty at this point
|
||||||
|
{
|
||||||
|
Point p = *first, q = *std::next(first), r = *last;
|
||||||
|
|
||||||
|
// Determine orientation from first 3 vertex (should be consistent)
|
||||||
|
Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) -
|
||||||
|
(Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p));
|
||||||
|
|
||||||
|
if(d > 0) {
|
||||||
|
// The polygon is clockwise. A flip is needed (for now)
|
||||||
|
sl::reserve(shcpy, last - first);
|
||||||
|
auto it = last; while(it != first) sl::addVertex(shcpy, *it--);
|
||||||
|
sl::addVertex(shcpy, *first);
|
||||||
|
first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cyclic iterator increment
|
||||||
|
auto inc = [&first, &last](Iterator& it) {
|
||||||
|
if(it == last) it = first; else ++it;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cyclic previous iterator
|
||||||
|
auto prev = [&first, &last](Iterator it) {
|
||||||
|
return it == first ? last : std::prev(it);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cyclic next iterator
|
||||||
|
auto next = [&first, &last](Iterator it) {
|
||||||
|
return it == last ? first : std::next(it);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Establish initial (axis aligned) rectangle support verices by determining
|
||||||
|
// polygon extremes:
|
||||||
|
|
||||||
|
auto it = first;
|
||||||
|
Iterator minX = it, maxX = it, minY = it, maxY = it;
|
||||||
|
|
||||||
|
do { // Linear walk through the vertices and save the extreme positions
|
||||||
|
|
||||||
|
Point v = *it, d = v - *minX;
|
||||||
|
if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it;
|
||||||
|
|
||||||
|
d = v - *maxX;
|
||||||
|
if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it;
|
||||||
|
|
||||||
|
d = v - *minY;
|
||||||
|
if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it;
|
||||||
|
|
||||||
|
d = v - *maxY;
|
||||||
|
if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it;
|
||||||
|
|
||||||
|
} while(++it != std::next(last));
|
||||||
|
|
||||||
|
// Update the vertices defining the bounding rectangle. The rectangle with
|
||||||
|
// the smallest rotation is selected and the supporting vertices are
|
||||||
|
// returned in the 'rect' argument.
|
||||||
|
auto update = [&next, &inc]
|
||||||
|
(const Point& w, std::array<Iterator, 4>& rect)
|
||||||
|
{
|
||||||
|
Iterator B = rect[0], Bn = next(B);
|
||||||
|
Iterator R = rect[1], Rn = next(R);
|
||||||
|
Iterator T = rect[2], Tn = next(T);
|
||||||
|
Iterator L = rect[3], Ln = next(L);
|
||||||
|
|
||||||
|
Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L;
|
||||||
|
Point pw = perp(w);
|
||||||
|
using Pt = Point;
|
||||||
|
|
||||||
|
Unit dotwpb = dot<Pt, Unit>( w, b), dotwpr = dot<Pt, Unit>(-pw, r);
|
||||||
|
Unit dotwpt = dot<Pt, Unit>(-w, t), dotwpl = dot<Pt, Unit>( pw, l);
|
||||||
|
Unit dw = magnsq<Pt, Unit>(w);
|
||||||
|
|
||||||
|
std::array<Ratio, 4> angles;
|
||||||
|
angles[0] = (Ratio(dotwpb) / magnsq<Pt, Unit>(b)) * dotwpb;
|
||||||
|
angles[1] = (Ratio(dotwpr) / magnsq<Pt, Unit>(r)) * dotwpr;
|
||||||
|
angles[2] = (Ratio(dotwpt) / magnsq<Pt, Unit>(t)) * dotwpt;
|
||||||
|
angles[3] = (Ratio(dotwpl) / magnsq<Pt, Unit>(l)) * dotwpl;
|
||||||
|
|
||||||
|
using AngleIndex = std::pair<Ratio, size_t>;
|
||||||
|
std::vector<AngleIndex> A; A.reserve(4);
|
||||||
|
|
||||||
|
for (size_t i = 3, j = 0; j < 4; i = j++) {
|
||||||
|
if(rect[i] != rect[j] && angles[i] < dw) {
|
||||||
|
auto iv = std::make_pair(angles[i], i);
|
||||||
|
auto it = std::lower_bound(A.begin(), A.end(), iv,
|
||||||
|
[](const AngleIndex& ai,
|
||||||
|
const AngleIndex& aj)
|
||||||
|
{
|
||||||
|
return ai.first > aj.first;
|
||||||
|
});
|
||||||
|
|
||||||
|
A.insert(it, iv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The polygon is supposed to be a rectangle.
|
||||||
|
if(A.empty()) return false;
|
||||||
|
|
||||||
|
auto amin = A.front().first;
|
||||||
|
auto imin = A.front().second;
|
||||||
|
for(auto& a : A) if(a.first == amin) inc(rect[a.second]);
|
||||||
|
|
||||||
|
std::rotate(rect.begin(), rect.begin() + imin, rect.end());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Point w(1, 0);
|
||||||
|
Point w_min = w;
|
||||||
|
Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) *
|
||||||
|
(Unit(getY(*maxY)) - getY(*minY)));
|
||||||
|
|
||||||
|
std::array<Iterator, 4> rect = {minY, maxX, maxY, minX};
|
||||||
|
std::array<Iterator, 4> minrect = rect;
|
||||||
|
|
||||||
|
// An edge might be examined twice in which case the algorithm terminates.
|
||||||
|
size_t c = 0, count = last - first + 1;
|
||||||
|
std::vector<bool> edgemask(count, false);
|
||||||
|
|
||||||
|
while(c++ < count)
|
||||||
|
{
|
||||||
|
// Update the support vertices, if cannot be updated, break the cycle.
|
||||||
|
if(! update(w, rect)) break;
|
||||||
|
|
||||||
|
size_t eidx = size_t(rect[0] - first);
|
||||||
|
|
||||||
|
if(edgemask[eidx]) break;
|
||||||
|
edgemask[eidx] = true;
|
||||||
|
|
||||||
|
// get the unnormalized direction vector
|
||||||
|
w = *rect[0] - *prev(rect[0]);
|
||||||
|
|
||||||
|
// get the area of the rotated rectangle
|
||||||
|
Ratio rarea = rectarea<Point, Unit, Ratio>(w, rect);
|
||||||
|
|
||||||
|
// Update min area and the direction of the min bounding box;
|
||||||
|
if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Unit a = dot<Point, Unit>(w_min, *minrect[1] - *minrect[3]);
|
||||||
|
Unit b = dot<Point, Unit>(-perp(w_min), *minrect[2] - *minrect[0]);
|
||||||
|
RotatedBox<Point, Unit> bb(w_min, a, b);
|
||||||
|
|
||||||
|
return bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawShape> Radians minAreaBoundingBoxRotation(const RawShape& sh)
|
||||||
|
{
|
||||||
|
return minAreaBoundingBox(sh).angleToX();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ROTCALIPERS_HPP
|
23
src/libnest2d/src/libnest2d.cpp
Normal file
23
src/libnest2d/src/libnest2d.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <libnest2d.h>
|
||||||
|
|
||||||
|
namespace libnest2d {
|
||||||
|
|
||||||
|
template class Nester<NfpPlacer, FirstFitSelection>;
|
||||||
|
template class Nester<BottomLeftPlacer, FirstFitSelection>;
|
||||||
|
|
||||||
|
template PackGroup nest(std::vector<Item>::iterator from,
|
||||||
|
std::vector<Item>::iterator to,
|
||||||
|
const Box& bin,
|
||||||
|
Coord dist = 0,
|
||||||
|
const NfpPlacer::Config& pconf,
|
||||||
|
const FirstFitSelection::Config& sconf);
|
||||||
|
|
||||||
|
template PackGroup nest(std::vector<Item>::iterator from,
|
||||||
|
std::vector<Item>::iterator to,
|
||||||
|
const Box& bin,
|
||||||
|
ProgressFunction prg,
|
||||||
|
StopCondition scond,
|
||||||
|
Coord dist = 0,
|
||||||
|
const NfpPlacer::Config& pconf,
|
||||||
|
const FirstFitSelection::Config& sconf);
|
||||||
|
}
|
@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt
|
|||||||
|
|
||||||
target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} )
|
target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} )
|
||||||
|
|
||||||
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE
|
target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS})
|
||||||
${GTEST_INCLUDE_DIRS})
|
|
||||||
|
if(NOT LIBNEST2D_HEADER_ONLY)
|
||||||
|
target_link_libraries(tests_clipper_nlopt ${LIBNAME})
|
||||||
|
else()
|
||||||
|
target_link_libraries(tests_clipper_nlopt libnest2d)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_test(libnest2d_tests tests_clipper_nlopt)
|
add_test(libnest2d_tests tests_clipper_nlopt)
|
||||||
|
@ -3,11 +3,43 @@
|
|||||||
|
|
||||||
#include <libnest2d.h>
|
#include <libnest2d.h>
|
||||||
#include "printer_parts.h"
|
#include "printer_parts.h"
|
||||||
#include <libnest2d/geometry_traits_nfp.hpp>
|
//#include <libnest2d/geometry_traits_nfp.hpp>
|
||||||
#include "../tools/svgtools.hpp"
|
#include "../tools/svgtools.hpp"
|
||||||
|
#include <libnest2d/utils/rotcalipers.hpp>
|
||||||
|
|
||||||
|
#include "boost/multiprecision/integer.hpp"
|
||||||
|
#include "boost/rational.hpp"
|
||||||
|
|
||||||
|
//#include "../tools/Int128.hpp"
|
||||||
|
|
||||||
|
//#include "gte/Mathematics/GteMinimumAreaBox2.h"
|
||||||
|
|
||||||
//#include "../tools/libnfpglue.hpp"
|
//#include "../tools/libnfpglue.hpp"
|
||||||
//#include "../tools/nfp_svgnest_glue.hpp"
|
//#include "../tools/nfp_svgnest_glue.hpp"
|
||||||
|
|
||||||
|
namespace libnest2d {
|
||||||
|
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
|
||||||
|
using LargeInt = __int128;
|
||||||
|
#else
|
||||||
|
using LargeInt = boost::multiprecision::int128_t;
|
||||||
|
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
|
||||||
|
#endif
|
||||||
|
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
|
||||||
|
|
||||||
|
namespace nfp {
|
||||||
|
|
||||||
|
template<class S>
|
||||||
|
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
|
||||||
|
{
|
||||||
|
NfpResult<S> operator()(const S &sh, const S &other)
|
||||||
|
{
|
||||||
|
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<libnest2d::Item>& prusaParts() {
|
std::vector<libnest2d::Item>& prusaParts() {
|
||||||
static std::vector<libnest2d::Item> ret;
|
static std::vector<libnest2d::Item> ret;
|
||||||
|
|
||||||
@ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles)
|
|||||||
ASSERT_DOUBLE_EQ(rad, Pi);
|
ASSERT_DOUBLE_EQ(rad, Pi);
|
||||||
ASSERT_DOUBLE_EQ(deg, 180);
|
ASSERT_DOUBLE_EQ(deg, 180);
|
||||||
ASSERT_DOUBLE_EQ(deg2, 180);
|
ASSERT_DOUBLE_EQ(deg2, 180);
|
||||||
ASSERT_DOUBLE_EQ(rad, (Radians) deg);
|
ASSERT_DOUBLE_EQ(rad, Radians(deg));
|
||||||
ASSERT_DOUBLE_EQ( (Degrees) rad, deg);
|
ASSERT_DOUBLE_EQ( Degrees(rad), deg);
|
||||||
|
|
||||||
ASSERT_TRUE(rad == deg);
|
ASSERT_TRUE(rad == deg);
|
||||||
|
|
||||||
@ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) {
|
|||||||
|
|
||||||
Segment seg(p1, p3);
|
Segment seg(p1, p3);
|
||||||
|
|
||||||
ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
|
// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755);
|
||||||
|
|
||||||
auto result = pointlike::horizontalDistance(p2, seg);
|
auto result = pointlike::horizontalDistance(p2, seg);
|
||||||
|
|
||||||
auto check = [](Coord val, Coord expected) {
|
auto check = [](TCompute<Coord> val, TCompute<Coord> expected) {
|
||||||
if(std::is_floating_point<Coord>::value)
|
if(std::is_floating_point<TCompute<Coord>>::value)
|
||||||
ASSERT_DOUBLE_EQ(static_cast<double>(val),
|
ASSERT_DOUBLE_EQ(static_cast<double>(val),
|
||||||
static_cast<double>(expected));
|
static_cast<double>(expected));
|
||||||
else
|
else
|
||||||
@ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose)
|
|||||||
namespace {
|
namespace {
|
||||||
using namespace libnest2d;
|
using namespace libnest2d;
|
||||||
|
|
||||||
template<unsigned long SCALE = 1, class Bin>
|
template<long long SCALE = 1, class Bin>
|
||||||
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
|
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
|
||||||
|
|
||||||
|
|
||||||
@ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(GeometryAlgorithms, convexHull) {
|
||||||
|
using namespace libnest2d;
|
||||||
|
|
||||||
|
ClipperLib::Path poly = PRINTER_PART_POLYGONS[0];
|
||||||
|
|
||||||
|
auto chull = sl::convexHull(poly);
|
||||||
|
|
||||||
|
ASSERT_EQ(chull.size(), poly.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(GeometryAlgorithms, NestTest) {
|
||||||
|
std::vector<Item> input = prusaParts();
|
||||||
|
|
||||||
|
PackGroup result = libnest2d::nest(input,
|
||||||
|
Box(250000000, 210000000),
|
||||||
|
[](unsigned cnt) {
|
||||||
|
std::cout
|
||||||
|
<< "parts left: " << cnt
|
||||||
|
<< std::endl;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_LE(result.size(), 2);
|
||||||
|
|
||||||
|
int partsum = std::accumulate(result.begin(),
|
||||||
|
result.end(),
|
||||||
|
0,
|
||||||
|
[](int s,
|
||||||
|
const decltype(result)::value_type &bin) {
|
||||||
|
return s += bin.size();
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(input.size(), partsum);
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct ItemPair {
|
struct ItemPair {
|
||||||
@ -713,7 +780,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
|
|||||||
|
|
||||||
auto& exportfun = exportSVG<SCALE, Box>;
|
auto& exportfun = exportSVG<SCALE, Box>;
|
||||||
|
|
||||||
auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){
|
auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
|
||||||
testcase++;
|
testcase++;
|
||||||
|
|
||||||
orbiter.translate({210*SCALE, 0});
|
orbiter.translate({210*SCALE, 0});
|
||||||
@ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
|
|||||||
rect2.translate({10, 0});
|
rect2.translate({10, 0});
|
||||||
rect3.translate({25, 0});
|
rect3.translate({25, 0});
|
||||||
|
|
||||||
shapelike::Shapes<PolygonImpl> pile;
|
TMultiShape<PolygonImpl> pile;
|
||||||
pile.push_back(rect1.transformedShape());
|
pile.push_back(rect1.transformedShape());
|
||||||
pile.push_back(rect2.transformedShape());
|
pile.push_back(rect2.transformedShape());
|
||||||
|
|
||||||
@ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) {
|
|||||||
ASSERT_EQ(shapelike::area(result.front()), ref.area());
|
ASSERT_EQ(shapelike::area(result.front()), ref.area());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
long double refMinAreaBox(const PolygonImpl& p) {
|
||||||
|
|
||||||
|
auto it = sl::cbegin(p), itx = std::next(it);
|
||||||
|
|
||||||
|
long double min_area = std::numeric_limits<long double>::max();
|
||||||
|
|
||||||
|
|
||||||
|
auto update_min = [&min_area, &it, &itx, &p]() {
|
||||||
|
Segment s(*it, *itx);
|
||||||
|
|
||||||
|
PolygonImpl rotated = p;
|
||||||
|
sl::rotate(rotated, -s.angleToXaxis());
|
||||||
|
auto bb = sl::boundingBox(rotated);
|
||||||
|
auto area = cast<long double>(sl::area(bb));
|
||||||
|
if(min_area > area) min_area = area;
|
||||||
|
};
|
||||||
|
|
||||||
|
while(itx != sl::cend(p)) {
|
||||||
|
update_min();
|
||||||
|
++it; ++itx;
|
||||||
|
}
|
||||||
|
|
||||||
|
it = std::prev(sl::cend(p)); itx = sl::cbegin(p);
|
||||||
|
update_min();
|
||||||
|
|
||||||
|
return min_area;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T> struct BoostGCD {
|
||||||
|
T operator()(const T &a, const T &b) { return boost::gcd(a, b); }
|
||||||
|
};
|
||||||
|
|
||||||
|
using Unit = int64_t;
|
||||||
|
using Ratio = boost::rational<boost::multiprecision::int128_t>;// Rational<boost::multiprecision::int256_t>;
|
||||||
|
|
||||||
|
//double gteMinAreaBox(const PolygonImpl& p) {
|
||||||
|
|
||||||
|
// using GteCoord = ClipperLib::cInt;
|
||||||
|
// using GtePoint = gte::Vector2<GteCoord>;
|
||||||
|
|
||||||
|
// gte::MinimumAreaBox2<GteCoord, Ratio> mb;
|
||||||
|
|
||||||
|
// std::vector<GtePoint> points;
|
||||||
|
// points.reserve(p.Contour.size());
|
||||||
|
|
||||||
|
// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)});
|
||||||
|
|
||||||
|
// mb(int(points.size()), points.data(), 0, nullptr, true);
|
||||||
|
|
||||||
|
// auto min_area = double(mb.GetArea());
|
||||||
|
|
||||||
|
// return min_area;
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RotatingCalipers, MinAreaBBCClk) {
|
||||||
|
// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}});
|
||||||
|
|
||||||
|
// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}});
|
||||||
|
|
||||||
|
auto u = [](ClipperLib::cInt n) { return n*1000000; };
|
||||||
|
PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
|
||||||
|
|
||||||
|
|
||||||
|
long double arearef = refMinAreaBox(poly);
|
||||||
|
long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
|
||||||
|
// double gtearea = gteMinAreaBox(poly);
|
||||||
|
|
||||||
|
ASSERT_LE(std::abs(area - arearef), 500e6 );
|
||||||
|
// ASSERT_LE(std::abs(gtearea - arearef), 500 );
|
||||||
|
// ASSERT_DOUBLE_EQ(gtearea, arearef);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RotatingCalipers, AllPrusaMinBB) {
|
||||||
|
size_t idx = 0;
|
||||||
|
long double err_epsilon = 500e6l;
|
||||||
|
|
||||||
|
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
|
||||||
|
// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
|
||||||
|
// rinput.pop_back();
|
||||||
|
// std::reverse(rinput.begin(), rinput.end());
|
||||||
|
|
||||||
|
// PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
|
||||||
|
PolygonImpl poly(rinput);
|
||||||
|
|
||||||
|
long double arearef = refMinAreaBox(poly);
|
||||||
|
auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput);
|
||||||
|
long double area = cast<long double>(bb.area());
|
||||||
|
// double area = gteMinAreaBox(poly);
|
||||||
|
|
||||||
|
bool succ = std::abs(arearef - area) < err_epsilon;
|
||||||
|
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
|
||||||
|
<< arearef << " actual: " << area << std::endl;
|
||||||
|
|
||||||
|
ASSERT_TRUE(succ);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) {
|
||||||
|
rinput.pop_back();
|
||||||
|
std::reverse(rinput.begin(), rinput.end());
|
||||||
|
|
||||||
|
PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
|
||||||
|
|
||||||
|
|
||||||
|
long double arearef = refMinAreaBox(poly);
|
||||||
|
auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly);
|
||||||
|
long double area = cast<long double>(bb.area());
|
||||||
|
// double area = gteMinAreaBox(poly);
|
||||||
|
|
||||||
|
bool succ = std::abs(arearef - area) < err_epsilon;
|
||||||
|
std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
|
||||||
|
<< arearef << " actual: " << area << std::endl;
|
||||||
|
|
||||||
|
ASSERT_TRUE(succ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
|
@ -160,6 +160,8 @@ add_library(libslic3r STATIC
|
|||||||
MTUtils.hpp
|
MTUtils.hpp
|
||||||
Zipper.hpp
|
Zipper.hpp
|
||||||
Zipper.cpp
|
Zipper.cpp
|
||||||
|
MinAreaBoundingBox.hpp
|
||||||
|
MinAreaBoundingBox.cpp
|
||||||
miniz_extension.hpp
|
miniz_extension.hpp
|
||||||
miniz_extension.cpp
|
miniz_extension.cpp
|
||||||
SLA/SLABoilerPlate.hpp
|
SLA/SLABoilerPlate.hpp
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
* *
|
* *
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef SLIC3R_INT128_HPP
|
||||||
|
#define SLIC3R_INT128_HPP
|
||||||
// #define SLIC3R_DEBUG
|
// #define SLIC3R_DEBUG
|
||||||
|
|
||||||
// Make assert active if SLIC3R_DEBUG
|
// Make assert active if SLIC3R_DEBUG
|
||||||
@ -48,6 +50,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
|
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
|
||||||
#define HAS_INTRINSIC_128_TYPE
|
#define HAS_INTRINSIC_128_TYPE
|
||||||
@ -293,3 +297,5 @@ public:
|
|||||||
return sign_determinant_2x2(p1, q1, p2, q2) * invert;
|
return sign_determinant_2x2(p1, q1, p2, q2) * invert;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // SLIC3R_INT128_HPP
|
||||||
|
142
src/libslic3r/MinAreaBoundingBox.cpp
Normal file
142
src/libslic3r/MinAreaBoundingBox.cpp
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#include "MinAreaBoundingBox.hpp"
|
||||||
|
|
||||||
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
#include <boost/rational.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/Int128.hpp>
|
||||||
|
|
||||||
|
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
|
||||||
|
#include <boost/multiprecision/integer.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <libnest2d/geometry_traits.hpp>
|
||||||
|
#include <libnest2d/utils/rotcalipers.hpp>
|
||||||
|
|
||||||
|
namespace libnest2d {
|
||||||
|
|
||||||
|
template<> struct PointType<Slic3r::Points> { using Type = Slic3r::Point; };
|
||||||
|
template<> struct CoordType<Slic3r::Point> { using Type = coord_t; };
|
||||||
|
template<> struct ShapeTag<Slic3r::ExPolygon> { using Type = PolygonTag; };
|
||||||
|
template<> struct ShapeTag<Slic3r::Polygon> { using Type = PolygonTag; };
|
||||||
|
template<> struct ShapeTag<Slic3r::Points> { using Type = PathTag; };
|
||||||
|
template<> struct ShapeTag<Slic3r::Point> { using Type = PointTag; };
|
||||||
|
template<> struct ContourType<Slic3r::ExPolygon> { using Type = Slic3r::Points; };
|
||||||
|
template<> struct ContourType<Slic3r::Polygon> { using Type = Slic3r::Points; };
|
||||||
|
|
||||||
|
namespace pointlike {
|
||||||
|
|
||||||
|
template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); }
|
||||||
|
template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); }
|
||||||
|
template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); }
|
||||||
|
template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); }
|
||||||
|
|
||||||
|
} // pointlike
|
||||||
|
|
||||||
|
namespace shapelike {
|
||||||
|
template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; }
|
||||||
|
template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; }
|
||||||
|
template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; }
|
||||||
|
template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; }
|
||||||
|
|
||||||
|
template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();}
|
||||||
|
template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); }
|
||||||
|
template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();}
|
||||||
|
template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); }
|
||||||
|
|
||||||
|
template<> inline Slic3r::ExPolygon create<Slic3r::ExPolygon>(Slic3r::Points&& contour)
|
||||||
|
{
|
||||||
|
Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour);
|
||||||
|
return expoly;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> inline Slic3r::Polygon create<Slic3r::Polygon>(Slic3r::Points&& contour)
|
||||||
|
{
|
||||||
|
Slic3r::Polygon poly; poly.points.swap(contour);
|
||||||
|
return poly;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // shapelike
|
||||||
|
} // libnest2d
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Used as compute type.
|
||||||
|
using Unit = int64_t;
|
||||||
|
|
||||||
|
#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__)
|
||||||
|
using Rational = boost::rational<boost::multiprecision::int128_t>;
|
||||||
|
#else
|
||||||
|
using Rational = boost::rational<__int128>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc)
|
||||||
|
{
|
||||||
|
const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
|
||||||
|
|
||||||
|
libnest2d::RotatedBox<Point, Unit> box =
|
||||||
|
libnest2d::minAreaBoundingBox<Polygon, Unit, Rational>(chull);
|
||||||
|
|
||||||
|
m_right = box.right_extent();
|
||||||
|
m_bottom = box.bottom_extent();
|
||||||
|
m_axis = box.axis();
|
||||||
|
}
|
||||||
|
|
||||||
|
MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc)
|
||||||
|
{
|
||||||
|
const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p);
|
||||||
|
|
||||||
|
libnest2d::RotatedBox<Point, Unit> box =
|
||||||
|
libnest2d::minAreaBoundingBox<ExPolygon, Unit, Rational>(chull);
|
||||||
|
|
||||||
|
m_right = box.right_extent();
|
||||||
|
m_bottom = box.bottom_extent();
|
||||||
|
m_axis = box.axis();
|
||||||
|
}
|
||||||
|
|
||||||
|
MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc)
|
||||||
|
{
|
||||||
|
const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts);
|
||||||
|
|
||||||
|
libnest2d::RotatedBox<Point, Unit> box =
|
||||||
|
libnest2d::minAreaBoundingBox<Points, Unit, Rational>(chull);
|
||||||
|
|
||||||
|
m_right = box.right_extent();
|
||||||
|
m_bottom = box.bottom_extent();
|
||||||
|
m_axis = box.axis();
|
||||||
|
}
|
||||||
|
|
||||||
|
double MinAreaBoundigBox::angle_to_X() const
|
||||||
|
{
|
||||||
|
double ret = std::atan2(m_axis.y(), m_axis.x());
|
||||||
|
auto s = std::signbit(ret);
|
||||||
|
if(s) ret += 2 * PI;
|
||||||
|
return -ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
long double MinAreaBoundigBox::width() const
|
||||||
|
{
|
||||||
|
return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
|
||||||
|
}
|
||||||
|
|
||||||
|
long double MinAreaBoundigBox::height() const
|
||||||
|
{
|
||||||
|
return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq<Point, long double>(m_axis));
|
||||||
|
}
|
||||||
|
|
||||||
|
long double MinAreaBoundigBox::area() const
|
||||||
|
{
|
||||||
|
long double asq = libnest2d::pl::magnsq<Point, long double>(m_axis);
|
||||||
|
return m_bottom * m_right / asq;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_collinear_points(Polygon &p)
|
||||||
|
{
|
||||||
|
p = libnest2d::removeCollinearPoints<Polygon>(p, Unit(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_collinear_points(ExPolygon &p)
|
||||||
|
{
|
||||||
|
p = libnest2d::removeCollinearPoints<ExPolygon>(p, Unit(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
59
src/libslic3r/MinAreaBoundingBox.hpp
Normal file
59
src/libslic3r/MinAreaBoundingBox.hpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef MINAREABOUNDINGBOX_HPP
|
||||||
|
#define MINAREABOUNDINGBOX_HPP
|
||||||
|
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class Polygon;
|
||||||
|
class ExPolygon;
|
||||||
|
|
||||||
|
void remove_collinear_points(Polygon& p);
|
||||||
|
void remove_collinear_points(ExPolygon& p);
|
||||||
|
|
||||||
|
/// A class that holds a rotated bounding box. If instantiated with a polygon
|
||||||
|
/// type it will hold the minimum area bounding box for the given polygon.
|
||||||
|
/// If the input polygon is convex, the complexity is linear to the number of
|
||||||
|
/// points. Otherwise a convex hull of O(n*log(n)) has to be performed.
|
||||||
|
class MinAreaBoundigBox {
|
||||||
|
Point m_axis;
|
||||||
|
long double m_bottom = 0.0l, m_right = 0.0l;
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Polygons can be convex or simple (convex or concave with possible holes)
|
||||||
|
enum PolygonLevel {
|
||||||
|
pcConvex, pcSimple
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constructors with various types of geometry data used in Slic3r.
|
||||||
|
// If the convexity is known apriory, pcConvex can be used to skip
|
||||||
|
// convex hull calculation. It is very important that the input polygons
|
||||||
|
// do NOT have any collinear points (except for the first and the last
|
||||||
|
// vertex being the same -- meaning a closed polygon for boost)
|
||||||
|
// To make sure this constraint is satisfied, you can call
|
||||||
|
// remove_collinear_points on the input polygon before handing over here)
|
||||||
|
explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple);
|
||||||
|
explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple);
|
||||||
|
explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple);
|
||||||
|
|
||||||
|
// Returns the angle in radians needed for the box to be aligned with the
|
||||||
|
// X axis. Rotate the polygon by this angle and it will be aligned.
|
||||||
|
double angle_to_X() const;
|
||||||
|
|
||||||
|
// The box width
|
||||||
|
long double width() const;
|
||||||
|
|
||||||
|
// The box height
|
||||||
|
long double height() const;
|
||||||
|
|
||||||
|
// The box area
|
||||||
|
long double area() const;
|
||||||
|
|
||||||
|
// The axis of the rotated box. If the angle_to_X is not sufficiently
|
||||||
|
// precise, use this unnormalized direction vector.
|
||||||
|
const Point& axis() const { return m_axis; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MINAREABOUNDINGBOX_HPP
|
@ -9,6 +9,31 @@
|
|||||||
#include <ClipperUtils.hpp>
|
#include <ClipperUtils.hpp>
|
||||||
|
|
||||||
#include <boost/geometry/index/rtree.hpp>
|
#include <boost/geometry/index/rtree.hpp>
|
||||||
|
#include <boost/multiprecision/integer.hpp>
|
||||||
|
#include <boost/rational.hpp>
|
||||||
|
|
||||||
|
namespace libnest2d {
|
||||||
|
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
|
||||||
|
using LargeInt = __int128;
|
||||||
|
#else
|
||||||
|
using LargeInt = boost::multiprecision::int128_t;
|
||||||
|
template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
|
||||||
|
#endif
|
||||||
|
template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
|
||||||
|
|
||||||
|
namespace nfp {
|
||||||
|
|
||||||
|
template<class S>
|
||||||
|
struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
|
||||||
|
{
|
||||||
|
NfpResult<S> operator()(const S &sh, const S &other)
|
||||||
|
{
|
||||||
|
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) {
|
|||||||
// at the same time, it has to provide reasonable results.
|
// at the same time, it has to provide reasonable results.
|
||||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||||
objfunc(const PointImpl& bincenter,
|
objfunc(const PointImpl& bincenter,
|
||||||
const shapelike::Shapes<PolygonImpl>& merged_pile,
|
const TMultiShape<PolygonImpl>& merged_pile,
|
||||||
const Box& pilebb,
|
const Box& pilebb,
|
||||||
const ItemGroup& items,
|
const ItemGroup& items,
|
||||||
const Item &item,
|
const Item &item,
|
||||||
@ -293,7 +318,7 @@ class AutoArranger {};
|
|||||||
// management and spatial index structures for acceleration.
|
// management and spatial index structures for acceleration.
|
||||||
template<class TBin>
|
template<class TBin>
|
||||||
class _ArrBase {
|
class _ArrBase {
|
||||||
protected:
|
public:
|
||||||
|
|
||||||
// Useful type shortcuts...
|
// Useful type shortcuts...
|
||||||
using Placer = TPacker<TBin>;
|
using Placer = TPacker<TBin>;
|
||||||
@ -301,7 +326,9 @@ protected:
|
|||||||
using Packer = Nester<Placer, Selector>;
|
using Packer = Nester<Placer, Selector>;
|
||||||
using PConfig = typename Packer::PlacementConfig;
|
using PConfig = typename Packer::PlacementConfig;
|
||||||
using Distance = TCoord<PointImpl>;
|
using Distance = TCoord<PointImpl>;
|
||||||
using Pile = sl::Shapes<PolygonImpl>;
|
using Pile = TMultiShape<PolygonImpl>;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
Packer m_pck;
|
Packer m_pck;
|
||||||
PConfig m_pconf; // Placement configuration
|
PConfig m_pconf; // Placement configuration
|
||||||
@ -539,7 +566,10 @@ public:
|
|||||||
// 2D shape from top view.
|
// 2D shape from top view.
|
||||||
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||||
|
|
||||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) {
|
ShapeData2D projectModelFromTop(const Slic3r::Model &model,
|
||||||
|
const WipeTowerInfo &wti,
|
||||||
|
double tolerance)
|
||||||
|
{
|
||||||
ShapeData2D ret;
|
ShapeData2D ret;
|
||||||
|
|
||||||
// Count all the items on the bin (all the object's instances)
|
// Count all the items on the bin (all the object's instances)
|
||||||
@ -561,21 +591,32 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
|
|||||||
// Object instances should carry the same scaling and
|
// Object instances should carry the same scaling and
|
||||||
// x, y rotation that is why we use the first instance.
|
// x, y rotation that is why we use the first instance.
|
||||||
{
|
{
|
||||||
ModelInstance *finst = objptr->instances.front();
|
ModelInstance *finst = objptr->instances.front();
|
||||||
Vec3d rotation = finst->get_rotation();
|
Vec3d rotation = finst->get_rotation();
|
||||||
rotation.z() = 0.;
|
rotation.z() = 0.;
|
||||||
Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror());
|
Transform3d trafo_instance = Geometry::assemble_transform(
|
||||||
|
Vec3d::Zero(),
|
||||||
|
rotation,
|
||||||
|
finst->get_scaling_factor(),
|
||||||
|
finst->get_mirror());
|
||||||
Polygon p = objptr->convex_hull_2d(trafo_instance);
|
Polygon p = objptr->convex_hull_2d(trafo_instance);
|
||||||
assert(! p.points.empty());
|
|
||||||
|
|
||||||
// this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209
|
assert(!p.points.empty());
|
||||||
if (p.points.empty())
|
|
||||||
continue;
|
// this may happen for malformed models, see:
|
||||||
|
// https://github.com/prusa3d/PrusaSlicer/issues/2209
|
||||||
|
if (p.points.empty()) continue;
|
||||||
|
|
||||||
|
if(tolerance > EPSILON) {
|
||||||
|
Polygons pp { p };
|
||||||
|
pp = p.simplify(double(scaled(tolerance)));
|
||||||
|
if (!pp.empty()) p = pp.front();
|
||||||
|
}
|
||||||
|
|
||||||
p.reverse();
|
p.reverse();
|
||||||
assert(!p.is_counter_clockwise());
|
assert(!p.is_counter_clockwise());
|
||||||
p.append(p.first_point());
|
|
||||||
clpath = Slic3rMultiPoint_to_ClipperPath(p);
|
clpath = Slic3rMultiPoint_to_ClipperPath(p);
|
||||||
|
auto firstp = clpath.front(); clpath.emplace_back(firstp);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3d rotation0 = objptr->instances.front()->get_rotation();
|
Vec3d rotation0 = objptr->instances.front()->get_rotation();
|
||||||
@ -589,7 +630,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo&
|
|||||||
|
|
||||||
// Invalid geometries would throw exceptions when arranging
|
// Invalid geometries would throw exceptions when arranging
|
||||||
if(item.vertexCount() > 3) {
|
if(item.vertexCount() > 3) {
|
||||||
item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))),
|
item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()));
|
||||||
item.translation({
|
item.translation({
|
||||||
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
|
ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR),
|
||||||
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
|
ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR)
|
||||||
@ -757,7 +798,7 @@ bool arrange(Model &model, // The model with the geometries
|
|||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
|
||||||
// Get the 2D projected shapes with their 3D model instance pointers
|
// Get the 2D projected shapes with their 3D model instance pointers
|
||||||
auto shapemap = arr::projectModelFromTop(model, wti);
|
auto shapemap = arr::projectModelFromTop(model, wti, 0.1);
|
||||||
|
|
||||||
// Copy the references for the shapes only as the arranger expects a
|
// Copy the references for the shapes only as the arranger expects a
|
||||||
// sequence of objects convertible to Item or ClipperPolygon
|
// sequence of objects convertible to Item or ClipperPolygon
|
||||||
@ -782,7 +823,7 @@ bool arrange(Model &model, // The model with the geometries
|
|||||||
static_cast<libnest2d::Coord>(bbb.min(0)),
|
static_cast<libnest2d::Coord>(bbb.min(0)),
|
||||||
static_cast<libnest2d::Coord>(bbb.min(1))
|
static_cast<libnest2d::Coord>(bbb.min(1))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
static_cast<libnest2d::Coord>(bbb.max(0)),
|
static_cast<libnest2d::Coord>(bbb.max(0)),
|
||||||
static_cast<libnest2d::Coord>(bbb.max(1))
|
static_cast<libnest2d::Coord>(bbb.max(1))
|
||||||
});
|
});
|
||||||
@ -858,7 +899,7 @@ void find_new_position(const Model &model,
|
|||||||
WipeTowerInfo& wti)
|
WipeTowerInfo& wti)
|
||||||
{
|
{
|
||||||
// Get the 2D projected shapes with their 3D model instance pointers
|
// Get the 2D projected shapes with their 3D model instance pointers
|
||||||
auto shapemap = arr::projectModelFromTop(model, wti);
|
auto shapemap = arr::projectModelFromTop(model, wti, 0.1);
|
||||||
|
|
||||||
// Copy the references for the shapes only as the arranger expects a
|
// Copy the references for the shapes only as the arranger expects a
|
||||||
// sequence of objects convertible to Item or ClipperPolygon
|
// sequence of objects convertible to Item or ClipperPolygon
|
||||||
|
@ -39,7 +39,12 @@
|
|||||||
#include "libslic3r/SLA/SLARotfinder.hpp"
|
#include "libslic3r/SLA/SLARotfinder.hpp"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
#include "libnest2d/optimizers/nlopt/genetic.hpp"
|
//#include "libslic3r/ClipperUtils.hpp"
|
||||||
|
|
||||||
|
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
|
||||||
|
// #include "libnest2d/backends/clipper/geometries.hpp"
|
||||||
|
// #include "libnest2d/utils/rotcalipers.hpp"
|
||||||
|
#include "libslic3r/MinAreaBoundingBox.hpp"
|
||||||
|
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
@ -2468,8 +2473,9 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
|
|||||||
},
|
},
|
||||||
[this]() { return was_canceled(); });
|
[this]() { return was_canceled(); });
|
||||||
|
|
||||||
const auto *bed_shape_opt = plater().config->opt<ConfigOptionPoints>(
|
const auto *bed_shape_opt =
|
||||||
"bed_shape");
|
plater().config->opt<ConfigOptionPoints>("bed_shape");
|
||||||
|
|
||||||
assert(bed_shape_opt);
|
assert(bed_shape_opt);
|
||||||
|
|
||||||
auto & bedpoints = bed_shape_opt->values;
|
auto & bedpoints = bed_shape_opt->values;
|
||||||
@ -2478,70 +2484,40 @@ void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
|
|||||||
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||||
|
|
||||||
double mindist = 6.0; // FIXME
|
double mindist = 6.0; // FIXME
|
||||||
double offs = mindist / 2.0 - EPSILON;
|
|
||||||
|
|
||||||
if (!was_canceled()) // wasn't canceled
|
if (!was_canceled()) {
|
||||||
for (ModelInstance *oi : o->instances) {
|
for(ModelInstance * oi : o->instances) {
|
||||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||||
|
|
||||||
auto trchull = o->convex_hull_2d(
|
auto trmatrix = oi->get_transformation().get_matrix();
|
||||||
oi->get_transformation().get_matrix());
|
Polygon trchull = o->convex_hull_2d(trmatrix);
|
||||||
|
|
||||||
namespace opt = libnest2d::opt;
|
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
|
||||||
opt::StopCriteria stopcr;
|
double r = rotbb.angle_to_X();
|
||||||
stopcr.relative_score_difference = 0.01;
|
|
||||||
stopcr.max_iterations = 10000;
|
|
||||||
stopcr.stop_score = 0.0;
|
|
||||||
opt::GeneticOptimizer solver(stopcr);
|
|
||||||
Polygon pbed(bed);
|
|
||||||
|
|
||||||
auto bin = pbed.bounding_box();
|
// The box should be landscape
|
||||||
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
if(rotbb.width() < rotbb.height()) r += PI / 2;
|
||||||
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
|
||||||
|
|
||||||
auto result = solver.optimize_min(
|
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
||||||
[&trchull, binw, binh](double rot) {
|
|
||||||
auto chull = trchull;
|
|
||||||
chull.rotate(rot);
|
|
||||||
|
|
||||||
auto bb = chull.bounding_box();
|
|
||||||
double bbw = bb.size()(X) * SCALING_FACTOR;
|
|
||||||
double bbh = bb.size()(Y) * SCALING_FACTOR;
|
|
||||||
|
|
||||||
auto wdiff = bbw - binw;
|
|
||||||
auto hdiff = bbh - binh;
|
|
||||||
double diff = 0;
|
|
||||||
if (wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
|
|
||||||
if (wdiff > 0) diff += wdiff;
|
|
||||||
if (hdiff > 0) diff += hdiff;
|
|
||||||
|
|
||||||
return diff;
|
|
||||||
},
|
|
||||||
opt::initvals(0.0),
|
|
||||||
opt::bound(-PI / 2, PI / 2));
|
|
||||||
|
|
||||||
double r = std::get<0>(result.optimum);
|
|
||||||
|
|
||||||
Vec3d rt = oi->get_rotation();
|
|
||||||
rt(Z) += r;
|
|
||||||
oi->set_rotation(rt);
|
oi->set_rotation(rt);
|
||||||
|
|
||||||
arr::WipeTowerInfo wti; // useless in SLA context
|
|
||||||
arr::find_new_position(plater().model,
|
|
||||||
o->instances,
|
|
||||||
coord_t(mindist / SCALING_FACTOR),
|
|
||||||
bed,
|
|
||||||
wti);
|
|
||||||
|
|
||||||
// Correct the z offset of the object which was corrupted be
|
|
||||||
// the rotation
|
|
||||||
o->ensure_on_bed();
|
|
||||||
|
|
||||||
update_status(100, _(L("Orientation found.")));
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
update_status(100, _(L("Orientation search canceled.")));
|
arr::WipeTowerInfo wti; // useless in SLA context
|
||||||
|
arr::find_new_position(plater().model,
|
||||||
|
o->instances,
|
||||||
|
coord_t(mindist / SCALING_FACTOR),
|
||||||
|
bed,
|
||||||
|
wti);
|
||||||
|
|
||||||
|
// Correct the z offset of the object which was corrupted be
|
||||||
|
// the rotation
|
||||||
|
o->ensure_on_bed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_status(100,
|
||||||
|
was_canceled() ? _(L("Orientation search canceled."))
|
||||||
|
: _(L("Orientation found.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Plater::priv::split_object()
|
void Plater::priv::split_object()
|
||||||
|
Loading…
Reference in New Issue
Block a user