# encoding: UTF-8
# mk6tb — 6T 절곡 판재 필러 재생성 (벽두께 = 원부재 수평단면 그대로 = 6mm 균일)
#   원본 각 조각을 중간높이에서 수평 절단한 단면(절곡선 포함 6mm 폭 띠)을 평면윤곽으로 사용.
#   상단 = SWD_04 하면 실측(2mm), 하단 = 주황판 상면 z=106.5.
MEMBER = ENV['DZW_MEMBER'] || "SWD_02_t6_1000_jog30"
PREFIX = ENV['DZW_PREFIX'] || "SWD_02_jog30_6TB"
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_mk6tb_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
# 수평면 z=zc 로 자른 단면 세그먼트
def section_segs(faces, zc)
  segs = []
  faces.each do |pts, n, _|
    next if n.z.abs > 0.6
    hits = []
    j = pts.length - 1
    (0...pts.length).each do |i|
      a = pts[j]; b = pts[i]
      if (a.z - zc) * (b.z - zc) < 0
        t = (zc - a.z) / (b.z - a.z)
        hits << Geom::Point3d.new(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, zc)
      elsif (a.z - zc).abs < 1e-9
        hits << Geom::Point3d.new(a.x, a.y, zc)
      end
      j = i
    end
    hits.uniq! { |p| [(p.x * 10000).round, (p.y * 10000).round] }
    segs << [hits[0], hits[1]] if hits.length >= 2
  end
  segs
end
def chain(segs, tol)
  return [] if segs.empty?
  pool = segs.dup
  s = pool.shift
  loop_pts = [s[0], s[1]]
  changed = true
  while changed && !pool.empty?
    changed = false
    pool.each_with_index do |(a, b), i|
      if loop_pts[-1].distance(a) < tol
        loop_pts << b; pool.delete_at(i); changed = true; break
      elsif loop_pts[-1].distance(b) < tol
        loop_pts << a; pool.delete_at(i); changed = true; break
      elsif loop_pts[0].distance(a) < tol
        loop_pts.unshift(b); pool.delete_at(i); changed = true; break
      elsif loop_pts[0].distance(b) < tol
        loop_pts.unshift(a); pool.delete_at(i); changed = true; break
      end
    end
  end
  loop_pts.pop if loop_pts.length > 2 && loop_pts[0].distance(loop_pts[-1]) < tol
  [loop_pts, pool.length]
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)
    zs = fs.flat_map { |pts, _, _| pts.map { |p| p.z } }
    zc = (zs.min + zs.max) / 2.0
    segs = section_segs(fs, zc)
    lp, left = chain(segs, 0.05 / MM)
    if lp.length < 3
      out << "조각#{i + 1}: 단면 추출 실패(세그 #{segs.length})"
      foots << nil
      next
    end
    # 단면 폭(벽두께) = 2*면적/둘레 근사 대신, 긴변/짧은변 구조상 면적/중심선길이
    per = 0.0
    lp.each_with_index { |p, k| per += p.distance(lp[(k + 1) % lp.length]) }
    area = 0.0
    lp.each_with_index { |p, k| q = lp[(k + 1) % lp.length]; area += (p.x * q.y - q.x * p.y) }
    area = area.abs / 2.0 * 645.16
    per_mm = per * 25.4
    thick = (4.0 * area / per_mm).round(2)  # 가늘고 긴 띠: t ≈ 4A/P
    foots << lp
    out << "조각#{i + 1}: 단면 #{lp.length}점, 잔여세그 #{left}, 단면적 #{area.round(0)}mm2, 둘레 #{per_mm.round(1)}mm, 추정 벽두께 #{thick}mm"
  end
  y0 = 11050.0; y1 = 11850.0; zb = 106.5
  allp = foots.compact.flatten
  xmid = allp.map { |p| p.x }.inject(:+) / allp.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? } || foots.compact.empty?
    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 |lp, i|
        next if lp.nil?
        xs = lp.map { |p| p.x * 25.4 }
        prism = pents.add_group
        pf = prism.entities.add_face(lp.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)}) 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")
"MK6TB"
