feat: 前后端全量开发 v0.2
后端: - 客户模块 CRUD+搜索 (api/services/repositories 分层) - 合同模块: FG单号规则/意向单转正式单/订单创建自动快照客户信息 - 优化下料: FFD首递减装箱算法(利用率统计/余料/未排入清单) 前端 (Vue3+TS+Element Plus): - 7页面: 登录/工作台/订单中心/合同/客户/样板库/优化下料 - 自研二维窗型画布渲染器: 解析drawing_data渲染玻璃区域/外框/ 扇开向标记/尺寸标注, 支持缩放平移复位 - 样板库与订单联动(用此样板下单自动带图纸) - axios统一envelope拦截/登录守卫/付费墙提示 工程: - SPA history 回退路由, 构建产物挂载后端 - 全接口回归 6/6 通过
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""一维优化下料服务:首递减(FFD)装箱算法,输出切割方案与利用率。
|
||||
|
||||
对齐目标站 optimize1d 语义:输入订单型材需求 + 库存原料,输出每根原料的切割组合。
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class BarDemand:
|
||||
"""一段型材需求。"""
|
||||
length: int # 需求长度(mm)
|
||||
qty: int # 数量
|
||||
label: str = "" # 用途标签
|
||||
|
||||
|
||||
@dataclass
|
||||
class StockBar:
|
||||
"""库存原料。"""
|
||||
length: int # 原料长度(mm)
|
||||
qty: int # 库存根数
|
||||
label: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class CutPiece:
|
||||
length: int
|
||||
label: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class CutPlan:
|
||||
"""一根原料的切割方案。"""
|
||||
stock_length: int
|
||||
stock_label: str
|
||||
pieces: list[CutPiece] = field(default_factory=list)
|
||||
waste: int = 0 # 余料浪费
|
||||
|
||||
|
||||
# 锯口损耗(mm),每次切割损耗一段
|
||||
KERF_DEFAULT = 5
|
||||
|
||||
|
||||
def optimize_1d(demands: list[BarDemand], stocks: list[StockBar],
|
||||
kerf: int = KERF_DEFAULT) -> dict:
|
||||
"""首递减装箱:长件优先装入可容纳的原料,输出方案与统计。"""
|
||||
# 展开需求并降序
|
||||
pieces: list[CutPiece] = []
|
||||
for d in demands:
|
||||
pieces.extend(CutPiece(length=d.length, label=d.label) for _ in range(max(0, d.qty)))
|
||||
pieces.sort(key=lambda p: p.length, reverse=True)
|
||||
|
||||
# 展开库存(可切割长度 = 原料长)
|
||||
avail: list[dict] = []
|
||||
for s in stocks:
|
||||
for _ in range(max(0, s.qty)):
|
||||
avail.append({"length": s.length, "label": s.label, "pieces": [], "used": 0})
|
||||
|
||||
unplaced: list[CutPiece] = []
|
||||
for piece in pieces:
|
||||
# 找第一根装得下的原料(剩余空间 >= 件长 + 锯口)
|
||||
placed = False
|
||||
for bar in avail:
|
||||
remain = bar["length"] - bar["used"]
|
||||
cost = piece.length + (kerf if bar["pieces"] else 0)
|
||||
if remain >= cost:
|
||||
bar["pieces"].append(piece)
|
||||
bar["used"] += cost
|
||||
placed = True
|
||||
break
|
||||
if not placed:
|
||||
unplaced.append(piece)
|
||||
|
||||
plans = [
|
||||
CutPlan(
|
||||
stock_length=b["length"],
|
||||
stock_label=b["label"],
|
||||
pieces=b["pieces"],
|
||||
waste=b["length"] - b["used"],
|
||||
)
|
||||
for b in avail if b["pieces"]
|
||||
]
|
||||
total_demand = sum(p.length for p in pieces)
|
||||
total_stock = sum(p.stock_length for p in plans)
|
||||
return {
|
||||
"plans": [
|
||||
{
|
||||
"stock_length": p.stock_length,
|
||||
"stock_label": p.stock_label,
|
||||
"pieces": [{"length": c.length, "label": c.label} for c in p.pieces],
|
||||
"waste": p.waste,
|
||||
}
|
||||
for p in plans
|
||||
],
|
||||
"unplaced": [{"length": c.length, "label": c.label} for c in unplaced],
|
||||
"stats": {
|
||||
"total_pieces": len(pieces),
|
||||
"placed_pieces": len(pieces) - len(unplaced),
|
||||
"stock_bars_used": len(plans),
|
||||
"total_demand_mm": total_demand,
|
||||
"total_stock_mm": total_stock,
|
||||
"utilization": round(total_demand / total_stock, 4) if total_stock else 0,
|
||||
"kerf_mm": kerf,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user