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文件-矩形排版之最低水平线法