Python的ezdxf包操作dxf文件-矩形排版之最低水平线法

dxf格式为R12格式

Python的ezdxf包操作dxf文件-矩形排版之最低水平线法

pip install ezdxf==1.0.2 -i https://pypi.mirrors.ustc.edu.cn/simple/

下面是文件代码

sf001.py

# 水平线类(起始x位置,终止x位置,高度)
class OutLine:
    def __init__(self, origin, end, height):
        self.origin = origin
        self.end = end
        self.height = height

    def __str__(self):
        return "OutLine:origin:{}, end:{}, height:{}".format(self.origin, self.end, self.height)


# 矩形物品类(宽度,高度,编号)
class Product:
    def __init__(self, w, h, num=0):
        self.w = w
        self.h = h
        self.num = num

    def __str__(self):
        return "product:w:{}, h:{}, num:{}".format(self.w, self.h, self.num)

    # 根据长度搜索矩形件(找出宽度小于目标长度的首个矩形件)
    @staticmethod
    def search_by_width(target_width, data):
        for index, p in enumerate(data):
            if p.w <= target_width:
                return index
        return None

    # 根据长度搜索矩形件(找出宽度或高度小于目标长度的首个矩形件)
    @staticmethod
    def search_by_size(target_width, data):
        for index, p in enumerate(data):
            if p.w <= target_width or p.h <= target_width:
                return index
        return None

    # 旋转90度并返回新对象
    def rotate_new(self):
        return Product(self.h, self.w, self.num)


# 布局类
class RectLayout:
    def __init__(self, width, line_list=[]):
        self.width = width
        # 水平线集合(起始x位置,终止x位置,高度)
        self.line_list = line_list
        # 水平线(起始x位置,终止x位置,高度)
        self.lowest_line = None
        # 水平线索引(起始x位置,终止x位置,高度)
        self.lowest_line_idx = 0
        # 最终位置结果[[矩形件编号,左下角横坐标,左下角纵坐标,矩形件宽度,矩形件高度], ...]
        self.result_pos = []
        # 板材利用率
        self.ratio = 0.0

    # 初始化水平线集合(起始x位置,终止x位置,高度)
    def init_line_list(self, origin, end, height):
        init_line = OutLine(origin, end, height)
        self.line_list = [init_line]
        self.lowest_line = init_line
        self.lowest_line_idx = 0

    # 提升最低水平线
    def enhance_line(self, index):
        if len(self.line_list) > 1:
            # 获取高度较低的相邻水平线索引,并更新水平线集
            neighbor_idx = 0
            if index == 0:
                neighbor_idx = 1
            elif index + 1 == len(self.line_list):
                neighbor_idx = index - 1
            else:
                # 左边相邻水平线
                left_neighbor = self.line_list[index - 1]
                # 右边相邻水平线
                right_neighbor = self.line_list[index + 1]
                # 选择高度较低的相邻水平线,左右相邻水平线高度相同时,选择左边相邻的水平线
                if left_neighbor.height < right_neighbor.height:
                    neighbor_idx = index - 1
                elif left_neighbor.height == right_neighbor.height:
                    if left_neighbor.origin < right_neighbor.origin:
                        neighbor_idx = index - 1
                    else:
                        neighbor_idx = index + 1
                else:
                    neighbor_idx = index + 1
            # 选中的高度较低的相邻水平线
            old = self.line_list[neighbor_idx]
            # 更新相邻水平线
            if neighbor_idx < index:
                self.line_list[neighbor_idx] = OutLine(old.origin, old.end + self.line_width(index), old.height)
            else:
                self.line_list[neighbor_idx] = OutLine(old.origin - self.line_width(index), old.end, old.height)
            # 删除当前水平线
            del self.line_list[index]

    # 按位置更新水平线
    def update_line_list(self, index, new_line):
        self.line_list[index] = new_line

    # 按位置插入水平线(插在某索引位置后面)
    def insert_line_list(self, index, new_line):
        new_lists = []
        if len(self.line_list) == index + 1:
            new_lists = self.line_list + [new_line]
        else:
            new_lists = self.line_list[:index + 1] + [new_line] + self.line_list[index + 1:]
        self.line_list = new_lists

    # 计算水平线宽度
    def line_width(self, index):
        line = self.line_list[index]
        return line.end - line.origin

    # 找出最低水平线(如果最低水平线不止一条则选取最左边的那条)
    def find_lowest_line(self):
        # 最低高度
        lowest = min([_l.height for _l in self.line_list])
        # 最低高度时,最小开始横坐标
        origin = min([_l.origin for _l in self.line_list if _l.height == lowest])
        for _idx, _line in enumerate(self.line_list):
            if _line.height == lowest and _line.origin == origin:
                self.lowest_line_idx = _idx
                self.lowest_line = _line

    # 清空水平线集合
    def empty_line_list(self):
        self.line_list.clear()

    # 计算最高水平线高度,即所用板材最大高度
    def cal_high_line(self):
        max_height = max([ll.height for ll in self.line_list])
        return max_height

    # 将矩形物品排样
    def packing(self, _pro: Product):
        # 最低水平线宽度
        lowest_line_width = self.line_width(self.lowest_line_idx)
        # 对矩形件排样
        self.result_pos.append([_pro.num, self.lowest_line.origin, self.lowest_line.height, _pro.w, _pro.h])
        # 更新水平线集
        new_line1 = OutLine(self.lowest_line.origin, self.lowest_line.origin + _pro.w, self.lowest_line.height + _pro.h)
        new_line2 = OutLine(self.lowest_line.origin + _pro.w, self.lowest_line.origin + lowest_line_width, self.lowest_line.height)
        self.update_line_list(self.lowest_line_idx, new_line1)
        if lowest_line_width - _pro.w > 0:
            self.insert_line_list(self.lowest_line_idx, new_line2)

    # 计算板材利用率
    def cal_used_ratio(self):
        # 计算板材利用率
        used_area = 0
        for _p in self.result_pos:
            used_area += _p[3] * _p[4]
        # 板材使用最大高度
        max_high = self.cal_high_line()
        # 利用率
        if max_high > 0:
            self.ratio = round((used_area * 100) / (self.width * max_high), 3)
        return used_area, self.ratio


# 主方法
def compute(container_width,_item_sizes,ran):
    # 矩形物品列表
    products = []
    #for idx in range(item_num):
    for idx in range(len(ran)):
        products.append(Product(_item_sizes[idx][0], _item_sizes[idx][1], ran[idx]))
    products = sorted(products, key=lambda x: x.w * x.h, reverse=True)
    # 初始化布局类
    layout = RectLayout(width=container_width)
    # 初始化水平线集
    layout.init_line_list(0, container_width, 0)
    while products:
        # 最低水平线及其索引
        layout.find_lowest_line()
        # 可用长度
        available_width = layout.line_width(layout.lowest_line_idx)
        # 候选物品索引
        # candidate_idx = Product.search_by_width(available_width, products)
        candidate_idx = Product.search_by_size(available_width, products)
        if candidate_idx is not None:
            # 候选物品
            pro = products[candidate_idx]
            # 宽度放不下时,对矩形件进行旋转
            if pro.w > available_width >= pro.h:
                pro = pro.rotate_new()
            # 将候选物品排样
            layout.packing(pro)
            # 剔除已经排样的物品
            products.pop(candidate_idx)
        else:
            # 最低水平线宽度小于要排样矩形宽度,提升最低水平线
            layout.enhance_line(layout.lowest_line_idx)

    # 计算板材利用率
    _area, _ratio = layout.cal_used_ratio()
    #print("used_area: {}".format(_area))
    #print("ratio: {}%".format(_ratio))
    # 计算最高水平线高度,即所用板材最大高度
    container_height = layout.cal_high_line()
    return layout.result_pos,container_height

 test001.py

import ezdxf
import math
import os
import sf001

def get_xy(e,a):
    x= e.dxf.center[0]+e.dxf.radius*math.cos(math.radians(a));
    y= e.dxf.center[1]+e.dxf.radius*math.sin(math.radians(a));
    return x,y
def get_min_w_h(file_name):
    doc = ezdxf.readfile(file_name,encoding="gbk")
    msp = doc.modelspace()
    points=[]
    for e in msp.query("POLYLINE"):
        for i in e.vertices:
            points.append(i.dxf.location)
    for e in msp.query("CIRCLE"):
        points.append(get_xy(e, 90))
        points.append(get_xy(e, 180))
        points.append(get_xy(e, 270))
        points.append(get_xy(e, 360))
    for e in msp.query("LINE"):
        points.append(e.dxf.start)
        points.append(e.dxf.end)
    for e in msp.query("ARC"):
        points.append(e.start_point)
        points.append(e.end_point)
        start_a=e.dxf.start_angle
        end_a = e.dxf.end_angle
        a=end_a+(360 if start_a>end_a else 0)-start_a
        mid_a=a/2+start_a
        points.append(get_xy(e, mid_a))
    xs=[]
    ys=[]
    for i in points:
        xs.append(i[0])
        ys.append(i[1])
    xs.sort()
    ys.sort()
    xy_min=xs[0],ys[0]
    w=xs[-1]-xs[0]
    h=ys[-1]-ys[0]
    return xy_min,w,h
        
def get_doc(file_name,doc1):
    xy_min,w,h=get_min_w_h(file_name)
    str1=file_name.rsplit("-",2)
    flag_name=str1[1]
    item_num=str1[-1][0:-4]
    #print(flag_name)
    #print(item_num)
    doc = ezdxf.readfile(file_name,encoding="gbk")
    msp = doc.modelspace()
    
    flag = doc1.blocks.new(name=flag_name)
    
    for e in msp.query("POLYLINE"):
        points=[]
        #print(e.dxf.all_existing_dxf_attribs())
        for i in e.vertices:
            x=i.dxf.location[0]-xy_min[0]
            y=i.dxf.location[1]-xy_min[1]
            points.append([x,y])
        if e.dxf.flags==1:
            points.append(points[0])
        arc=0
        for i in e.virtual_entities():
            if i.dxftype() == "ARC":
                arc=1
        if arc==1:
            for i in e.virtual_entities():
                if i.dxftype() == "ARC":
                    x=i.dxf.center[0]-xy_min[0]
                    y=i.dxf.center[1]-xy_min[1]
                    flag.add_arc([x,y], i.dxf.radius, i.dxf.start_angle, i.dxf.end_angle)
                if i.dxftype() == "LINE":
                    ax=i.dxf.start[0]-xy_min[0]
                    ay=i.dxf.start[1]-xy_min[1]
                    ex=i.dxf.end[0]-xy_min[0]
                    ey=i.dxf.end[1]-xy_min[1]
                    flag.add_line([ax,ay],[ex,ey])
        else:
            flag.add_polyline2d(points)
            
    for e in msp.query("CIRCLE"):
        x=e.dxf.center[0]-xy_min[0]
        y=e.dxf.center[1]-xy_min[1]
        flag.add_circle([x,y],e.dxf.radius)
    for e in msp.query("LINE"):
        ax=e.dxf.start[0]-xy_min[0]
        ay=e.dxf.start[1]-xy_min[1]
        ex=e.dxf.end[0]-xy_min[0]
        ey=e.dxf.end[1]-xy_min[1]
        flag.add_line([ax,ay],[ex,ey])
    for e in msp.query("ARC"):
        x=e.dxf.center[0]-xy_min[0]
        y=e.dxf.center[1]-xy_min[1]
        flag.add_arc([x,y], e.dxf.radius, e.dxf.start_angle, e.dxf.end_angle)
    return doc1,flag_name,item_num,w,h

doc1 = ezdxf.new('R2000')

path="workspace"

_item_sizes=[]
ran=[]
gap=2.5
container_width=1500
for file_name in os.listdir(path):
    #后缀判断和前缀判断
    if file_name.endswith('.dxf') and file_name.startswith('3mm'):
        doc1,flag_name,item_num,w,h=get_doc(os.path.join(path,file_name),doc1)
        for i in range(int(item_num)):
            _item_sizes.append([w+2*gap,h+2*gap])
            ran.append(flag_name)

msp1 = doc1.modelspace()
poss,container_height=sf001.compute(container_width,_item_sizes, ran)

for pos in poss:
    a=_item_sizes[ran.index(pos[0])]
    b=[pos[3],pos[4]]
    if a[0]==b[0] and a[1]==b[1]:
        ins=[pos[1]+gap,pos[2]+gap]
        angle=0
    else:
        ins=[pos[1]-gap+b[0],pos[2]+gap]
        angle=90
    msp1.add_blockref(pos[0],ins, dxfattribs={'xscale': 1,'yscale': 1,'rotation': angle})
points = [(0, 0), (container_width, 0),(container_width, container_height), (0, container_height)]
msp1.add_polyline2d(points,dxfattribs={'layer': 'MyLine1','color':100,'closed': True})
doc1.saveas("lowest_horizontal_line_search.dxf")

 1.bat

python test001.py
pause

Python的ezdxf包操作dxf文件-矩形排版之最低水平线法

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
DXF文件中,我们最关心的是如何得到模型上各个点的坐标,并且用这些点连成许多个三用形,构成面,进而绘制出整个模型。在DXF文件的结构中,我们已经看到,DXF文件先叙述实体上各个点的坐标,然后叙述实体上有多少个面,每个面由哪些点构成。这样,我们至少需要2个数组来存储一个实体的信息,一个用于存储点的坐标,一个用于存储点序,我们可以把这2个数组放到一个结构中,如果模型中实体的数目不止一个是,我们就用这个结构来定义一个数组。在本文中,我们使用      Visual C++ 6.0 来写一个读取DXF文件的小程序。     在实际应用中,模型中实体的数目以及实体中点和面的数目都是不定的,为了有效地利用内存,我们选择MFC类库中的聚合类CobArray类所创建的对象vertex,      sequence来存储和管理实体的点坐标和点序。     CObArray类是一个用来存放数组类的聚合类,它能根据要存进来的数组(或结构)多少自动进行自身大小的高速,而且这个类本身具有的成员函数使得我们对它的对象的操作更加方便、快捷,用它编的程序也易于读懂。     三维实体模型的模型信息中的一部分信息可以在标题段中读出,通过读取变量名为$UCSORG的三个变量,可以得到三维实体在世界坐标系中自身所定义的用户坐标系原点的三维坐标。通过读取$EXTMAX,$EXTMIN可以获知三维实体在世界坐标系中的范围,而其它部分的信息只有读完了全部DXF文件后才可以通过计算确定。对于三维实体模型的全部点坐标、点序,可以在实体段中按照前面介绍的DXF文件基本结构读出。现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值