# encoding: UTF-8
# mk6t30 — jog30 필러를 6T 절곡 판재(벽두께 6mm, 속 빈 판)로 재생성.
#   원본 각 조각의 "밑면(판 두께 6mm 폭의 좁은 면)" 평면윤곽을 그대로 쓰므로 벽두께 6mm·절곡선 유지.
#   상단은 SWD_04 하면 실측 프로파일(2mm 간격), 하단은 주황판 상면 z=106.5.
MEMBER = "SWD_02_t6_1000_jog30"
PREFIX = "SWD_02_jog30_6T"
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_mk6t30_heartbeat.txt'
def hb(s)
  begin
    File.open(HB, 'a:UTF-8') { |f| f.write("[#{Time.now.strftime('%H:%M:%S')}] #{s}\n"); f.flush }
  rescue Exception
    nil
  end
end
MM = 25.4
def r2(v); (v * 25.4).round(2); end
def ents_of(e); e.is_a?(Sketchup::ComponentInstance) ? e.definition.entities : e.entities; end
def collect(ent, tr, faces)
  ents_of(ent).each do |e|
    case e
    when Sketchup::Face
      pts = e.vertices.map { |v| v.position.transform(tr) }
      n = e.normal.transform(tr); n.normalize!
      faces << [pts, n, e.area * 645.16]
    when Sketchup::Group, Sketchup::ComponentInstance
      collect(e, tr * e.transformation, faces)
    end
  end
end
def zs_at(faces, x, y)
  res = []
  faces.each do |pts, n, _|
    next if n.z.abs < 0.05
    inside = false
    j = pts.length - 1
    (0...pts.length).each do |i|
      xi = pts[i].x; yi = pts[i].y; xj = pts[j].x; yj = pts[j].y
      inside = !inside if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)
      j = i
    end
    next unless inside
    p0 = pts[0]
    d = n.x * (x - p0.x) + n.y * (y - p0.y)
    res << (p0.z - d / n.z)
  end
  res
end

m = Sketchup.active_model
out = []
hb("START #{MEMBER}")
g2 = m.entities.grep(Sketchup::Group).find { |g| g.definition.name == "그룹#2" }
src = g2 ? g2.entities.grep(Sketchup::Group).find { |g| g.name == MEMBER } : nil
s04 = g2 ? g2.entities.grep(Sketchup::Group).find { |g| g.name == "SWD_04_t9_240x1046" } : nil
if src.nil? || s04.nil?
  out << "대상 없음 -> 중단"
  hb("ABORT")
else
  f4 = []
  collect(s04, g2.transformation * s04.transformation, f4)
  subs = src.definition.entities.grep(Sketchup::Group)
  foots = []
  subs.each_with_index do |c, i|
    fs = []
    collect(c, g2.transformation * src.transformation * c.transformation, fs)
    bot = fs.select { |_, n, _| n.z < -0.5 }.max_by { |_, _, a| a }
    if bot.nil?
      out << "조각#{i}: 밑면 없음"
      foots << nil
      next
    end
    pts = bot[0]
    xs = pts.map { |p| p.x * 25.4 }; ys = pts.map { |p| p.y * 25.4 }
    foots << pts
    out << "조각#{i + 1} 밑면 #{pts.length}점 면적 #{bot[2].round(0)}mm2 plan x #{xs.min.round(1)}..#{xs.max.round(1)} y #{ys.min.round(1)}..#{ys.max.round(1)}"
  end
  y0 = 11050.0; y1 = 11850.0; zb = 106.5
  allpts = foots.compact.flatten
  xmid = allpts.map { |p| p.x }.inject(:+) / allpts.length
  prof = []
  yy = y0
  while yy <= y1 + 0.001
    z = zs_at(f4, xmid, yy / MM)
    prof << [yy, z.empty? ? nil : z.min * 25.4]
    yy += 2.0
  end
  last = nil
  prof.each_with_index { |(y, z), i| if z.nil? then prof[i][1] = last else last = z end }
  if prof.any? { |_, z| z.nil? }
    out << "프로파일 결측 -> 중단"
    hb("ABORT miss")
  else
    begin
      base = File.basename(m.path, '.skp'); dir = File.dirname(m.path)
      bak = File.join(dir, "#{base}.bak_#{Time.now.strftime('%Y%m%d_%H%M%S')}_#{PREFIX}직전.skp")
      m.save_copy(bak)
      out << "사전 백업 OK -> #{bak}"
      hb("BACKUP OK")
      pents = g2.entities
      made = []
      m.start_operation("#{PREFIX} 6T 절곡 필러 생성", true)
      foots.each_with_index do |pts, i|
        next if pts.nil?
        xs = pts.map { |p| p.x * 25.4 }
        prism = pents.add_group
        pf = prism.entities.add_face(pts.map { |p| Geom::Point3d.new(p.x, p.y, zb / MM) })
        pf.reverse! if pf.normal.z < 0
        pf.pushpull((260.0 - zb) / MM)
        hb("PRISM#{i} solid=#{prism.manifold?}")
        gb = pents.add_group
        xlo = xs.min - 30.0; xhi = xs.max + 30.0
        pl = [Geom::Point3d.new(xlo / MM, y0 / MM, zb / MM), Geom::Point3d.new(xlo / MM, y1 / MM, zb / MM)]
        prof.reverse.each { |y, z| pl << Geom::Point3d.new(xlo / MM, y / MM, z / MM) }
        gf = gb.entities.add_face(pl)
        gf.pushpull(gf.normal.x > 0 ? (xhi - xlo) / MM : -(xhi - xlo) / MM)
        hb("GAP#{i} solid=#{gb.manifold?}")
        if !prism.manifold? || !gb.manifold?
          prism.erase!; gb.erase!
          out << "조각#{i + 1}: 솔리드 실패 -> 건너뜀"
          next
        end
        res = prism.intersect(gb)
        hb("INT#{i} #{res.class}")
        if res
          res.name = "#{PREFIX}_#{i + 1}_20260919"
          made << res
        end
      end
      src.hidden = true
      m.commit_operation
      hb("COMMIT made=#{made.length}")
      out << "생성 #{made.length}개 (원본 hidden)"
      made.each do |gp|
        b = gp.bounds
        out << "  #{gp.name} bbox (#{r2(b.min.x)},#{r2(b.min.y)},#{r2(b.min.z)})..(#{r2(b.max.x)},#{r2(b.max.y)},#{r2(b.max.z)}) 평면두께 #{[r2(b.max.x) - r2(b.min.x), r2(b.max.y) - r2(b.min.y)].min.round(2)}mm solid=#{gp.manifold?}"
      end
    rescue Exception => e
      m.commit_operation rescue nil
      out << "ERR #{e.class}: #{e.message}"
      hb("ERR #{e.message}")
    end
  end
end
puts out.join("\n")
hb("END")
"MK6T"
