Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -13,9 +13,8 @@ from reportlab.lib.pagesizes import letter
|
|
| 13 |
# ==========================================
|
| 14 |
# CONFIGURAÇÃO
|
| 15 |
# ==========================================
|
| 16 |
-
st.set_page_config(page_title="Calculadora Formadoras
|
| 17 |
|
| 18 |
-
# Lista de fitas disponíveis no estoque
|
| 19 |
FITAS = [
|
| 20 |
{"nome": "Fita 1.20", "tamanho": 1.20}, {"nome": "Fita 1.40", "tamanho": 1.40},
|
| 21 |
{"nome": "Fita 1.50", "tamanho": 1.50}, {"nome": "Fita 1.60", "tamanho": 1.60},
|
|
@@ -66,20 +65,19 @@ class PackageCalculator:
|
|
| 66 |
return best_item, best_dist
|
| 67 |
|
| 68 |
def _escolher_fita(self, perimetro_m):
|
| 69 |
-
# Margem de 10%
|
| 70 |
perimetro_com_margem = perimetro_m * 1.10
|
| 71 |
for f in FITAS:
|
| 72 |
if f["tamanho"] >= perimetro_com_margem:
|
| 73 |
return f["nome"], f["tamanho"], perimetro_com_margem
|
| 74 |
-
return "Sob Medida
|
| 75 |
|
| 76 |
def calculate(self, modo, meta, l, a, e, c, tipo, acabamento, aba=0):
|
| 77 |
l, a, e = float(l), float(a), float(e)
|
| 78 |
is_galv = (acabamento == "Galvanizado (GI/GA)")
|
| 79 |
wood_h = 6.60 if is_galv else 0.0
|
| 80 |
-
wood_w = 48.20
|
| 81 |
|
| 82 |
-
# Busca no Banco
|
| 83 |
key_red = f"{l:.2f}_{e:.2f}"
|
| 84 |
key_met = f"{l:.2f}_{a:.2f}_{e:.2f}"
|
| 85 |
key_met_inv = f"{a:.2f}_{l:.2f}_{e:.2f}"
|
|
@@ -100,7 +98,6 @@ class PackageCalculator:
|
|
| 100 |
item = None
|
| 101 |
origem = "Engenharia (Cálculo)"
|
| 102 |
|
| 103 |
-
# Propriedades Físicas
|
| 104 |
densidade = 7.85e-6
|
| 105 |
if tipo == "Redondo":
|
| 106 |
area = math.pi * ((l/2)**2 - (l/2-e)**2)
|
|
@@ -110,7 +107,6 @@ class PackageCalculator:
|
|
| 110 |
area = (l + 2*a - 2*e) * e
|
| 111 |
peso_unit = area * c * densidade
|
| 112 |
|
| 113 |
-
# Definição do Layout
|
| 114 |
if item:
|
| 115 |
num_pecas = item['qtde']
|
| 116 |
if item.get('sextavado') or (tipo == "Redondo" and 'padrao' in item):
|
|
@@ -120,7 +116,6 @@ class PackageCalculator:
|
|
| 120 |
layout = None
|
| 121 |
is_sext, gl, ga = False, item.get('ml', 0), item.get('ma', 0)
|
| 122 |
else:
|
| 123 |
-
# Fallback Matemático
|
| 124 |
peso_alvo = 1000
|
| 125 |
num_pecas = int(peso_alvo / peso_unit) if peso_unit > 0 else 1
|
| 126 |
if tipo == "Redondo":
|
|
@@ -140,7 +135,6 @@ class PackageCalculator:
|
|
| 140 |
|
| 141 |
peso_pct = num_pecas * peso_unit
|
| 142 |
|
| 143 |
-
# Totais
|
| 144 |
if modo == "Toneladas":
|
| 145 |
total_pcs = int((meta * 1000) / peso_unit) if peso_unit > 0 else 0
|
| 146 |
else:
|
|
@@ -148,7 +142,6 @@ class PackageCalculator:
|
|
| 148 |
qtd_pcts = total_pcs // num_pecas if num_pecas > 0 else 0
|
| 149 |
sobra = total_pcs % num_pecas
|
| 150 |
|
| 151 |
-
# Montagem
|
| 152 |
main = self._build_pkg(l, a, e, c, tipo, aba, num_pecas, peso_pct, origem, is_sext, layout, gl, ga, False, is_galv, wood_h, wood_w)
|
| 153 |
|
| 154 |
sobra_pkg = None
|
|
@@ -178,7 +171,6 @@ class PackageCalculator:
|
|
| 178 |
"is_galv": is_galv, "wood_h": wood_h, "wood_w": wood_w
|
| 179 |
}
|
| 180 |
|
| 181 |
-
# Dimensões e Madeiras
|
| 182 |
qtd_madeiras = 0
|
| 183 |
if sext:
|
| 184 |
rows = layout or []
|
|
@@ -186,9 +178,8 @@ class PackageCalculator:
|
|
| 186 |
info["larg_total"] = max_c * l
|
| 187 |
# Altura = Geometria + Madeira entre camadas
|
| 188 |
h_geo = l + (len(rows) - 1) * l * math.sin(math.radians(60))
|
| 189 |
-
# Se Galv, 5 madeiras por linha (exceto a última talvez, mas vamos padronizar por camada usada)
|
| 190 |
if is_galv:
|
| 191 |
-
h_wood = (len(rows)) * wood_h
|
| 192 |
qtd_madeiras = len(rows) * 5
|
| 193 |
else:
|
| 194 |
h_wood = 0
|
|
@@ -196,9 +187,8 @@ class PackageCalculator:
|
|
| 196 |
else:
|
| 197 |
info["larg_total"] = gl * l
|
| 198 |
if tipo == "Enrijecido": info["larg_total"] += aba
|
| 199 |
-
# Altura
|
| 200 |
if is_galv:
|
| 201 |
-
h_wood = ga * wood_h
|
| 202 |
qtd_madeiras = ga * 5
|
| 203 |
else:
|
| 204 |
h_wood = 0
|
|
@@ -206,21 +196,17 @@ class PackageCalculator:
|
|
| 206 |
|
| 207 |
info["qtd_madeiras"] = qtd_madeiras
|
| 208 |
|
| 209 |
-
#
|
| 210 |
-
# 1. Fita Principal (Envolve tudo)
|
| 211 |
perim_total = (info["larg_total"] + info["alt_total"]) * 2 / 1000
|
| 212 |
nome_fita, tam_fita, real_fita = self._escolher_fita(perim_total)
|
| 213 |
info["fita_nome"] = nome_fita
|
| 214 |
info["fita_tamanho"] = tam_fita
|
| 215 |
-
info["fita_real_necessaria"] = real_fita
|
| 216 |
|
| 217 |
-
#
|
| 218 |
info["fita_meio_nome"] = "N/A"
|
| 219 |
info["fita_meio_tamanho"] = 0
|
| 220 |
|
| 221 |
if not is_galv:
|
| 222 |
-
# Fita que pega largura total + metade da altura
|
| 223 |
-
# Perimetro parcial = 2 * (Largura + Altura/2)
|
| 224 |
perim_meio = (info["larg_total"] + (info["alt_total"] / 2)) * 2 / 1000
|
| 225 |
n_meio, t_meio, r_meio = self._escolher_fita(perim_meio)
|
| 226 |
info["fita_meio_nome"] = n_meio
|
|
@@ -238,75 +224,49 @@ class PackageCalculator:
|
|
| 238 |
def plot_cross_section(info):
|
| 239 |
fig, ax = plt.subplots(figsize=(10, 8))
|
| 240 |
l, a, e = info['l'], info['a'], info['e']
|
| 241 |
-
wood_h
|
| 242 |
is_galv = info['is_galv']
|
| 243 |
|
| 244 |
fc = 'lightblue' if not info['is_rem'] else 'lightcoral'
|
| 245 |
ec = 'black'
|
| 246 |
|
| 247 |
-
# Desenhar Peças e Madeira
|
| 248 |
if info['sext']:
|
| 249 |
-
#
|
| 250 |
rows = info['layout']
|
| 251 |
-
|
| 252 |
dy = l * 0.866
|
| 253 |
total_w = info['larg_total']
|
| 254 |
|
| 255 |
for i, count in enumerate(rows):
|
| 256 |
row_w = count * l
|
| 257 |
x0 = (total_w - row_w) / 2
|
| 258 |
-
|
| 259 |
-
y_base = r + i * dy + (i * wood_h)
|
| 260 |
|
| 261 |
-
#
|
| 262 |
if is_galv:
|
| 263 |
-
#
|
| 264 |
-
#
|
| 265 |
-
wy = y_base -
|
| 266 |
-
|
| 267 |
-
for m in range(5):
|
| 268 |
-
wx = (m * espaco) + (espaco/2) - (wood_w/2)
|
| 269 |
-
ax.add_patch(patches.Rectangle((wx, wy), wood_w, wood_h, fc='peru', ec='brown'))
|
| 270 |
|
| 271 |
for k in range(count):
|
| 272 |
-
cx = x0 + k * l +
|
| 273 |
-
ax.add_patch(patches.Circle((cx, y_base),
|
| 274 |
-
ax.add_patch(patches.Circle((cx, y_base),
|
| 275 |
else:
|
| 276 |
-
#
|
| 277 |
gl, ga = info['gl'], info['ga']
|
| 278 |
drawn = 0
|
| 279 |
for i in range(ga):
|
| 280 |
y = i * (a + wood_h)
|
| 281 |
|
| 282 |
-
# Madeira (
|
| 283 |
-
if is_galv:
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
# Vamos desenhar a madeira embaixo de cada tubo.
|
| 290 |
-
tube_y = y
|
| 291 |
-
if i == 0: # Base
|
| 292 |
-
# Madeira base
|
| 293 |
-
wy = 0
|
| 294 |
-
tube_y = wood_h
|
| 295 |
-
else:
|
| 296 |
-
# Madeira entre camadas
|
| 297 |
-
wy = i * (a + wood_h)
|
| 298 |
-
tube_y = wy + wood_h
|
| 299 |
-
|
| 300 |
-
# Mas para manter simples: Y calculado acima já considera espaçamento
|
| 301 |
-
# Vamos desenhar a madeira 'sob' o tubo dessa iteração
|
| 302 |
-
# Distribui 5 na largura
|
| 303 |
-
espaco = info['larg_total'] / 5
|
| 304 |
-
for m in range(5):
|
| 305 |
-
wx = (m * espaco) + (espaco/2) - (wood_w/2)
|
| 306 |
-
# Desenha madeira na base da camada i
|
| 307 |
-
ax.add_patch(patches.Rectangle((wx, i*(a+wood_h)), wood_w, wood_h, fc='peru', ec='brown'))
|
| 308 |
-
|
| 309 |
-
tube_y_draw = i * (a + wood_h) + (wood_h if is_galv else 0)
|
| 310 |
|
| 311 |
for j in range(gl):
|
| 312 |
if drawn >= info['num']: break
|
|
@@ -320,24 +280,14 @@ def plot_cross_section(info):
|
|
| 320 |
ax.add_patch(patches.Rectangle((x+inset, tube_y_draw+inset), l-2*inset, a-2*inset, fc='white', ec=ec, lw=0.5))
|
| 321 |
drawn += 1
|
| 322 |
|
| 323 |
-
# Cotas
|
| 324 |
L_tot, A_tot = info['larg_total'], info['alt_total']
|
| 325 |
margin = max(L_tot, A_tot) * 0.2
|
| 326 |
|
| 327 |
-
# Bounding Box
|
| 328 |
ax.add_patch(patches.Rectangle((-2, -2), L_tot+4, A_tot+4, fill=False, ec='red', ls='--', lw=1.5, label='Limite'))
|
| 329 |
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
ax.add_patch(patches.Rectangle((-off, -off), L_tot+2*off, A_tot+2*off, fill=False, ec='purple', ls='-', lw=2, label='Fita Principal'))
|
| 333 |
-
|
| 334 |
-
# Cinta Meio (Se houver)
|
| 335 |
-
if info['fita_meio_tamanho'] > 0:
|
| 336 |
-
# Representação visual da fita de meio (metade da altura)
|
| 337 |
-
ax.add_patch(patches.Rectangle((-off-2, -off-2), L_tot+2*(off+2), (A_tot/2)+2*(off+2), fill=False, ec='orange', ls='--', lw=2, label='Fita Meio'))
|
| 338 |
-
|
| 339 |
-
ax.text(L_tot/2, -margin/2, f"{L_tot:.1f} mm", ha='center', va='center', fontweight='bold')
|
| 340 |
-
ax.text(-margin/2, A_tot/2, f"{A_tot:.1f} mm", ha='center', va='center', fontweight='bold', rotation=90)
|
| 341 |
|
| 342 |
ax.set_xlim(-margin, L_tot+margin)
|
| 343 |
ax.set_ylim(-margin, A_tot+margin)
|
|
@@ -345,8 +295,7 @@ def plot_cross_section(info):
|
|
| 345 |
ax.axis('off')
|
| 346 |
|
| 347 |
if is_galv:
|
| 348 |
-
|
| 349 |
-
ax.add_patch(patches.Rectangle((0,0),0,0, fc='peru', label=f"{info['qtd_madeiras']} Madeiras"))
|
| 350 |
|
| 351 |
plt.title(f"{info['origem']} | {info['num']} Pçs | {info['peso']:.1f}kg", fontsize=11)
|
| 352 |
plt.legend(loc='upper right', fontsize='small')
|
|
@@ -356,29 +305,63 @@ def plot_side_view(info):
|
|
| 356 |
fig, ax = plt.subplots(figsize=(10, 3))
|
| 357 |
c, h = info['c'], info['alt_total']
|
| 358 |
is_galv = info['is_galv']
|
|
|
|
|
|
|
| 359 |
|
| 360 |
ax.add_patch(patches.Rectangle((0, 0), c, h, fc='lightgray', ec='black'))
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
# Fitas
|
| 363 |
-
|
| 364 |
-
for idx, p in enumerate(
|
| 365 |
x = c * p
|
| 366 |
if not is_galv and idx == 2: # Meio
|
| 367 |
-
ax.plot([x, x], [0, h/2], color='orange', lw=3, ls='--')
|
| 368 |
else:
|
| 369 |
-
ax.plot([x, x], [0, h], color='purple', lw=3)
|
| 370 |
|
| 371 |
ax.text(c/2, -h*0.2, f"COMPRIMENTO: {c:.0f} mm", ha='center', va='top', fontweight='bold', color='black')
|
| 372 |
ax.set_xlim(-c*0.05, c*1.05)
|
| 373 |
ax.set_ylim(-h*0.3, h*1.1)
|
| 374 |
ax.axis('off')
|
| 375 |
-
plt.title(f"Vista Lateral - {info['desc_fitas']}", fontsize=10)
|
| 376 |
return fig
|
| 377 |
|
| 378 |
# ==========================================
|
| 379 |
# EXPORTAÇÃO
|
| 380 |
# ==========================================
|
| 381 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
def generate_pdf(res):
|
| 383 |
buffer = io.BytesIO()
|
| 384 |
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
|
@@ -387,40 +370,41 @@ def generate_pdf(res):
|
|
| 387 |
m = res['main']
|
| 388 |
|
| 389 |
story.append(Paragraph(f"Ficha: {m['tipo']} {m['l']}x{m['a']} #{m['e']}", styles['Title']))
|
|
|
|
| 390 |
|
| 391 |
-
|
| 392 |
-
|
|
|
|
|
|
|
| 393 |
if m['fita_meio_tamanho'] > 0:
|
| 394 |
fita_txt += f" + 1x {m['fita_meio_nome']} ({m['fita_meio_tamanho']}m)"
|
| 395 |
else:
|
| 396 |
-
fita_txt = f"5x {m['
|
| 397 |
|
| 398 |
wood_txt = f"{m['qtd_madeiras']} peças (48.2x6.6mm)" if m['is_galv'] else "N/A"
|
| 399 |
|
| 400 |
data = [
|
| 401 |
["Item", f"{m['l']}x{m['a']} #{m['e']}mm"],
|
| 402 |
-
["Acabamento",
|
| 403 |
-
["
|
|
|
|
| 404 |
["Cintas", fita_txt],
|
| 405 |
-
["Madeiras", wood_txt]
|
| 406 |
-
["Peso Total", f"{res['peso_total']:.2f} kg"]
|
| 407 |
]
|
| 408 |
t = Table(data, colWidths=[150, 300])
|
| 409 |
t.setStyle(TableStyle([('GRID', (0,0), (-1,-1), 1, colors.black), ('BACKGROUND', (0,0), (0,-1), colors.lightgrey)]))
|
| 410 |
story.append(t)
|
| 411 |
story.append(Spacer(1, 20))
|
| 412 |
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
buf1.seek(0)
|
| 417 |
-
story.append(RLImage(buf1, width=400, height=300, kind='proportional'))
|
| 418 |
-
|
| 419 |
-
buf2 = io.BytesIO()
|
| 420 |
-
plot_side_view(m).savefig(buf2, format='png', dpi=100, bbox_inches='tight')
|
| 421 |
-
buf2.seek(0)
|
| 422 |
-
story.append(RLImage(buf2, width=400, height=150, kind='proportional'))
|
| 423 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
doc.build(story)
|
| 425 |
buffer.seek(0)
|
| 426 |
return buffer
|
|
@@ -431,14 +415,15 @@ def generate_csv(res):
|
|
| 431 |
m = res['main']
|
| 432 |
writer.writerow(["Tipo", "L", "A", "E", "Acab", "Qtd Pcts", "Pecas/Pct", "Fita Princ", "Fita Meio", "Qtd Madeira"])
|
| 433 |
acab = "GI/GA" if m['is_galv'] else "Normal"
|
| 434 |
-
|
|
|
|
| 435 |
return output.getvalue()
|
| 436 |
|
| 437 |
# ==========================================
|
| 438 |
# INTERFACE
|
| 439 |
# ==========================================
|
| 440 |
|
| 441 |
-
st.title("🏭 Calculadora Formadoras
|
| 442 |
|
| 443 |
with st.sidebar:
|
| 444 |
st.header("Parâmetros")
|
|
@@ -446,10 +431,11 @@ with st.sidebar:
|
|
| 446 |
acabamento = st.radio("Acabamento", ["Preto (Normal)", "Galvanizado (GI/GA)"])
|
| 447 |
|
| 448 |
c1, c2 = st.columns(2)
|
| 449 |
-
l = c1.number_input("Largura/Diâm (mm)", value=50.0)
|
| 450 |
-
a = c2.number_input("Altura (mm)", value=50.0, disabled=(tipo=="Redondo"))
|
| 451 |
-
e =
|
| 452 |
-
c =
|
|
|
|
| 453 |
aba = st.number_input("Aba (U)", value=0.0, disabled=(tipo!="U/Enrijecido"))
|
| 454 |
|
| 455 |
st.markdown("---")
|
|
|
|
| 13 |
# ==========================================
|
| 14 |
# CONFIGURAÇÃO
|
| 15 |
# ==========================================
|
| 16 |
+
st.set_page_config(page_title="Calculadora Formadoras v6.0", layout="wide")
|
| 17 |
|
|
|
|
| 18 |
FITAS = [
|
| 19 |
{"nome": "Fita 1.20", "tamanho": 1.20}, {"nome": "Fita 1.40", "tamanho": 1.40},
|
| 20 |
{"nome": "Fita 1.50", "tamanho": 1.50}, {"nome": "Fita 1.60", "tamanho": 1.60},
|
|
|
|
| 65 |
return best_item, best_dist
|
| 66 |
|
| 67 |
def _escolher_fita(self, perimetro_m):
|
| 68 |
+
# Margem de 10%
|
| 69 |
perimetro_com_margem = perimetro_m * 1.10
|
| 70 |
for f in FITAS:
|
| 71 |
if f["tamanho"] >= perimetro_com_margem:
|
| 72 |
return f["nome"], f["tamanho"], perimetro_com_margem
|
| 73 |
+
return "Sob Medida", perimetro_com_margem, perimetro_com_margem
|
| 74 |
|
| 75 |
def calculate(self, modo, meta, l, a, e, c, tipo, acabamento, aba=0):
|
| 76 |
l, a, e = float(l), float(a), float(e)
|
| 77 |
is_galv = (acabamento == "Galvanizado (GI/GA)")
|
| 78 |
wood_h = 6.60 if is_galv else 0.0
|
| 79 |
+
wood_w = 48.20
|
| 80 |
|
|
|
|
| 81 |
key_red = f"{l:.2f}_{e:.2f}"
|
| 82 |
key_met = f"{l:.2f}_{a:.2f}_{e:.2f}"
|
| 83 |
key_met_inv = f"{a:.2f}_{l:.2f}_{e:.2f}"
|
|
|
|
| 98 |
item = None
|
| 99 |
origem = "Engenharia (Cálculo)"
|
| 100 |
|
|
|
|
| 101 |
densidade = 7.85e-6
|
| 102 |
if tipo == "Redondo":
|
| 103 |
area = math.pi * ((l/2)**2 - (l/2-e)**2)
|
|
|
|
| 107 |
area = (l + 2*a - 2*e) * e
|
| 108 |
peso_unit = area * c * densidade
|
| 109 |
|
|
|
|
| 110 |
if item:
|
| 111 |
num_pecas = item['qtde']
|
| 112 |
if item.get('sextavado') or (tipo == "Redondo" and 'padrao' in item):
|
|
|
|
| 116 |
layout = None
|
| 117 |
is_sext, gl, ga = False, item.get('ml', 0), item.get('ma', 0)
|
| 118 |
else:
|
|
|
|
| 119 |
peso_alvo = 1000
|
| 120 |
num_pecas = int(peso_alvo / peso_unit) if peso_unit > 0 else 1
|
| 121 |
if tipo == "Redondo":
|
|
|
|
| 135 |
|
| 136 |
peso_pct = num_pecas * peso_unit
|
| 137 |
|
|
|
|
| 138 |
if modo == "Toneladas":
|
| 139 |
total_pcs = int((meta * 1000) / peso_unit) if peso_unit > 0 else 0
|
| 140 |
else:
|
|
|
|
| 142 |
qtd_pcts = total_pcs // num_pecas if num_pecas > 0 else 0
|
| 143 |
sobra = total_pcs % num_pecas
|
| 144 |
|
|
|
|
| 145 |
main = self._build_pkg(l, a, e, c, tipo, aba, num_pecas, peso_pct, origem, is_sext, layout, gl, ga, False, is_galv, wood_h, wood_w)
|
| 146 |
|
| 147 |
sobra_pkg = None
|
|
|
|
| 171 |
"is_galv": is_galv, "wood_h": wood_h, "wood_w": wood_w
|
| 172 |
}
|
| 173 |
|
|
|
|
| 174 |
qtd_madeiras = 0
|
| 175 |
if sext:
|
| 176 |
rows = layout or []
|
|
|
|
| 178 |
info["larg_total"] = max_c * l
|
| 179 |
# Altura = Geometria + Madeira entre camadas
|
| 180 |
h_geo = l + (len(rows) - 1) * l * math.sin(math.radians(60))
|
|
|
|
| 181 |
if is_galv:
|
| 182 |
+
h_wood = (len(rows)) * wood_h
|
| 183 |
qtd_madeiras = len(rows) * 5
|
| 184 |
else:
|
| 185 |
h_wood = 0
|
|
|
|
| 187 |
else:
|
| 188 |
info["larg_total"] = gl * l
|
| 189 |
if tipo == "Enrijecido": info["larg_total"] += aba
|
|
|
|
| 190 |
if is_galv:
|
| 191 |
+
h_wood = ga * wood_h
|
| 192 |
qtd_madeiras = ga * 5
|
| 193 |
else:
|
| 194 |
h_wood = 0
|
|
|
|
| 196 |
|
| 197 |
info["qtd_madeiras"] = qtd_madeiras
|
| 198 |
|
| 199 |
+
# Fita Principal
|
|
|
|
| 200 |
perim_total = (info["larg_total"] + info["alt_total"]) * 2 / 1000
|
| 201 |
nome_fita, tam_fita, real_fita = self._escolher_fita(perim_total)
|
| 202 |
info["fita_nome"] = nome_fita
|
| 203 |
info["fita_tamanho"] = tam_fita
|
|
|
|
| 204 |
|
| 205 |
+
# Fita Meio
|
| 206 |
info["fita_meio_nome"] = "N/A"
|
| 207 |
info["fita_meio_tamanho"] = 0
|
| 208 |
|
| 209 |
if not is_galv:
|
|
|
|
|
|
|
| 210 |
perim_meio = (info["larg_total"] + (info["alt_total"] / 2)) * 2 / 1000
|
| 211 |
n_meio, t_meio, r_meio = self._escolher_fita(perim_meio)
|
| 212 |
info["fita_meio_nome"] = n_meio
|
|
|
|
| 224 |
def plot_cross_section(info):
|
| 225 |
fig, ax = plt.subplots(figsize=(10, 8))
|
| 226 |
l, a, e = info['l'], info['a'], info['e']
|
| 227 |
+
wood_h = info['wood_h']
|
| 228 |
is_galv = info['is_galv']
|
| 229 |
|
| 230 |
fc = 'lightblue' if not info['is_rem'] else 'lightcoral'
|
| 231 |
ec = 'black'
|
| 232 |
|
|
|
|
| 233 |
if info['sext']:
|
| 234 |
+
# --- SEXTAVADO ---
|
| 235 |
rows = info['layout']
|
| 236 |
+
radius = l / 2
|
| 237 |
dy = l * 0.866
|
| 238 |
total_w = info['larg_total']
|
| 239 |
|
| 240 |
for i, count in enumerate(rows):
|
| 241 |
row_w = count * l
|
| 242 |
x0 = (total_w - row_w) / 2
|
| 243 |
+
y_base = radius + i * dy + (i * wood_h)
|
|
|
|
| 244 |
|
| 245 |
+
# Madeira Transversal (Vista de Frente = Barra Contínua)
|
| 246 |
if is_galv:
|
| 247 |
+
# A madeira é um sarrafo transversal. No corte, vemos o comprimento da madeira atravessando a largura.
|
| 248 |
+
# Desenhamos uma barra inteira por baixo da camada
|
| 249 |
+
wy = y_base - radius - wood_h
|
| 250 |
+
ax.add_patch(patches.Rectangle((0, wy), total_w, wood_h, fc='peru', ec='brown'))
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
for k in range(count):
|
| 253 |
+
cx = x0 + k * l + radius
|
| 254 |
+
ax.add_patch(patches.Circle((cx, y_base), radius, fc=fc, ec=ec))
|
| 255 |
+
ax.add_patch(patches.Circle((cx, y_base), radius-e, fc='white', ec=ec, lw=0.5))
|
| 256 |
else:
|
| 257 |
+
# --- GRADE RETANGULAR ---
|
| 258 |
gl, ga = info['gl'], info['ga']
|
| 259 |
drawn = 0
|
| 260 |
for i in range(ga):
|
| 261 |
y = i * (a + wood_h)
|
| 262 |
|
| 263 |
+
# Madeira Transversal (Barra Contínua)
|
| 264 |
+
if is_galv:
|
| 265 |
+
# Desenha madeira na base da camada
|
| 266 |
+
ax.add_patch(patches.Rectangle((0, y), info['larg_total'], wood_h, fc='peru', ec='brown'))
|
| 267 |
+
tube_y_draw = y + wood_h
|
| 268 |
+
else:
|
| 269 |
+
tube_y_draw = y
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
|
| 271 |
for j in range(gl):
|
| 272 |
if drawn >= info['num']: break
|
|
|
|
| 280 |
ax.add_patch(patches.Rectangle((x+inset, tube_y_draw+inset), l-2*inset, a-2*inset, fc='white', ec=ec, lw=0.5))
|
| 281 |
drawn += 1
|
| 282 |
|
| 283 |
+
# Cotas
|
| 284 |
L_tot, A_tot = info['larg_total'], info['alt_total']
|
| 285 |
margin = max(L_tot, A_tot) * 0.2
|
| 286 |
|
|
|
|
| 287 |
ax.add_patch(patches.Rectangle((-2, -2), L_tot+4, A_tot+4, fill=False, ec='red', ls='--', lw=1.5, label='Limite'))
|
| 288 |
|
| 289 |
+
ax.text(L_tot/2, -margin/2, f"L: {L_tot:.1f} mm", ha='center', va='center', fontweight='bold')
|
| 290 |
+
ax.text(-margin/2, A_tot/2, f"A: {A_tot:.1f} mm", ha='center', va='center', fontweight='bold', rotation=90)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
ax.set_xlim(-margin, L_tot+margin)
|
| 293 |
ax.set_ylim(-margin, A_tot+margin)
|
|
|
|
| 295 |
ax.axis('off')
|
| 296 |
|
| 297 |
if is_galv:
|
| 298 |
+
ax.plot([], [], color='peru', label='Madeira (Vista Transversal)', lw=4)
|
|
|
|
| 299 |
|
| 300 |
plt.title(f"{info['origem']} | {info['num']} Pçs | {info['peso']:.1f}kg", fontsize=11)
|
| 301 |
plt.legend(loc='upper right', fontsize='small')
|
|
|
|
| 305 |
fig, ax = plt.subplots(figsize=(10, 3))
|
| 306 |
c, h = info['c'], info['alt_total']
|
| 307 |
is_galv = info['is_galv']
|
| 308 |
+
wood_w = info['wood_w']
|
| 309 |
+
wood_h = info['wood_h']
|
| 310 |
|
| 311 |
ax.add_patch(patches.Rectangle((0, 0), c, h, fc='lightgray', ec='black'))
|
| 312 |
|
| 313 |
+
# --- DESENHO DAS 5 MADEIRAS (VISTA LATERAL) ---
|
| 314 |
+
if is_galv:
|
| 315 |
+
# Temos 'ga' camadas (ou len(layout)). Para cada camada, desenhamos 5 tocos de madeira.
|
| 316 |
+
# Precisamos saber a altura Y de cada camada de madeira.
|
| 317 |
+
layers = info['ga'] if not info['sext'] else len(info['layout'])
|
| 318 |
+
|
| 319 |
+
# Distribuição ao longo do comprimento (5 posições)
|
| 320 |
+
positions_wood = [0.1, 0.3, 0.5, 0.7, 0.9]
|
| 321 |
+
|
| 322 |
+
for i in range(layers):
|
| 323 |
+
# Calcular Y da madeira dessa camada
|
| 324 |
+
if info['sext']:
|
| 325 |
+
# Sextavado: madeira entra abaixo da camada i
|
| 326 |
+
radius = info['l'] / 2
|
| 327 |
+
dy = info['l'] * 0.866
|
| 328 |
+
y_wood = (radius + i * dy + (i * wood_h)) - radius - wood_h
|
| 329 |
+
else:
|
| 330 |
+
# Retangular
|
| 331 |
+
y_wood = i * (info['a'] + wood_h)
|
| 332 |
+
|
| 333 |
+
# Desenhar os 5 tocos nessa altura Y
|
| 334 |
+
for pos in positions_wood:
|
| 335 |
+
wx = (c * pos) - (wood_w / 2)
|
| 336 |
+
ax.add_patch(patches.Rectangle((wx, y_wood), wood_w, wood_h, fc='peru', ec='brown'))
|
| 337 |
+
|
| 338 |
# Fitas
|
| 339 |
+
positions_fita = [0.1, 0.3, 0.5, 0.7, 0.9]
|
| 340 |
+
for idx, p in enumerate(positions_fita):
|
| 341 |
x = c * p
|
| 342 |
if not is_galv and idx == 2: # Meio
|
| 343 |
+
ax.plot([x, x], [0, h/2], color='orange', lw=3, ls='--')
|
| 344 |
else:
|
| 345 |
+
ax.plot([x, x], [0, h], color='purple', lw=3)
|
| 346 |
|
| 347 |
ax.text(c/2, -h*0.2, f"COMPRIMENTO: {c:.0f} mm", ha='center', va='top', fontweight='bold', color='black')
|
| 348 |
ax.set_xlim(-c*0.05, c*1.05)
|
| 349 |
ax.set_ylim(-h*0.3, h*1.1)
|
| 350 |
ax.axis('off')
|
| 351 |
+
plt.title(f"Vista Lateral - {info['desc_fitas']} + Madeiras", fontsize=10)
|
| 352 |
return fig
|
| 353 |
|
| 354 |
# ==========================================
|
| 355 |
# EXPORTAÇÃO
|
| 356 |
# ==========================================
|
| 357 |
|
| 358 |
+
def fig_to_img(fig):
|
| 359 |
+
buf = io.BytesIO()
|
| 360 |
+
fig.savefig(buf, format='png', dpi=100, bbox_inches='tight')
|
| 361 |
+
buf.seek(0)
|
| 362 |
+
plt.close(fig)
|
| 363 |
+
return RLImage(buf, width=450, height=350, kind='proportional')
|
| 364 |
+
|
| 365 |
def generate_pdf(res):
|
| 366 |
buffer = io.BytesIO()
|
| 367 |
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
|
|
|
| 370 |
m = res['main']
|
| 371 |
|
| 372 |
story.append(Paragraph(f"Ficha: {m['tipo']} {m['l']}x{m['a']} #{m['e']}", styles['Title']))
|
| 373 |
+
story.append(Spacer(1, 12))
|
| 374 |
|
| 375 |
+
acabamento = "Galvanizado (Com Madeira)" if m['is_galv'] else "Preto (Normal)"
|
| 376 |
+
|
| 377 |
+
# CORREÇÃO AQUI: Usar fita_nome
|
| 378 |
+
fita_txt = f"4x {m['fita_nome']} ({m['fita_tamanho']}m)"
|
| 379 |
if m['fita_meio_tamanho'] > 0:
|
| 380 |
fita_txt += f" + 1x {m['fita_meio_nome']} ({m['fita_meio_tamanho']}m)"
|
| 381 |
else:
|
| 382 |
+
fita_txt = f"5x {m['fita_nome']} ({m['fita_tamanho']}m)"
|
| 383 |
|
| 384 |
wood_txt = f"{m['qtd_madeiras']} peças (48.2x6.6mm)" if m['is_galv'] else "N/A"
|
| 385 |
|
| 386 |
data = [
|
| 387 |
["Item", f"{m['l']}x{m['a']} #{m['e']}mm"],
|
| 388 |
+
["Acabamento", acabamento],
|
| 389 |
+
["Produção", f"{res['total_pcts']} pct + {1 if res['sobra'] else 0} sobra"],
|
| 390 |
+
["Peso Total", f"{res['peso_total']:.2f} kg"],
|
| 391 |
["Cintas", fita_txt],
|
| 392 |
+
["Madeiras", wood_txt]
|
|
|
|
| 393 |
]
|
| 394 |
t = Table(data, colWidths=[150, 300])
|
| 395 |
t.setStyle(TableStyle([('GRID', (0,0), (-1,-1), 1, colors.black), ('BACKGROUND', (0,0), (0,-1), colors.lightgrey)]))
|
| 396 |
story.append(t)
|
| 397 |
story.append(Spacer(1, 20))
|
| 398 |
|
| 399 |
+
story.append(Paragraph(f"1. Pacote Padrão ({m['num']} pçs)", styles['Heading2']))
|
| 400 |
+
story.append(fig_to_img(plot_cross_section(m)))
|
| 401 |
+
story.append(fig_to_img(plot_side_view(m)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
|
| 403 |
+
if res['sobra']:
|
| 404 |
+
story.append(PageBreak())
|
| 405 |
+
story.append(Paragraph(f"2. Pacote de Sobra ({res['sobra']['num']} pçs)", styles['Heading2']))
|
| 406 |
+
story.append(fig_to_img(plot_cross_section(res['sobra'])))
|
| 407 |
+
|
| 408 |
doc.build(story)
|
| 409 |
buffer.seek(0)
|
| 410 |
return buffer
|
|
|
|
| 415 |
m = res['main']
|
| 416 |
writer.writerow(["Tipo", "L", "A", "E", "Acab", "Qtd Pcts", "Pecas/Pct", "Fita Princ", "Fita Meio", "Qtd Madeira"])
|
| 417 |
acab = "GI/GA" if m['is_galv'] else "Normal"
|
| 418 |
+
# CORREÇÃO AQUI TAMBÉM: Usar fita_nome
|
| 419 |
+
writer.writerow([m['tipo'], m['l'], m['a'], m['e'], acab, res['total_pcts'], m['num'], m['fita_nome'], m['fita_meio_nome'], m['qtd_madeiras']])
|
| 420 |
return output.getvalue()
|
| 421 |
|
| 422 |
# ==========================================
|
| 423 |
# INTERFACE
|
| 424 |
# ==========================================
|
| 425 |
|
| 426 |
+
st.title("🏭 Calculadora Formadoras v6.0")
|
| 427 |
|
| 428 |
with st.sidebar:
|
| 429 |
st.header("Parâmetros")
|
|
|
|
| 431 |
acabamento = st.radio("Acabamento", ["Preto (Normal)", "Galvanizado (GI/GA)"])
|
| 432 |
|
| 433 |
c1, c2 = st.columns(2)
|
| 434 |
+
l = c1.number_input("Largura/Diâm (mm)", value=50.0, step=0.1)
|
| 435 |
+
a = c2.number_input("Altura (mm)", value=50.0, step=0.1, disabled=(tipo=="Redondo"))
|
| 436 |
+
e = c3.number_input("Espessura (mm)", value=2.00, step=0.05)
|
| 437 |
+
c = c4.number_input("Comp. (mm)", value=6000)
|
| 438 |
+
|
| 439 |
aba = st.number_input("Aba (U)", value=0.0, disabled=(tipo!="U/Enrijecido"))
|
| 440 |
|
| 441 |
st.markdown("---")
|