☰
【计算几何】二维排样库libnest2d
2026/10/6 2:43:44 网站建设 项目流程

本文涉及知识点

数学 几何

翻译

CLOCKWISE 顺时针
COUNTER_CLOCKWISE 逆时针
Boilerplate 样板、模板、脚手架、固定套话。后来引申为“重复使用的标准模板代码”。
rawShape 直译:原始形状 / 裸形状。
Raw 原始的、未加工的、裸的
Shape 形状、几何体

预习及复习

C++98:1998 年
C++11:2011 年(曾叫 C++0x)
C++17:2017 年
C++20:2020 年

usingstd::forward;usingstd::tuple;usingstd::make_tuple;

可以只简写一个类、函数,而不是整个命名空间。

remove_reference

typedefstd::remove_reference<T>::type T1;typedefstd::remove_cv<T1>::type T2;constchar*p1=typeid(T1).name();constchar*p2=typeid(T2).name();

VS2019 C++14 运行结果:
int
int
利用偏特性实现的,很简单。
消除引用

template<class_Ty>structremove_reference{usingtype=_Ty;using_Const_thru_ref_type=const_Ty;};template<class_Ty>structremove_reference<_Ty&>{usingtype=_Ty;using_Const_thru_ref_type=const_Ty&;};template<class_Ty>structremove_reference<_Ty&&>{usingtype=_Ty;using_Const_thru_ref_type=const_Ty&&;};

消除常量和跨线程变量

structremove_cv<const_Ty>{usingtype=_Ty;template<template<class>class_Fn>using_Apply=const_Fn<_Ty>;};template<class_Ty>structremove_cv<volatile_Ty>{usingtype=_Ty;template<template<class>class_Fn>using_Apply=volatile_Fn<_Ty>;};template<class_Ty>structremove_cv<constvolatile_Ty>{usingtype=_Ty;template<template<class>class_Fn>using_Apply=constvolatile_Fn<_Ty>;};

NAN和INF

doublea=NAN;doubleb=std::nan("123");doublec=std::strtod("NAN(123)",nullptr);doubled=std::numeric_limits<double>::quiet_NaN();doublee=std::numeric_limits<double>::signaling_NaN();doublef=1E300*1E300;

前5个是非数字(不确定数字),第6个是无穷大。

doublea=1/F(0);doubleb=0/F(0);doublec=std::sqrt(-1);doubled=std::numeric_limits<double>::infinity()-std::numeric_limits<double>::infinity();doublee=std::numeric_limits<double>::infinity()/std::numeric_limits<double>::infinity();doublef=std::sqrt(std::numeric_limits<double>::infinity());

a和f是INF,bcde是NAN。

libnest2d

克隆代码,发现有大量有文件,只有一个源文件,且此源文件只有二十余行。猜测是仅仅头文件(HEADER_ONLY)。头文件中有_MSC_VER <= 1800,说明支持VS。
LIBNEST2D_GEOMETRIES_clipper 宏决定是否使用Clipper裁剪库,此宏必须存在,否则一些基础类没有定义。
LIBNEST2D_OPTIMIZER_nlopt 宏决定是否使用非线性优化库nlopt 作为优化器后端。必定定义,否则只能使用基础功能。平时都是用VS,不熟悉makelist,所以几小时没编译通过,似乎nplot版本不对。最终群友给我一个VS2019编译好的lib,dll,include。

带孔多边形

攻略上说:目前只支持凸多边形,不支持凹多边形,也不支持带孔多边形。

inlinePolygon(constPath&cont,constPaths&holes):Contour(cont),Holes(holes){}

可以构造带孔多边形,但默认参数似乎没正确除了孔洞。可能保留接口,为自定义放置器准备。

并发库

TBB 与 OpenMP 对比两者都是 C++ 中常用的并行编程技术。TBB(Intel Threading Building Blocks):是一个第三方 C++ 库,需要单独安装、包含头文件并链接库。它提供高级并行算法和数据结构(如 parallel_for、parallel_reduce、并发容器等)。OpenMP:是一个并行编程 API 规范,它由编译器实现支持(如 GCC 的 -fopenmp、Clang 的 -fopenmp、MSVC 的 /openmp)。

#ifdefLIBNEST2D_THREADING_tbb#include<tbb/parallel_for.h>#endif#ifdefLIBNEST2D_THREADING_omp#include<omp.h>#endif

下面还有LIBNEST2D_THREADING_std,由于是刚开始熟悉代码,故暂时用std。这三个宏是三选一,不能多选,也不能少选。enumerate 直译是枚举,实际是每个函数启动一个线程。核心代码很容易理解,以stl为例,启动线程并等待。

std::vector<std::future<void>>rets(N);autoit=from;for(TN b=0;b<N;b++){rets[b]=std::async(policy,fn,*it++,unsigned(b));}for(TN fi=0;fi<N;++fi)rets[fi].wait();

优化

template<classT,classB=void>structlimits{inlinestaticTmin(){returnstd::numeric_limits<T>::min();}inlinestaticTmax(){returnstd::numeric_limits<T>::max();}};template<classT>structlimits<T,enable_if_t<std::numeric_limits<T>::has_infinity,void>>{inlinestaticTmin(){return-std::numeric_limits<T>::infinity();}inlinestaticTmax(){returnstd::numeric_limits<T>::infinity();}};

如果本数据类型支持无穷大,则最大值(最小值)是正(负)无穷大。否则调用 std::min和std::max

一些简单类、结构体

下面的类是一个简单的类,没有放到源文件。

classDouble{protected:doubleval_;public:Double():val_(double{}){}Double(doubled):val_(d){}operatordouble()constBP2D_NOEXCEPT{returnval_;}operatordouble&()BP2D_NOEXCEPT{returnval_;}};

Radians 记录弧度。
Degrees记录度数。

template struct ContourType { using Type = S; }
template<> struct ContourType { using Type = PathImpl; };
template struct ContourType<DefaultMultiShape> {
using Type = typename ContourType::Type;
};

需要排样的物品项Item

using Item = _Item;
using PolygonImpl = ClipperLib::Polygon;

items.emplace_back(Rectangle(mm(30),mm(30)));ClipperLib::Path path={{0,0},{1000,0},{50,80}};items.emplace_back(Item(path));

上述方法增加矩形和凸多边形。

主函数(排样)

template<classPlacer=NfpPlacer,classSelector=FirstFitSelection,classContainer=std::vector<Item>>std::size_tnest(Container&&cont,consttypenamePlacer::BinType&bin,Coord dist=0,constNestConfig<Placer,Selector>&cfg={},NestControl ctl={})
externtemplatestd::size_tnest(std::vector<Item>::iterator from,std::vector<Item>::iterator to,constBox&bin,Coord dist,constNestConfig<BottomLeftPlacer,FirstFitSelection>&cfg,NestControl ctl);

Placer ,放置器类型,决定当前物品放在那。
Selector,选择器,从未完成物品中选择当前物品。
Container ,放置容器。
Coord dist = 0 物品见最小距离。 物品外扩⌈ d i s t ÷ 2 ⌉ \lceil dist \div 2 \rceil⌈dist÷2⌉
NestConfig<Placer, Selector> 排样参数。
NestControl 排样过程的控制参数。

放置器

BottomLeftPlacer (左下角放置器,实现简单)
NfpPlacer (NFP 放置器,功能强)

选择器

_FirstFitSelection:首次适应选择。按照物品在列表中的原始顺序依次尝试放置。它会遍历物品列表,对每个物品调用放置器,找到第一个能成功放入箱子(或当前箱子)的位置就放置,然后继续下一个。
_FillerSelection:填充选择。通常会维护一个待选物品列表,在每个放置步骤中,评估所有剩余物品,选择那个“最能填充当前可用空间”的物品进行放置。这种策略有助于减少碎片空间。
_DJDHeuristic:DJD 启发式选择。这是一种更复杂的启发式选择算法,旨在通过智能地选择物品放置顺序,来获得更优的排样结果。

StopCriteria

停止标准。

structStopCriteria{/// If the absolute value difference between two scores.doubleabsolute_score_difference=std::nan("");/// If the relative value difference between two scores.doublerelative_score_difference=std::nan("");/// Stop if this value or better is found.doublestop_score=std::nan("");/// A predicate that if evaluates to true, the optimization should terminate/// and the best result found prior to termination should be returned.std::function<bool()>stop_condition=[]{returnfalse;};/// The max allowed number of iterations.unsignedmax_iterations=0;};

如果 |score_new - score_old| < absolute_score_difference,则停止,认为分数不再变化。
如果 |score_new - score_old| / max(|score_old|, |score_new|) < relative_score_difference,停止;
如果找到的得分 <= stop_score(或更优),停止。
stop_condition,通过函数自定义停止条件。
max_iterations ,最大迭代次数。

扩展阅读

计算几何为骨,排样优化为魂
作品:亲士CAD工具箱
经典文章推荐:二维排样
万物皆数学
查阅鄙人的博文,请点击博文下载学院导航
活到老,学到老。明朝中后期,大约50%的进士能当上堂官(副部及更高);能当上堂官的举人只有十余人。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。

测试环境

操作系统:win7 开发环境: VS2019C++17
或者 操作系统:win10 开发环境: VS2022C++17
如无特殊说明,本算法用**C++**实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询