# encoding: UTF-8
# mk_jog30 — jog30 자리에 3구간 상단 프로파일(=SWD_04 하면 실형상) 필러 솔리드 3개 신규 생성.
#   원본 SWD_03_t6_1000_jog20 은 hidden 처리(형상 보존).
#   상단선: SWD_04 하면을 2mm 간격 실측한 폴리라인 -> 상승(22.4도) / 일정 / 하강 3구간이 그대로 반영됨.
#   하단: 주황판 상면 z=106.5 평면.
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_mk20_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
def r1(v); (v * 25.4).round(2); end
MM = 25.4

def ents_of(e); e.is_a?(Sketchup::ComponentInstance) ? e.definition.entities : e.entities; end
def collect(ent, tr, faces, verts)
  ents_of(ent).each do |e|
    case e
    when Sketchup::Face
      pts = e.vertices.map { |v| v.position.transform(tr) }
      pts.each { |p| verts << p } if verts
      n = e.normal.transform(tr); n.normalize!
      faces << [pts, n] if faces && n.z.abs > 0.05
    when Sketchup::Group, Sketchup::ComponentInstance
      collect(e, tr * e.transformation, faces, verts)
    end
  end
end
def zs_at(faces, x, y)
  res = []
  faces.each do |pts, n|
    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) if n.z.abs > 1e-9
  end
  res
end
def hull(pts2d)
  pts = pts2d.uniq { |p| [(p[0] * 1000).round, (p[1] * 1000).round] }.sort
  return pts if pts.length < 3
  cross = lambda { |o, a, b| (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) }
  lower = []
  pts.each { |p| lower.pop while lower.length >= 2 && cross.call(lower[-2], lower[-1], p) <= 0; lower << p }
  upper = []
  pts.reverse.each { |p| upper.pop while upper.length >= 2 && cross.call(upper[-2], upper[-1], p) <= 0; upper << p }
  (lower[0...-1] + upper[0...-1])
end

m = Sketchup.active_model
out = []
hb("START")
g2 = m.entities.grep(Sketchup::Group).find { |g| g.definition.name == "그룹#2" }
s02 = g2 ? g2.entities.grep(Sketchup::Group).find { |g| g.name == "SWD_03_t6_1000_jog20" } : nil
s04 = g2 ? g2.entities.grep(Sketchup::Group).find { |g| g.name == "SWD_04_t9_240x1046" } : nil
if s02.nil? || s04.nil?
  out << "대상 없음(jog30=#{!s02.nil?} SWD_04=#{!s04.nil?}) -> 중단"
  hb("ABORT")
else
  f4 = []
  collect(s04, g2.transformation * s04.transformation, f4, nil)
  subs = s02.definition.entities.grep(Sketchup::Group)
  out << "jog20 하위 솔리드 #{subs.length}개"
  foots = []
  subs.each do |c|
    vs = []
    collect(c, g2.transformation * s02.transformation * c.transformation, nil, vs)
    h = hull(vs.map { |p| [p.x, p.y] })
    xs = vs.map { |p| p.x * 25.4 }; ys = vs.map { |p| p.y * 25.4 }
    foots << h
    out << "  조각 평면윤곽 #{h.length}점  x #{xs.min.round(1)}..#{xs.max.round(1)} y #{ys.min.round(1)}..#{ys.max.round(1)}"
  end
  xmid = foots.flatten(1).map { |p| p[0] }.inject(:+) / foots.flatten(1).length
  # 상단 프로파일 실측 (2mm 간격)
  y0 = 11050.0; y1 = 11850.0; zb = 106.5
  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
  filled = prof.map { |y, z| [y, z] }
  last = nil
  filled.each_with_index { |(y, z), i| if z.nil? then filled[i][1] = last else last = z end }
  filled.reverse.each_with_index { |(y, z), i| }
  miss = filled.count { |_, z| z.nil? }
  out << "상단 프로파일 #{filled.length}점 (결측 #{miss}) z #{filled.map { |_, z| z }.compact.min.round(1)}..#{filled.map { |_, z| z }.compact.max.round(1)}"
  if miss > 0
    out << "결측 있음 -> 중단"
    hb("ABORT miss=#{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')}_mk20직전.skp")
      m.save_copy(bak)
      out << "사전 백업 OK -> #{bak}"
      hb("BACKUP OK")

      pents = g2.entities
      made = []
      m.start_operation("jog20 3구간 필러 생성", true)
      mk_gapbody = lambda do |xlo, xhi|
        gb = pents.add_group
        pl = []
        pl << Geom::Point3d.new(xlo / MM, y0 / MM, zb / MM)
        pl << Geom::Point3d.new(xlo / MM, y1 / MM, zb / MM)
        filled.reverse.each { |y, z| pl << Geom::Point3d.new(xlo / MM, y / MM, z / MM) }
        f = gb.entities.add_face(pl)
        f.pushpull(f.normal.x > 0 ? (xhi - xlo) / MM : -(xhi - xlo) / MM)
        gb
      end
      subs.each_with_index do |c, i|
        h = foots[i]
        xs = h.map { |p| p[0] * 25.4 }
        prism = pents.add_group
        pf = prism.entities.add_face(h.map { |p| Geom::Point3d.new(p[0], p[1], zb / MM) })
        pf.reverse! if pf.normal.z < 0
        pf.pushpull((260.0 - zb) / MM)
        hb("PRISM#{i} solid=#{prism.manifold?}")
        gb = mk_gapbody.call(xs.min - 30.0, xs.max + 30.0)
        hb("GAPBODY#{i} solid=#{gb.manifold?}")
        if !prism.manifold? || !gb.manifold?
          prism.erase!; gb.erase!
          out << "  조각#{i}: 솔리드 생성 실패 -> 건너뜀"
          next
        end
        res = prism.intersect(gb)
        hb("INTERSECT#{i} -> #{res.class}")
        if res
          res.name = "SWD_03_jog20_필러#{i + 1}_20260919"
          made << res
        else
          out << "  조각#{i}: intersect nil"
        end
      end
      s02.hidden = true
      m.commit_operation
      hb("COMMIT OK made=#{made.length}")
      out << "생성 #{made.length}개, 원본 jog20 hidden"
      made.each do |gp|
        b = gp.bounds
        out << "  #{gp.name} bbox (#{r1(b.min.x)},#{r1(b.min.y)},#{r1(b.min.z)})..(#{r1(b.max.x)},#{r1(b.max.y)},#{r1(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")
"MK20"
