# encoding: UTF-8
# mkfull — 원부재의 절곡 단면(jog 단차 포함)을 그대로 따라가는 6T 판재 필러 "1개" 생성.
#   3조각 단면을 union 으로 한 몸으로 합친 뒤, 상단 3구간 프로파일로 intersect.
MEMBER = "SWD_02_t6_1000_jog30"
NEWNAME = "SWD_02_jog30_6T절곡_20260919"
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_mkfull_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 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
if src.nil? || s04.nil?
  out << "대상 없음 -> 중단"
  hb("ABORT")
else
  f4 = []
  collect(s04, s04.transformation, f4)
  subs = src.definition.entities.grep(Sketchup::Group)
  foots = []
  subs.each_with_index do |c, i|
    fs = []
    collect(c, src.transformation * c.transformation, fs)
    zs = fs.flat_map { |pts, _, _| pts.map { |p| p.z } }
    zc = (zs.min + zs.max) / 2.0
    lp, = chain(section_segs(fs, zc), 0.05 / MM)
    if lp.length < 3
      out << "조각#{i + 1} 단면 실패"; next
    end
    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
  y0 = 11050.0; y1 = 11850.0; zb = 106.5
  allp = foots.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 foots.length < 2 || prof.any? { |_, z| z.nil? }
    out << "단면/프로파일 부족 -> 중단"
    hb("ABORT")
  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')}_#{NEWNAME}직전.skp")
      m.save_copy(bak)
      out << "사전 백업 OK -> #{bak}"
      hb("BACKUP OK")
      pents = g2.entities
      m.start_operation("#{NEWNAME} 생성", true)
      prisms = []
      foots.each_with_index do |lp, i|
        g = pents.add_group
        f = g.entities.add_face(lp.map { |p| Geom::Point3d.new(p.x, p.y, zb / MM) })
        f.reverse! if f.normal.z < 0
        f.pushpull((260.0 - zb) / MM)
        hb("PRISM#{i} solid=#{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
      hb("BODY solid=#{body.manifold?}")
      xs = allp.map { |p| p.x * 25.4 }
      gb = pents.add_group
      xlo = xs.min - 40.0; xhi = xs.max + 40.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("GAPBODY solid=#{gb.manifold?}")
      res = body.intersect(gb)
      hb("INTERSECT -> #{res.class}")
      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 << "intersect nil -> 생성 실패"
      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
puts out.join("\n")
hb("END")
"MKFULL"
