# encoding: UTF-8
# mkw — 월드좌표 기준 6T 절곡 격판 1개 생성.
#   단면 = 원부재 수평단면(jog 단차 절곡 윤곽 그대로), 상단 = 주황판(SWD_04) 하면, 하단 = 흰판 상면(경사 6.445도).
MEMBER = "SWD_02_t6_1000_jog30"
NEWNAME = "SWD_02_jog30_6T_20260919"
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_mkw_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
def zs_down(faces, x, y)
  res = []
  faces.each do |pts, n, _|
    next if n.z > -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
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)
      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 [[], 0] if segs.empty?
  pool = segs.dup
  s = pool.shift
  lp = [s[0], s[1]]
  changed = true
  while changed && !pool.empty?
    changed = false
    pool.each_with_index do |(a, b), i|
      if lp[-1].distance(a) < tol then lp << b; pool.delete_at(i); changed = true; break
      elsif lp[-1].distance(b) < tol then lp << a; pool.delete_at(i); changed = true; break
      elsif lp[0].distance(a) < tol then lp.unshift(b); pool.delete_at(i); changed = true; break
      elsif lp[0].distance(b) < tol then lp.unshift(a); pool.delete_at(i); changed = true; break
      end
    end
  end
  lp.pop if lp.length > 2 && lp[0].distance(lp[-1]) < tol
  [lp, 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
won = m.entities.grep(Sketchup::Group).find { |g| g.name == "조립부원판_20260919" }
if src.nil? || s04.nil? || won.nil?
  out << "대상 없음 -> 중단"
  hb("ABORT")
else
  f4 = []; collect(s04, g2.transformation * s04.transformation, f4)
  fw = []; collect(won, won.transformation, fw)
  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 } }
    lp, = chain(section_segs(fs, (zs.min + zs.max) / 2.0), 0.05 / MM)
    next if lp.length < 3
    per = 0.0
    lp.each_with_index { |p, k| per += p.distance(lp[(k + 1) % lp.length]) }
    ar = 0.0
    lp.each_with_index { |p, k| q = lp[(k + 1) % lp.length]; ar += (p.x * q.y - q.x * p.y) }
    ar = ar.abs / 2.0 * 645.16
    out << "단면조각#{i + 1}: #{lp.length}점(절곡 #{lp.length / 2 - 1}회) 두께 #{(4.0 * ar / (per * 25.4)).round(2)}mm"
    foots << lp
  end
  if foots.empty?
    out << "단면 실패 -> 중단"
    hb("ABORT sec")
  else
    allp = foots.flatten
    xs = allp.map { |p| p.x * 25.4 }
    ys = allp.map { |p| p.y * 25.4 }
    xmid = (xs.min + xs.max) / 2.0
    # 측면 막음판 안쪽 면까지 길이 연장 (틈 0, 9T 침범 없음)
    ylo_t = 10879.34; yhi_t = 11872.77
    ymn = ys.min; ymx = ys.max
    foots = foots.map do |lp|
      lp.map do |q|
        yy = q.y * 25.4
        yy = ylo_t if yy <= ymn + 1.0
        yy = yhi_t if yy >= ymx - 1.0
        Geom::Point3d.new(q.x, yy / 25.4, q.z)
      end
    end
    allp = foots.flatten
    y0 = ylo_t; y1 = yhi_t
    out << "길이 연장: #{ymn.round(1)}..#{ymx.round(1)} -> #{y0}..#{y1} (측면판 안쪽면)"
    prof = []
    yy = y0
    while yy <= y1 + 0.001
      z = zs_down(f4, xmid / MM, 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 }
    last = nil
    (prof.length - 1).downto(0) { |i| if prof[i][1].nil? then prof[i][1] = last else last = prof[i][1] end }
    if prof.any? { |_, z| z.nil? }
      out << "상단 프로파일 전 구간 결측 -> 중단"
      hb("ABORT prof")
    else
      zt = prof.map { |_, z| z }
      out << "상단 프로파일 y #{y0.round(1)}..#{y1.round(1)}, z #{zt.min.round(2)}..#{zt.max.round(2)}"
      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')}_#{NEWNAME}직전.skp")
        m.save_copy(bak)
        out << "사전 백업 OK"
        hb("BACKUP OK")
        ents = m.entities
        # 프리즘 바닥: 흰판 두께 내부(하면 위, 상면 아래)에 두어야 subtract 후 잔재가 없다
        wtops = []; wbots = []
        allp.each do |p|
          zz = zs_at(fw, p.x, p.y)
          next if zz.length < 2
          wtops << zz.max * 25.4
          wbots << zz.min * 25.4
        end
        if wtops.empty?
          out << "흰판이 이 부재 범위에 없음 -> 중단"
          hb("ABORT nowhite")
          raise "흰판 범위 밖"
        end
        zbot = wbots.max + 1.0
        out << "흰판 상면 #{wtops.min.round(2)}..#{wtops.max.round(2)} / 하면 #{wbots.min.round(2)}..#{wbots.max.round(2)} -> 프리즘 바닥 #{zbot.round(2)}"
        if zbot >= wtops.min
          out << "흰판 두께 내 바닥 설정 불가 -> 중단"
          hb("ABORT zbot")
          raise "zbot 설정 불가"
        end
        m.start_operation("#{NEWNAME} 생성", true)
        prisms = []
        foots.each_with_index do |lp, i|
          g = ents.add_group
          f = g.entities.add_face(lp.map { |p| Geom::Point3d.new(p.x, p.y, zbot / MM) })
          f.reverse! if f.normal.z < 0
          f.pushpull((280.0 - zbot) / MM)
          hb("PRISM#{i} #{g.manifold?}")
          prisms << g
        end
        body = prisms.shift
        prisms.each_with_index do |g, i|
          u = body.union(g)
          hb("UNION#{i} #{u.class}")
          body = u if u
        end
        gb = ents.add_group
        xlo = xs.min - 60.0; xhi = xs.max + 60.0
        pl = [Geom::Point3d.new(xlo / MM, y0 / MM, zbot / MM), Geom::Point3d.new(xlo / MM, y1 / MM, zbot / 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 #{gb.manifold?}")
        body2 = body.intersect(gb)
        hb("TOPTRIM #{body2.class}")
        res = body2
        if body2
          wc = ents.add_group
          inst = wc.entities.add_instance(won.definition, won.transformation)
          inst.explode
          hb("WHITECOPY #{wc.manifold?}")
          if wc.manifold?
            body3 = wc.subtract(body2)   # SketchUp API: a.subtract(b) = b - a
            hb("BOTTRIM #{body3.class}")
            res = body3 if body3
          else
            wc.erase!
            out << "흰판 복제 솔리드 실패 -> 하단 트림 생략"
          end
        end
        if res
          res.name = NEWNAME
          b = res.bounds
          out << "생성: #{NEWNAME} 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=#{res.manifold?}"
        else
          out << "생성 실패"
        end
        src.hidden = true
        m.commit_operation
        hb("COMMIT")
      rescue Exception => e
        m.commit_operation rescue nil
        out << "ERR #{e.class}: #{e.message}"
        hb("ERR #{e.message}")
      end
    end
  end
end
puts out.join("\n")
hb("END")
"MKW"
