【计算几何】libnest2d之选择器

翻译及基础

Placement 放置

Strategy 策略

Like 类似、像......一样的

PlacementStrategyLike 意译放置策略萃取器 或 放置策略适配器。

touches 通常表示: 两个形状的内部不相交; 但它们的边界相交(共享点、边或顶点)。

wrapper:包装器。

enable_if_t

cpp 复制代码
template<class T>
std::enable_if_t<std::is_integral_v<T>, T>
foo(T x) { 
	std::cout << typeid(T).name() << "\n";
	return x; }
cpp 复制代码
	foo(42);     // OK,T=int,条件为真
	foo((short)2);   

结果为:

int

short

cpp 复制代码
foo(2.4);

编译错误。

cpp 复制代码
using enable_if_t = typename std::enable_if<false, int>::type;

这段代码会编译错误,在重载模板时,不会报错,只会忽略类或函数。

如果编译不报错,则std::enable_if_t<std::is_integral_v
, T>是T,即返回值是T。
std::enable_if_t<std::is_integral_v, T>也合法,

以下代码合法

cpp 复制代码
void fun(int = 0) {

}

_Item

RawShape sh_; 原始图形

Vertex translation_{0, 0}; 平移的矢量。

Radians rotation_{0.0};旋转的弧度。

Coord inflation_{0}; 是扩张/膨胀(向外偏移)

bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; 是否旋转、平移、膨胀,由于误差的原因,旋转0度和不旋转不等效。

mutable VertexConstIterator rmt_; // y 最大的顶点(若 y 相同取 x 最大);由于是缓存,故const函数可以修改。

mutable VertexConstIterator lmb_; //y 最小的顶点(若 y 相同取 x 最小)。

cpp 复制代码
 static inline bool vsort(const Vertex& v1, const Vertex& v2)
    {
        TCompute<Vertex> x1 = getX(v1), x2 = getX(v2);
        TCompute<Vertex> y1 = getY(v1), y2 = getY(v2);
        return y1 == y2 ? x1 < x2 : y1 < y2;
    }

fixed_ 决定物品是直接放入指定箱子,还是加入待排列表;

binid_ 决定固定物品放入哪个箱子;

disallowed_ 在放置器内部使用,作为障碍区域。至少填充选择器对物品和障碍物的逻辑不同。

priority_ :优先级。优先级高的先选择。

2个物品占用三个箱子

cpp 复制代码
   std::vector<Item> items;
	Item item1(Rectangle(10, 10));
	item1.markAsFixedInBin(1);
	Item item2(Rectangle(10, 10));
	item2.markAsFixedInBin(2);
	items.emplace_back(item1);
	items.emplace_back(item2);

    const Box bin(mm(100), mm(100));
	

    const std::size_t bins = nest(items, bin, mm(0));

物品一占用第一个箱子,物品二占用第二个箱子,加上空置的0号箱子,共3个。

基类SelectionBoilerplate

ItemGroup,向量记录物品引用。

PackGroup :两层向量记录物品。

packed_bins_ 记录各箱子物品。

remove_unpackable_items函数:如果一个物品无法装箱,则删除。

_FirstFitSelection

Container store_; 需要排样的物体。

cpp 复制代码
        
        std::for_each(first, last, [this](Item& itm) {
            if(itm.isFixed()) {
                if (itm.binId() < 0) itm.binId(0);
                auto binidx = size_t(itm.binId());
                
                while(packed_bins_.size() <= binidx)
                    packed_bins_.emplace_back();
                
                packed_bins_[binidx].emplace_back(itm);
            } else {
                store_.emplace_back(itm);
            }
        });

遍历各物品,如果是非固定物品放到store_。如果需要固定的箱子号为负,强制转为0。如果箱子不够,增加箱子。将物品放到指定的箱子中。

cpp 复制代码
       while(!was_packed && !cancelled()) {
                for(; j < placers.size() && !was_packed && !cancelled(); j++) {
                    if((was_packed = placers[j].pack(*it, rem(it, store_) ))) {
                        it->get().binId(int(j));
                        makeProgress(placers[j], j);
                    }
                }

尝试已有箱子,能否放得下当前物品。

cpp 复制代码
    if(!was_packed) {
                    placers.emplace_back(bin);
                    placers.back().configure(pconfig);
                    packed_bins_.emplace_back();
                    j = placers.size() - 1;
                }

当前物品占用一个完整的箱子。停止条件达到时,快速解决。

_FillerSelection

按面积从大到小处理。忽略物品的优先级。

_DJDHeuristic

Heuristic,启发性算法、方法。

配置

struct Config {

bool try_reverse_order = true;//尝试所有的顺序

bool try_pairs = true;//尝试2个一组

bool try_triplets = false;//尝试3个一组

double initial_fill_proportion = 1.0/3.0;//先用这个比例的面积填满箱子;然后才开始尝试逐个、成对、三元组放置。

double waste_increment = 0.1;DJD 启发式允许一定的"浪费"(未填充面积);每次迭代放宽这个阈值,允许更多浪费;直到能放下更多物品或达到上限。

bool allow_parallel = true;//容许并行

bool force_parallel = false;//强制并行

};

成员

static const unsigned MAX_ITEMS_SEQUENTIALLY = 30;

static const unsigned MAX_VERTICES_SEQUENTIALLY = MAX_ITEMS_SEQUENTIALLY*20;

当物品数和顶点数都不超过上述值时,采用串行,而不时并行。以避免新线程的开销。

cpp 复制代码
        bool do_parallel = config_.allow_parallel && bincount_guess > 1 &&
                ((glob_vertex_count >  MAX_VERTICES_SEQUENTIALLY ||
                 store_.size() > MAX_ITEMS_SEQUENTIALLY) ||
                config_.force_parallel);

装箱

忽略物品的优先级。

cpp 复制代码
        auto check_pair = [](const std::vector<TPair>& wrong_pairs,
                ItemRef i1, ItemRef i2)
        {
            return std::any_of(wrong_pairs.begin(), wrong_pairs.end(),
                               [&i1, &i2](const TPair& pair)
            {
                Item& pi1 = std::get<0>(pair), &pi2 = std::get<1>(pair);
                Item& ri1 = i1, &ri2 = i2;
                return (&pi1 == &ri1 && &pi2 == &ri2) ||
                       (&pi1 == &ri2 && &pi2 == &ri1);
            });
        };

判断错误表,是否包括{i1,i2}。

cpp 复制代码
     // Method for checking if a triplet was a pack failure
        auto check_triplet = [](
                const std::vector<TTriplet>& wrong_triplets,
                ItemRef i1,
                ItemRef i2,
                ItemRef i3)
        {
            return std::any_of(wrong_triplets.begin(),
                               wrong_triplets.end(),
                               [&i1, &i2, &i3](const TTriplet& tripl)
            {
                Item& pi1 = std::get<0>(tripl);
                Item& pi2 = std::get<1>(tripl);
                Item& pi3 = std::get<2>(tripl);
                Item& ri1 = i1, &ri2 = i2, &ri3 = i3;
                return  (&pi1 == &ri1 && &pi2 == &ri2 && &pi3 == &ri3) ||
                        (&pi1 == &ri1 && &pi2 == &ri3 && &pi3 == &ri2) ||
                        (&pi1 == &ri2 && &pi2 == &ri1 && &pi3 == &ri3) ||
                        (&pi1 == &ri3 && &pi2 == &ri2 && &pi3 == &ri1);
            });
        };

判断{i1,i2,i3}是否在错误三元组中。3个物品排列有6种排列,代码中只列出了4种。可能是别的地方做了处理,也可能是缺陷。就算是缺陷,也不影响正确性,只会造成性能损失。

cpp 复制代码
    auto largestPiece = [](ItemListIt it, ItemList& not_packed) {
            return it == not_packed.begin()? std::next(it) : not_packed.begin();
        };

        auto secondLargestPiece = [&largestPiece](ItemListIt it,
                ItemList& not_packed) {
            auto ret = std::next(largestPiece(it, not_packed));
            return ret == it? std::next(ret) : ret;
        };

        auto smallestPiece = [](ItemListIt it, ItemList& not_packed) {
            auto last = std::prev(not_packed.end());
            return it == last? std::prev(it) : last;
        };

        auto secondSmallestPiece = [&smallestPiece](ItemListIt it,
                ItemList& not_packed) {
            auto ret = std::prev(smallestPiece(it, not_packed));
            return ret == it? std::prev(ret) : ret;
        };

假定删除it后,最大、次大、最小、次小物品。

tryOneByOne

依次尝试待放物品,如果将当前物品装箱,剩余面积小于等于可浪费面积,则装箱。

tryGroupsOfTwo

两两尝试待放置物品,如果放置这两个物品后,剩余面积小于等于可浪费面积,则装箱。

tryGroupsOfThree

三个一组进行尝试。任意组合都可能尝试。故此函数的时间复杂度是O(n\^3)。

packjob

执行多轮,每轮waste都增加。每轮都依次执行:一,初始填充。二,tryOneByOne。三,tryGroupsOfTwo。四,tryGroupsOfThree。

初始填充阶段:在开始成对/三元组尝试之前,先把箱子填充到大约 INITIAL_FILL_AREA(默认箱子面积的 1/3)。