GabrielGD commited on
Commit
bc0c97c
·
verified ·
1 Parent(s): 969aa43

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -111
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 v5.0", layout="wide")
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% para o esticador pneumático e selo TR (igual código antigo)
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 (>4m)", perimetro_com_margem, perimetro_com_margem
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 # Largura da madeira (visual)
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 # Uma base + entre camadas
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 # 1 camada de madeira para cada camada de tubo
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
- # --- CÁLCULO DE FITAS ---
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
- # 2. Fita do Meio (Só para normal)
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, wood_w = info['wood_h'], info['wood_w']
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
- # Hexagonal
250
  rows = info['layout']
251
- r = l / 2
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
- # Y considera madeira acumulada
259
- y_base = r + i * dy + (i * wood_h)
260
 
261
- # Desenhar 5 madeiras abaixo da linha (se galv)
262
  if is_galv:
263
- # Distribuir 5 madeiras na largura do pacote
264
- # Y da madeira é logo abaixo da linha de tubos
265
- wy = y_base - r - wood_h
266
- espaco = total_w / 5
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 + r
273
- ax.add_patch(patches.Circle((cx, y_base), r, fc=fc, ec=ec))
274
- ax.add_patch(patches.Circle((cx, y_base), r-e, fc='white', ec=ec, lw=0.5))
275
  else:
276
- # Grade
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 (Abaixo da camada)
283
- if is_galv:
284
- wy = y - wood_h if i > 0 else y # Na base desenha em 0? Vamos assumir que Y começa deslocado
285
- # Ajuste: Vamos desenhar a madeira em y, e o tubo em y + wood_h
286
- # Correção visual:
287
- # Camada 0: Tubo em 0? Se tiver madeira na base, tubo sobe.
288
- # O User disse: "começa na primeira fileira".
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 e Fitas
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
- # Cinta Principal
331
- off = 5
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
- # Legenda madeira dummy
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
- positions = [0.1, 0.3, 0.5, 0.7, 0.9]
364
- for idx, p in enumerate(positions):
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='--') # Fita meio até metade
368
  else:
369
- ax.plot([x, x], [0, h], color='purple', lw=3) # Fita completa
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
- # Detalhes Fitas e Madeira
392
- fita_txt = f"4x {m['fita']} ({m['fita_tamanho']}m)"
 
 
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['fita']} ({m['fita_tamanho']}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", "Galvanizado" if m['is_galv'] else "Preto"],
403
- ["Total Produção", f"{res['total_pcts']} pacotes"],
 
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
- # Imagens
414
- buf1 = io.BytesIO()
415
- plot_cross_section(m).savefig(buf1, format='png', dpi=100, bbox_inches='tight')
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
- writer.writerow([m['tipo'], m['l'], m['a'], m['e'], acab, res['total_pcts'], m['num'], m['fita'], m['fita_meio_nome'], m['qtd_madeiras']])
 
435
  return output.getvalue()
436
 
437
  # ==========================================
438
  # INTERFACE
439
  # ==========================================
440
 
441
- st.title("🏭 Calculadora Formadoras v5.0")
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 = st.number_input("Espessura (mm)", value=2.00)
452
- c = st.number_input("Comprimento (mm)", value=6000)
 
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("---")