GabrielGD commited on
Commit
8ea69c3
·
verified ·
1 Parent(s): a074f68

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -54
app.py CHANGED
@@ -6,16 +6,15 @@ import matplotlib.patches as patches
6
  import io
7
  import csv
8
  import os
9
- from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, Table, TableStyle, PageBreak, KeepTogether
10
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
11
  from reportlab.lib import colors
12
  from reportlab.lib.pagesizes import letter
13
- from reportlab.lib.units import mm
14
 
15
  # ==========================================
16
  # CONFIGURAÇÃO GERAL
17
  # ==========================================
18
- st.set_page_config(page_title="SGP - Gestão de Embalagem v14.0", layout="wide", page_icon="🏭")
19
 
20
  PESO_IDEAL_MIN = 970
21
  PESO_IDEAL_MAX = 1080
@@ -121,10 +120,17 @@ class PackageCalculator:
121
  if catalog_item:
122
  origem = "Catálogo (Norma)"
123
  num_pecas = catalog_item['qtde']
 
 
 
 
 
 
 
124
  if catalog_item.get('sextavado') and 'padrao' in catalog_item:
125
  layout = self._parse_layout(catalog_item['padrao'])
126
  is_sext, gl, ga = True, 0, len(layout)
127
- elif tipo == "Redondo":
128
  # Conversão Grade->Ninho
129
  bw = catalog_item.get('ml', 0) or int(math.sqrt(num_pecas))
130
  layout, rem, cw = [], num_pecas, bw
@@ -134,6 +140,7 @@ class PackageCalculator:
134
  cw = bw-1 if cw==bw else bw
135
  is_sext, gl, ga = True, 0, len(layout)
136
  else:
 
137
  is_sext, gl, ga = False, catalog_item.get('ml',0), catalog_item.get('ma',0)
138
  else:
139
  origem = "Engenharia (Cálculo)"
@@ -161,7 +168,7 @@ class PackageCalculator:
161
 
162
  # Montagem Pacotes
163
  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)
164
- main['catalog_data'] = catalog_item # Salva dados do catalogo para o PDF
165
  main['peso_metro'] = peso_metro
166
  main['area_secao'] = area
167
 
@@ -197,13 +204,19 @@ class PackageCalculator:
197
  info["layout_str"] = "-".join(map(str, r)) if r else ""
198
  else:
199
  info["larg_total"] = gl*l + (aba if tipo=="Enrijecido" else 0)
 
 
 
 
 
 
 
200
  if is_galv:
201
  cm = max(0, ga-1)
202
- hb = (l + (ga-1)*l*math.sin(math.radians(60))) if tipo=="Redondo" else ga*a
203
- info["alt_total"] = hb + cm*wood_h
204
  info["qtd_madeiras"] = cm * 5
205
  else:
206
- info["alt_total"] = (l + (ga-1)*l*math.sin(math.radians(60))) if tipo=="Redondo" else ga*a
207
  info["qtd_madeiras"] = 0
208
  info["layout_str"] = ""
209
 
@@ -224,7 +237,6 @@ class PackageCalculator:
224
  # ==========================================
225
 
226
  def plot_single_profile(info):
227
- """Desenha um único perfil com cotas para o PDF"""
228
  fig, ax = plt.subplots(figsize=(4, 4))
229
  l, a, e = info['l'], info['a'], info['e']
230
  tipo = info['tipo']
@@ -232,25 +244,18 @@ def plot_single_profile(info):
232
  if tipo == "Redondo":
233
  ax.add_patch(patches.Circle((l/2, l/2), l/2, fc='lightgray', ec='black', lw=2))
234
  ax.add_patch(patches.Circle((l/2, l/2), l/2-e, fc='white', ec='black', lw=1))
235
- # Cota Diametro
236
  ax.annotate(f"Ø {l:.2f}", xy=(l/2, -l*0.1), xytext=(l/2, -l*0.2), ha='center', arrowprops=dict(arrowstyle='<->'))
237
- # Cota Espessura
238
  ax.annotate(f"e={e:.2f}", xy=(l/2, l-e), xytext=(l*1.2, l), arrowprops=dict(arrowstyle='->'))
239
  ax.set_xlim(-l*0.2, l*1.2); ax.set_ylim(-l*0.2, l*1.2)
240
-
241
- else: # Quadrado/Ret
242
  ax.add_patch(patches.Rectangle((0,0), l, a, fc='lightgray', ec='black', lw=2))
243
  ax.add_patch(patches.Rectangle((e,e), l-2*e, a-2*e, fc='white', ec='black', lw=1))
244
- # Cota L
245
  ax.annotate(f"L={l:.2f}", xy=(l/2, -a*0.1), xytext=(l/2, -a*0.2), ha='center', arrowprops=dict(arrowstyle='<->'))
246
- # Cota A
247
  ax.annotate(f"A={a:.2f}", xy=(-l*0.1, a/2), xytext=(-l*0.2, a/2), va='center', rotation=90, arrowprops=dict(arrowstyle='<->'))
248
- # Cota E
249
  ax.annotate(f"e={e:.2f}", xy=(l-e/2, a/2), xytext=(l+l*0.2, a/2), arrowprops=dict(arrowstyle='->'))
250
  ax.set_xlim(-l*0.3, l*1.3); ax.set_ylim(-a*0.3, a*1.3)
251
 
252
- ax.set_aspect('equal')
253
- ax.axis('off')
254
  plt.title(f"Perfil Individual ({tipo})", fontsize=10)
255
  return fig
256
 
@@ -283,24 +288,29 @@ def plot_cross_section(info):
283
  drawn = 0
284
  rad = l/2; dy = l*0.866
285
  for i in range(ga):
286
- if info['tipo']=="Redondo": y = rad + i*dy + i*wood_h
287
- else: y = i*(a+wood_h)
 
 
 
 
 
288
 
289
  if is_galv and i>0:
290
- wy = y-rad-wood_h if info['tipo']=="Redondo" else y-wood_h
291
  wx = (total_w - wood_len)/2
292
  ax.add_patch(patches.Rectangle((wx, wy), wood_len, wood_h, fc='peru', ec='brown'))
293
- tube_y = y+wood_h if info['tipo']!="Redondo" else y
294
- else: tube_y = y
295
 
296
  for j in range(gl):
297
  if drawn >= info['num']: break
 
 
298
  if info['tipo']=="Redondo":
299
- x = j*l if i%2==0 else j*l+rad
300
- ax.add_patch(patches.Circle((x+rad, y), rad, fc=fc, ec=ec))
301
- ax.add_patch(patches.Circle((x+rad, y), rad-e, fc='white', ec=ec, lw=0.5))
 
302
  else:
303
- x = j*l
304
  ax.add_patch(patches.Rectangle((x, tube_y), l, a, fc=fc, ec=ec))
305
  ax.add_patch(patches.Rectangle((x+e, tube_y+e), l-2*e, a-2*e, fc='white', ec=ec, lw=0.5))
306
  drawn+=1
@@ -309,7 +319,6 @@ def plot_cross_section(info):
309
  margin = max(total_w, info['alt_total']) * 0.15
310
  ax.add_patch(patches.Rectangle((-2, -2), total_w+4, info['alt_total']+4, fill=False, ec='red', ls='--', lw=1))
311
 
312
- # Texto Grande
313
  if not info['sext']:
314
  ax.text(total_w/2, info['alt_total']+margin*0.2, f"BASE: {info['gl']}", ha='center', color='blue', weight='bold', fontsize=12)
315
  ax.text(-margin*0.2, info['alt_total']/2, f"ALT: {info['ga']}", ha='right', va='center', color='blue', weight='bold', rotation=90, fontsize=12)
@@ -330,8 +339,14 @@ def plot_side_view(info):
330
  if info['is_galv']:
331
  lays = info['ga'] if not info['sext'] else len(info['layout'])
332
  for i in range(1, lays):
333
- y = i*(info['l']*0.866 + info['wood_h']) if info['sext'] else i*(info['a']+info['wood_h'])
334
- y -= info['wood_h']
 
 
 
 
 
 
335
  for p in [0.1, 0.3, 0.5, 0.7, 0.9]:
336
  ax.add_patch(patches.Rectangle((c*p-24, y), 48, info['wood_h'], fc='peru'))
337
 
@@ -352,7 +367,7 @@ def fig_to_img(fig, w=400, h=300):
352
  return RLImage(buf, width=w, height=h, kind='proportional')
353
 
354
  # ==========================================
355
- # GERADOR DE PDF DETALHADO
356
  # ==========================================
357
 
358
  def generate_pdf(res):
@@ -362,16 +377,19 @@ def generate_pdf(res):
362
  story = []
363
  m = res['main']
364
 
365
- # Cabeçalho
366
  story.append(Paragraph(f"FICHA TÉCNICA DE PRODUÇÃO", styles['Title']))
367
  story.append(Spacer(1, 12))
368
 
369
- # 1. Dados do Produto (Compara Pedido vs Norma)
370
  catalog_data = m.get('catalog_data')
371
- if catalog_data:
372
- cat_desc = f"Norma IT{13 if m['tipo']=='Redondo' else 14}: Qtde {catalog_data['qtde']} | Peso {catalog_data['peso']}kg"
373
- else:
374
- cat_desc = "Não catalogado (Cálculo Engenharia)"
 
 
 
 
 
375
 
376
  tbl_data = [
377
  ["Parâmetro", "Valor Solicitado", "Dados de Referência"],
@@ -381,34 +399,27 @@ def generate_pdf(res):
381
  ]
382
 
383
  t1 = Table(tbl_data, colWidths=[120, 180, 220])
384
- t1.setStyle(TableStyle([
385
- ('GRID', (0,0), (-1,-1), 1, colors.black),
386
- ('BACKGROUND', (0,0), (0,0), colors.lightgrey),
387
- ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
388
- ('ALIGN', (0,0), (-1,-1), 'LEFT')
389
- ]))
390
  story.append(t1)
391
  story.append(Spacer(1, 15))
392
 
393
- # 2. Dados Técnicos Individuais (Novo)
394
  story.append(Paragraph("Especificações do Tubo Individual", styles['Heading2']))
395
 
396
- # Cria duas colunas: Texto e Desenho do Tubo
397
  tech_data = [
398
  ["Peso Teórico:", f"{m.get('peso_metro', 0):.3f} kg/m"],
399
  ["Área Seção:", f"{m.get('area_secao', 0):.2f} mm²"],
400
  ["Peso Total Peça:", f"{(m['peso']/m['num']):.3f} kg"]
401
  ]
402
 
403
- # Desenho Individual
404
- img_single = fig_to_img(plot_single_profile(m), w=150, h=150)
 
405
 
406
- # Tabela de layout (Texto esquerda, Imagem direita)
407
- t_tech = Table([[Table(tech_data, style=t1.getStyle()), img_single]], colWidths=[300, 200])
408
  story.append(t_tech)
409
  story.append(Spacer(1, 15))
410
 
411
- # 3. Dados do Pacote (Embalagem)
412
  story.append(Paragraph(f"Configuração do Pacote ({m['num']} Peças)", styles['Heading2']))
413
 
414
  pack_data = [
@@ -423,12 +434,10 @@ def generate_pdf(res):
423
  story.append(t_pack)
424
  story.append(Spacer(1, 10))
425
 
426
- # Imagens do Pacote
427
  story.append(fig_to_img(plot_cross_section(m), w=400, h=300))
428
  story.append(Spacer(1, 5))
429
  story.append(fig_to_img(plot_side_view(m), w=400, h=120))
430
 
431
- # 4. Sobra
432
  if res['sobra']:
433
  story.append(PageBreak())
434
  story.append(Paragraph(f"Detalhe do Pacote de Sobra ({res['sobra']['num']} peças)", styles['Heading2']))
@@ -441,7 +450,7 @@ def generate_pdf(res):
441
  # ==========================================
442
  # INTERFACE
443
  # ==========================================
444
- st.title("🏭 SGP - Sistema de Gestão de Produção v14.0")
445
 
446
  with st.sidebar:
447
  st.header("Parâmetros")
 
6
  import io
7
  import csv
8
  import os
9
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, Table, TableStyle, PageBreak
10
+ from reportlab.lib.styles import getSampleStyleSheet
11
  from reportlab.lib import colors
12
  from reportlab.lib.pagesizes import letter
 
13
 
14
  # ==========================================
15
  # CONFIGURAÇÃO GERAL
16
  # ==========================================
17
+ st.set_page_config(page_title="SGP - Gestão de Embalagem v14.1", layout="wide", page_icon="🏭")
18
 
19
  PESO_IDEAL_MIN = 970
20
  PESO_IDEAL_MAX = 1080
 
120
  if catalog_item:
121
  origem = "Catálogo (Norma)"
122
  num_pecas = catalog_item['qtde']
123
+
124
+ # Verifica se é Grade Reta (IT13/14 com ml e ma definidos e coerentes)
125
+ is_grid_rect = False
126
+ if catalog_item.get('ml') and catalog_item.get('ma'):
127
+ if catalog_item['ml'] * catalog_item['ma'] == num_pecas:
128
+ is_grid_rect = True
129
+
130
  if catalog_item.get('sextavado') and 'padrao' in catalog_item:
131
  layout = self._parse_layout(catalog_item['padrao'])
132
  is_sext, gl, ga = True, 0, len(layout)
133
+ elif tipo == "Redondo" and not is_grid_rect:
134
  # Conversão Grade->Ninho
135
  bw = catalog_item.get('ml', 0) or int(math.sqrt(num_pecas))
136
  layout, rem, cw = [], num_pecas, bw
 
140
  cw = bw-1 if cw==bw else bw
141
  is_sext, gl, ga = True, 0, len(layout)
142
  else:
143
+ # Grade Reta (Metalon ou Redondo "quadrado")
144
  is_sext, gl, ga = False, catalog_item.get('ml',0), catalog_item.get('ma',0)
145
  else:
146
  origem = "Engenharia (Cálculo)"
 
168
 
169
  # Montagem Pacotes
170
  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)
171
+ main['catalog_data'] = catalog_item
172
  main['peso_metro'] = peso_metro
173
  main['area_secao'] = area
174
 
 
204
  info["layout_str"] = "-".join(map(str, r)) if r else ""
205
  else:
206
  info["larg_total"] = gl*l + (aba if tipo=="Enrijecido" else 0)
207
+
208
+ # Altura Base
209
+ if tipo == "Redondo":
210
+ h_base = ga * l
211
+ else:
212
+ h_base = ga * a
213
+
214
  if is_galv:
215
  cm = max(0, ga-1)
216
+ info["alt_total"] = h_base + cm*wood_h
 
217
  info["qtd_madeiras"] = cm * 5
218
  else:
219
+ info["alt_total"] = h_base
220
  info["qtd_madeiras"] = 0
221
  info["layout_str"] = ""
222
 
 
237
  # ==========================================
238
 
239
  def plot_single_profile(info):
 
240
  fig, ax = plt.subplots(figsize=(4, 4))
241
  l, a, e = info['l'], info['a'], info['e']
242
  tipo = info['tipo']
 
244
  if tipo == "Redondo":
245
  ax.add_patch(patches.Circle((l/2, l/2), l/2, fc='lightgray', ec='black', lw=2))
246
  ax.add_patch(patches.Circle((l/2, l/2), l/2-e, fc='white', ec='black', lw=1))
 
247
  ax.annotate(f"Ø {l:.2f}", xy=(l/2, -l*0.1), xytext=(l/2, -l*0.2), ha='center', arrowprops=dict(arrowstyle='<->'))
 
248
  ax.annotate(f"e={e:.2f}", xy=(l/2, l-e), xytext=(l*1.2, l), arrowprops=dict(arrowstyle='->'))
249
  ax.set_xlim(-l*0.2, l*1.2); ax.set_ylim(-l*0.2, l*1.2)
250
+ else:
 
251
  ax.add_patch(patches.Rectangle((0,0), l, a, fc='lightgray', ec='black', lw=2))
252
  ax.add_patch(patches.Rectangle((e,e), l-2*e, a-2*e, fc='white', ec='black', lw=1))
 
253
  ax.annotate(f"L={l:.2f}", xy=(l/2, -a*0.1), xytext=(l/2, -a*0.2), ha='center', arrowprops=dict(arrowstyle='<->'))
 
254
  ax.annotate(f"A={a:.2f}", xy=(-l*0.1, a/2), xytext=(-l*0.2, a/2), va='center', rotation=90, arrowprops=dict(arrowstyle='<->'))
 
255
  ax.annotate(f"e={e:.2f}", xy=(l-e/2, a/2), xytext=(l+l*0.2, a/2), arrowprops=dict(arrowstyle='->'))
256
  ax.set_xlim(-l*0.3, l*1.3); ax.set_ylim(-a*0.3, a*1.3)
257
 
258
+ ax.set_aspect('equal'); ax.axis('off')
 
259
  plt.title(f"Perfil Individual ({tipo})", fontsize=10)
260
  return fig
261
 
 
288
  drawn = 0
289
  rad = l/2; dy = l*0.866
290
  for i in range(ga):
291
+ if info['tipo']=="Redondo":
292
+ # Grade Redonda (Alinhada)
293
+ y = i*(l+wood_h)
294
+ cy = y + l/2
295
+ else:
296
+ y = i*(a+wood_h)
297
+ tube_y = y
298
 
299
  if is_galv and i>0:
300
+ wy = y-wood_h
301
  wx = (total_w - wood_len)/2
302
  ax.add_patch(patches.Rectangle((wx, wy), wood_len, wood_h, fc='peru', ec='brown'))
 
 
303
 
304
  for j in range(gl):
305
  if drawn >= info['num']: break
306
+ x = j*l
307
+
308
  if info['tipo']=="Redondo":
309
+ # Grade Redonda
310
+ cx = x + rad
311
+ ax.add_patch(patches.Circle((cx, cy), rad, fc=fc, ec=ec))
312
+ ax.add_patch(patches.Circle((cx, cy), rad-e, fc='white', ec=ec, lw=0.5))
313
  else:
 
314
  ax.add_patch(patches.Rectangle((x, tube_y), l, a, fc=fc, ec=ec))
315
  ax.add_patch(patches.Rectangle((x+e, tube_y+e), l-2*e, a-2*e, fc='white', ec=ec, lw=0.5))
316
  drawn+=1
 
319
  margin = max(total_w, info['alt_total']) * 0.15
320
  ax.add_patch(patches.Rectangle((-2, -2), total_w+4, info['alt_total']+4, fill=False, ec='red', ls='--', lw=1))
321
 
 
322
  if not info['sext']:
323
  ax.text(total_w/2, info['alt_total']+margin*0.2, f"BASE: {info['gl']}", ha='center', color='blue', weight='bold', fontsize=12)
324
  ax.text(-margin*0.2, info['alt_total']/2, f"ALT: {info['ga']}", ha='right', va='center', color='blue', weight='bold', rotation=90, fontsize=12)
 
339
  if info['is_galv']:
340
  lays = info['ga'] if not info['sext'] else len(info['layout'])
341
  for i in range(1, lays):
342
+ # Cálculo Y da madeira na vista lateral
343
+ if info['sext']:
344
+ y = i*(info['l']*0.866 + info['wood_h']) - info['wood_h'] # Aprox
345
+ else:
346
+ y = i*(info['a'] + info['wood_h']) - info['wood_h']
347
+ if info['tipo'] == "Redondo":
348
+ y = i*(info['l'] + info['wood_h']) - info['wood_h']
349
+
350
  for p in [0.1, 0.3, 0.5, 0.7, 0.9]:
351
  ax.add_patch(patches.Rectangle((c*p-24, y), 48, info['wood_h'], fc='peru'))
352
 
 
367
  return RLImage(buf, width=w, height=h, kind='proportional')
368
 
369
  # ==========================================
370
+ # GERADOR DE PDF DETALHADO (CORRIGIDO)
371
  # ==========================================
372
 
373
  def generate_pdf(res):
 
377
  story = []
378
  m = res['main']
379
 
 
380
  story.append(Paragraph(f"FICHA TÉCNICA DE PRODUÇÃO", styles['Title']))
381
  story.append(Spacer(1, 12))
382
 
 
383
  catalog_data = m.get('catalog_data')
384
+ cat_desc = f"Norma IT{13 if m['tipo']=='Redondo' else 14}: Qtde {catalog_data['qtde']} | Peso {catalog_data['peso']}kg" if catalog_data else "Não catalogado (Cálculo Engenharia)"
385
+
386
+ # Definindo estilo explicitamente para evitar o erro getStyle()
387
+ t_style = TableStyle([
388
+ ('GRID', (0,0), (-1,-1), 1, colors.black),
389
+ ('BACKGROUND', (0,0), (0,0), colors.lightgrey),
390
+ ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
391
+ ('ALIGN', (0,0), (-1,-1), 'LEFT')
392
+ ])
393
 
394
  tbl_data = [
395
  ["Parâmetro", "Valor Solicitado", "Dados de Referência"],
 
399
  ]
400
 
401
  t1 = Table(tbl_data, colWidths=[120, 180, 220])
402
+ t1.setStyle(t_style)
 
 
 
 
 
403
  story.append(t1)
404
  story.append(Spacer(1, 15))
405
 
 
406
  story.append(Paragraph("Especificações do Tubo Individual", styles['Heading2']))
407
 
 
408
  tech_data = [
409
  ["Peso Teórico:", f"{m.get('peso_metro', 0):.3f} kg/m"],
410
  ["Área Seção:", f"{m.get('area_secao', 0):.2f} mm²"],
411
  ["Peso Total Peça:", f"{(m['peso']/m['num']):.3f} kg"]
412
  ]
413
 
414
+ # Nested Table com o estilo definido
415
+ t_nested = Table(tech_data)
416
+ t_nested.setStyle(t_style)
417
 
418
+ img_single = fig_to_img(plot_single_profile(m), w=150, h=150)
419
+ t_tech = Table([[t_nested, img_single]], colWidths=[300, 200])
420
  story.append(t_tech)
421
  story.append(Spacer(1, 15))
422
 
 
423
  story.append(Paragraph(f"Configuração do Pacote ({m['num']} Peças)", styles['Heading2']))
424
 
425
  pack_data = [
 
434
  story.append(t_pack)
435
  story.append(Spacer(1, 10))
436
 
 
437
  story.append(fig_to_img(plot_cross_section(m), w=400, h=300))
438
  story.append(Spacer(1, 5))
439
  story.append(fig_to_img(plot_side_view(m), w=400, h=120))
440
 
 
441
  if res['sobra']:
442
  story.append(PageBreak())
443
  story.append(Paragraph(f"Detalhe do Pacote de Sobra ({res['sobra']['num']} peças)", styles['Heading2']))
 
450
  # ==========================================
451
  # INTERFACE
452
  # ==========================================
453
+ st.title("🏭 SGP - Sistema de Gestão de Produção v14.1")
454
 
455
  with st.sidebar:
456
  st.header("Parâmetros")