# -*- coding: utf-8 -*-
# 장공(스케일로 늘어난 구멍) -> 원형 Ø17 복구.  원본 무수정, 보정 사본 생성.
def f(v); (v.to_f*25.4).round(2); end
MM = 1.0/25.4
m = Sketchup.active_model
selg = m.selection.first
raise "선택 없음" unless selg

log = []
m.start_operation("장공복구_원형_20260919", true)
begin
  host = m.entities.add_group
  host.name = "장공복구_원형_20260919"
  inst = host.entities.add_instance(selg.definition, selg.transformation)

  # --- 스케일을 정점에 굽기(recursive explode -> regroup) ---
  def bake(ents, inst, nm, out)
    res = inst.explode
    subs = res.select{|e| e.valid? && (e.is_a?(Sketchup::Group)||e.is_a?(Sketchup::ComponentInstance))}
    if subs.empty?
      geo = res.select{|e| e.valid? && (e.is_a?(Sketchup::Face)||e.is_a?(Sketchup::Edge))}
      g = ents.add_group(geo)
      g.name = nm
      out << g
    else
      subs.each do |s|
        next unless s.valid?
        sn = (s.respond_to?(:name) && s.name.to_s != "") ? s.name.to_s : (s.definition.name.to_s rescue "sub")
        bake(ents, s, sn, out)
      end
    end
  end
  leaves = []
  bake(host.entities, inst, "root", leaves)
  log << "구운 리프 수 = #{leaves.size} : #{leaves.map{|g| g.name}.inspect}"

  plate = leaves.find{|g| g.name =~ /SWD_04/}
  raise "SWD_04 리프 못 찾음" unless plate
  pe = plate.entities
  log << "SWD_04 baked: faces=#{pe.grep(Sketchup::Face).size} manifold_before=#{plate.manifold?}"

  # --- 장공(48정점 내부 루프) 수집 ---
  slots = []
  pe.grep(Sketchup::Face).each do |fc|
    fc.loops.each do |lp|
      next if lp.outer?
      vs = lp.vertices
      next unless vs.size == 48
      pts = vs.map{|v| v.position}
      cx = pts.map{|p| p.x}.inject(:+)/pts.size
      cy = pts.map{|p| p.y}.inject(:+)/pts.size
      cz = pts.map{|p| p.z}.inject(:+)/pts.size
      c = Geom::Point3d.new(cx,cy,cz)
      xs=pts.map{|p| f(p.x)}; ys=pts.map{|p| f(p.y)}; zs=pts.map{|p| f(p.z)}
      slots << {:face=>fc, :loop=>lp, :c=>c, :n=>fc.normal,
                :bb=>[(xs.max-xs.min),(ys.max-ys.min),(zs.max-zs.min)]}
    end
  end
  log << "장공 루프 수(전) = #{slots.size}"
  slots.each_with_index{|s,i| log << "  slot#{i} c=(#{f(s[:c].x)},#{f(s[:c].y)},#{f(s[:c].z)}) bbox=#{s[:bb].map{|v| v.round(2)}.inspect}" if i<4}

  # --- 짝짓기: 법선방향으로 마주보는 루프 2개 = 관통구멍 1개 ---
  used = []
  pairs = []
  slots.each_with_index do |s,i|
    next if used.include?(i)
    best=nil; bestd=nil
    slots.each_with_index do |t,j|
      next if j<=i || used.include?(j)
      v = t[:c] - s[:c]
      perp = (v - Geom::Vector3d.new(*s[:n].to_a.map{|x| x*(v.dot(s[:n]))})).length rescue nil
      d = v.length
      # 법선에 거의 평행해야 함
      next unless d > 0.001
      vv = Geom::Vector3d.new(v.x, v.y, v.z)
      ang = vv.angle_between(s[:n])*180/Math::PI
      next unless ang < 15 || ang > 165
      if bestd.nil? || d < bestd; bestd=d; best=j; end
    end
    if best
      used << i << best
      pairs << [s, slots[best], f(bestd)]
    end
  end
  log << "관통구멍 짝 수 = #{pairs.size} (두께: #{pairs.map{|p| p[2]}.uniq.inspect} mm)"

  # --- 1) 장공 에지 전부 삭제 -> 면이 자동 치유(구멍 메움) ---
  drill = []
  pairs.each do |a,b,t|
    drill << {:c=>a[:c], :n=>a[:normal] || a[:n], :t=>t}
  end
  del = []
  slots.each do |s|
    next unless s[:loop].valid?
    s[:loop].edges.each do |e|
      next unless e.valid?
      del << e
      e.faces.each do |ff|
        next if ff == s[:face]
        ff.edges.each{|e2| del << e2}
      end
    end
  end
  del = del.uniq.select{|e| e.valid?}
  log << "삭제할 에지 수 = #{del.size}"
  pe.erase_entities(del)
  log << "메움 후: faces=#{pe.grep(Sketchup::Face).size} manifold=#{plate.manifold?}"

  # --- 2) Ø17 원형 구멍 재천공 ---
  ok=0; ng=0
  drill.each_with_index do |h, idx|
    unless plate.valid?; ng+=1; log << "  drill#{idx} plate invalid"; next; end
    pe2 = plate.entities
    c = h[:c]; n = h[:n]; t = h[:t]
    tgt = nil; cproj = nil
    pe2.grep(Sketchup::Face).each do |fc|
      next unless fc.valid?
      next unless fc.normal.parallel?(n)
      pl = fc.plane
      next if c.distance_to_plane(pl).abs > 0.02
      pr = c.project_to_plane(pl)
      if fc.classify_point(pr) == Sketchup::Face::PointInside
        tgt = fc; cproj = pr; break
      end
    end
    if tgt.nil?; ng+=1; log << "  drill#{idx} 대상면 없음"; next; end
    nrm = tgt.normal
    circ = pe2.add_circle(cproj, nrm, 8.5*MM, 48)
    nf = nil
    circ.each do |e|
      next unless e.valid?
      e.faces.each{|ff| nf = ff if ff.valid? && ff.edges.size == 48}
      break if nf
    end
    nf = pe2.add_face(circ) if nf.nil?
    if nf.nil?; ng+=1; log << "  drill#{idx} add_face 실패"; next; end
    begin
      nf.pushpull(-t*MM, false)
      ok+=1
    rescue => e2
      ng+=1; log << "  drill#{idx} pushpull 실패 #{e2.message}"
    end
  end
  log << "재천공 성공=#{ok} 실패=#{ng}"
  log << "최종: faces=#{plate.entities.grep(Sketchup::Face).size} manifold=#{plate.manifold?} volume=#{(plate.volume*25.4**3/1000.0).round(1) rescue 'n/a'} cm3"

  # 검증: 남은 내부 루프
  after = []
  plate.entities.grep(Sketchup::Face).each{|fc| fc.loops.each{|lp| after << lp unless lp.outer?}}
  rnd = after.select{|lp|
    pts = lp.vertices.map{|v| v.position}
    xs=pts.map{|p| f(p.x)}; ys=pts.map{|p| f(p.y)}; zs=pts.map{|p| f(p.z)}
    ex=[(xs.max-xs.min),(ys.max-ys.min),(zs.max-zs.min)].sort
    ex[2] < 20.0
  }
  log << "복구 후 내부 루프 총 #{after.size}개 / 그중 최대치수<20mm(원형Ø17) = #{rnd.size}개"
  rnd.first(3).each_with_index do |lp,i|
    pts = lp.vertices.map{|v| v.position}
    xs=pts.map{|p| f(p.x)}; ys=pts.map{|p| f(p.y)}; zs=pts.map{|p| f(p.z)}
    log << "  hole#{i} bbox=#{[(xs.max-xs.min).round(2),(ys.max-ys.min).round(2),(zs.max-zs.min).round(2)].inspect} nvert=#{lp.vertices.size}"
  end

  # 원본은 숨김(수정 아님, 되돌리기 가능)
  selg.hidden = true
  log << "원본(그룹#2) hidden=true 처리(무수정)"
  m.commit_operation
rescue => ex
  m.abort_operation
  log << "ERROR #{ex.class}: #{ex.message}"
  log << ex.backtrace.first(6).join("\n")
end
puts log.join("\n")
"DONE6"
