# encoding: UTF-8
# ridgefix — ① 잘못 생성된 밴드(y 10100~10950)와 기존 통짜 능선을 하판 아래에서 제거
#            ② 12T 절곡 능선 2줄을 올바른 방향으로 재성형 후 union
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_rfix_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
THK = 12.0
RC = 12.0
KF = 0.42
def vol(g); (g.volume rescue -1) * 16387.064; 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.outer_loop.vertices.map { |v| v.position.transform(tr) }
      n = e.normal.transform(tr); n.normalize!
      faces << [pts, n]
    when Sketchup::Group, Sketchup::ComponentInstance
      collect(e, tr * e.transformation, faces)
    end
  end
end
def zhit(faces, x, y, up)
  res = []
  faces.each do |pts, n|
    next if n.z.abs < 0.05
    next if up && n.z < 0.05
    next if !up && 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 fillet2(pl, r, seg)
  outp = [pl[0]]
  (1...pl.length - 1).each do |i|
    a = pl[i - 1]; b = pl[i]; c = pl[i + 1]
    v1 = [a[0] - b[0], a[1] - b[1]]; v2 = [c[0] - b[0], c[1] - b[1]]
    l1 = Math.sqrt(v1[0]**2 + v1[1]**2); l2 = Math.sqrt(v2[0]**2 + v2[1]**2)
    next if l1 < 1e-9 || l2 < 1e-9
    u1 = [v1[0] / l1, v1[1] / l1]; u2 = [v2[0] / l2, v2[1] / l2]
    ca = [[u1[0] * u2[0] + u1[1] * u2[1], 1.0].min, -1.0].max
    ang = Math.acos(ca)
    if ang > Math::PI - 0.02 || ang < 0.02
      outp << b
      next
    end
    tn = [r / Math.tan(ang / 2.0), l1 * 0.45, l2 * 0.45].min
    rr = tn * Math.tan(ang / 2.0)
    p1 = [b[0] + u1[0] * tn, b[1] + u1[1] * tn]
    p2 = [b[0] + u2[0] * tn, b[1] + u2[1] * tn]
    bis = [u1[0] + u2[0], u1[1] + u2[1]]
    bl = Math.sqrt(bis[0]**2 + bis[1]**2)
    if bl < 1e-9
      outp << b
      next
    end
    dc = rr / Math.sin(ang / 2.0)
    cx = b[0] + bis[0] / bl * dc; cy = b[1] + bis[1] / bl * dc
    a1 = Math.atan2(p1[1] - cy, p1[0] - cx)
    a2 = Math.atan2(p2[1] - cy, p2[0] - cx)
    da = a2 - a1
    da -= 2 * Math::PI while da > Math::PI
    da += 2 * Math::PI while da < -Math::PI
    (0..seg).each { |k| t = a1 + da * k / seg.to_f; outp << [cx + rr * Math.cos(t), cy + rr * Math.sin(t)] }
  end
  outp << pl[-1]
  res = [outp[0]]
  outp[1..-1].each { |p| res << p if Math.sqrt((p[0] - res[-1][0])**2 + (p[1] - res[-1][1])**2) > 0.02 }
  res
end
def off2(pl, h)
  nrm = []
  (0...pl.length - 1).each do |i|
    dx = pl[i + 1][0] - pl[i][0]; dz = pl[i + 1][1] - pl[i][1]
    l = Math.sqrt(dx * dx + dz * dz)
    nrm << [-dz / l, dx / l]
  end
  vn = pl.each_with_index.map do |p, i|
    if i == 0 then nrm[0]
    elsif i == pl.length - 1 then nrm[-1]
    else
      ax = nrm[i - 1][0] + nrm[i][0]; az = nrm[i - 1][1] + nrm[i][1]
      l = Math.sqrt(ax * ax + az * az)
      sc = (ax / l) * nrm[i][0] + (az / l) * nrm[i][1]
      sc = 0.3 if sc < 0.3
      [(ax / l) / sc, (az / l) / sc]
    end
  end
  up = pl.each_with_index.map { |p, i| [p[0] + vn[i][0] * h, p[1] + vn[i][1] * h] }
  dn = pl.each_with_index.map { |p, i| [p[0] - vn[i][0] * h, p[1] - vn[i][1] * h] }
  [up, dn]
end
def boxgrp(ents, x0, x1, y0, y1, z0, z1)
  g = ents.add_group
  f = g.entities.add_face([
    Geom::Point3d.new(x0 / 25.4, y0 / 25.4, z0 / 25.4),
    Geom::Point3d.new(x1 / 25.4, y0 / 25.4, z0 / 25.4),
    Geom::Point3d.new(x1 / 25.4, y1 / 25.4, z0 / 25.4),
    Geom::Point3d.new(x0 / 25.4, y1 / 25.4, z0 / 25.4)
  ])
  f.reverse! if f.normal.z < 0
  f.pushpull((z1 - z0) / 25.4)
  g
end

m = Sketchup.active_model
out = []
hb("START")
plate = m.entities.grep(Sketchup::Group).find { |g| g.name == "조립부원판_20260919" }
if plate.nil?
  out << "하판 없음 -> 중단"
else
  fw = []; collect(plate, plate.transformation, fw)
  ys = 11375.0
  zt1 = zhit(fw, 8850.0 / MM, ys / MM, true)
  zt2 = zhit(fw, 9200.0 / MM, ys / MM, true)
  za = zt1.max * 25.4; zbv = zt2.max * 25.4
  slope = (zbv - za) / 350.0
  ztop = lambda { |x| za + slope * (x - 8850.0) }
  vcos = Math.cos(Math.atan(slope))
  zmid = lambda { |x| ztop.call(x) - (THK / 2.0) / vcos }
  zbot = lambda { |x| ztop.call(x) - THK / vcos }
  out << "하판 상면 기울기 #{(Math.atan(-slope) * 180 / Math::PI).round(4)}도 / 두께 #{THK}T -> 능선도 12T(하판과 동일 판재)"
  ry0 = 10950.0; ry1 = 11800.0
  xs_c = [8950.0, 9130.0]
  wtop = 100.0; wbot = 60.0; dep = 25.0
  begin
    base = File.basename(m.path, '.skp'); dir = File.dirname(m.path)
    m.save_copy(File.join(dir, "#{base}.bak_#{Time.now.strftime('%Y%m%d_%H%M%S')}_rfix직전.skp"))
    hb("BACKUP OK")
    m.start_operation("능선 정리 후 12T 절곡 성형", true)
    body = plate
    # ① 하판 아래 잔재(통짜 능선 + 오배치 밴드) 일괄 제거
    xs_c.each_with_index do |xc, i|
      zcut = zbot.call(xc) - 0.3
      cb = boxgrp(m.entities, xc - 70.0, xc + 70.0, 10050.0, 11950.0, -250.0, zcut)
      r = cb.subtract(body)
      body = r if r
      hb("CLEAN#{i} #{r.class} zcut=#{zcut.round(2)}")
    end
    out << "하판 아래 잔재 제거 후 체적 #{vol(body).round(0)}mm3, bbox y #{(body.bounds.min.y*25.4).round(1)}..#{(body.bounds.max.y*25.4).round(1)} z #{(body.bounds.min.z*25.4).round(1)}..#{(body.bounds.max.z*25.4).round(1)}"
    # ② 12T 절곡 밴드 성형
    info = []
    xs_c.each_with_index do |xc, idx|
      win = boxgrp(m.entities, xc - wtop / 2, xc + wtop / 2, ry0, ry1, -250.0, 400.0)
      r = win.subtract(body)
      body = r if r
      hb("WIN#{idx} #{r.class}")
      path = [
        [xc - wtop / 2 - 1.0, zmid.call(xc - wtop / 2 - 1.0)],
        [xc - wtop / 2 + 10.0, zmid.call(xc - wtop / 2 + 10.0)],
        [xc - wbot / 2, zmid.call(xc) - dep],
        [xc + wbot / 2, zmid.call(xc) - dep],
        [xc + wtop / 2 - 10.0, zmid.call(xc + wtop / 2 - 10.0)],
        [xc + wtop / 2 + 1.0, zmid.call(xc + wtop / 2 + 1.0)]
      ]
      bends = []
      (1...path.length - 1).each do |i|
        v1 = [path[i][0] - path[i - 1][0], path[i][1] - path[i - 1][1]]
        v2 = [path[i + 1][0] - path[i][0], path[i + 1][1] - path[i][1]]
        l1 = Math.sqrt(v1[0]**2 + v1[1]**2); l2 = Math.sqrt(v2[0]**2 + v2[1]**2)
        ca = [[(v1[0] * v2[0] + v1[1] * v2[1]) / (l1 * l2), 1.0].min, -1.0].max
        bends << (Math.acos(ca) * 180 / Math::PI)
      end
      fp = fillet2(path, RC, 6)
      up, dn = off2(fp, THK / 2.0)
      poly = (up + dn.reverse).map { |p| Geom::Point3d.new(p[0] / MM, ry0 / MM, p[1] / MM) }
      bg = m.entities.add_group
      bf = bg.entities.add_face(poly)
      bf.pushpull((ry1 - ry0) / MM)
      bb = bg.bounds
      if (bb.min.y * 25.4) < ry0 - 1.0
        bg.transform!(Geom::Transformation.translation(Geom::Vector3d.new(0, (ry1 - ry0) / MM, 0)))
        bb = bg.bounds
      end
      hb("BAND#{idx} solid=#{bg.manifold?} y #{(bb.min.y*25.4).round(1)}..#{(bb.max.y*25.4).round(1)}")
      if bg.manifold? && (bb.min.y * 25.4) > ry0 - 1.0
        u = body.union(bg)
        body = u if u
        flat = 0.0
        (0...fp.length - 1).each { |i| flat += Math.sqrt((fp[i + 1][0] - fp[i][0])**2 + (fp[i + 1][1] - fp[i][1])**2) }
        ri = RC - THK / 2.0
        ba = bends.map { |a| (Math::PI / 180.0) * a * (ri + KF * THK) }
        info << [xc, bends, flat, ba, (bb.min.y * 25.4).round(1), (bb.max.y * 25.4).round(1)]
        hb("UNION#{idx} #{u.class}")
      else
        bg.erase!
        out << "능선#{idx + 1}: 밴드 실패(solid=#{bg.valid? ? bg.manifold? : 'erased'})"
      end
    end
    body.name = "조립부원판_20260919"
    m.commit_operation
    hb("COMMIT")
    info.each_with_index do |(xc, bends, flat, ba, y0b, y1b), i|
      ri = RC - THK / 2.0
      out << "능선#{i + 1}: x중심 #{xc}, 상부폭 #{wtop}/하부폭 #{wbot}/깊이 #{dep}mm, y #{y0b}~#{y1b}(길이 #{(y1b - y0b).round(0)}), 판두께 #{THK}T 일정"
      out << "   절곡 #{bends.length}개, 각도 #{bends.map { |a| a.round(1) }.inspect}도, 중립선R #{RC} (내R #{ri.round(1)} / 외R #{(RC + THK / 2).round(1)}), K=#{KF}"
      out << "   전개 블랭크 폭 #{(flat).round(1)}mm(중립선 전개) / BA합 #{ba.inject(0.0) { |s, v| s + v }.round(1)}mm × 길이 #{(y1b - y0b).round(0)}mm"
    end
    out << "하판 최종: solid=#{body.manifold?} 체적 #{vol(body).round(0)}mm3 면수 #{body.entities.grep(Sketchup::Face).length} bbox y #{(body.bounds.min.y*25.4).round(1)}..#{(body.bounds.max.y*25.4).round(1)} z #{(body.bounds.min.z*25.4).round(1)}..#{(body.bounds.max.z*25.4).round(1)}"
  rescue Exception => e
    m.commit_operation rescue nil
    out << "ERR #{e.class}: #{e.message}"
    hb("ERR #{e.message}")
  end
end
v = m.active_view
v.camera = Sketchup::Camera.new(Geom::Point3d.new(8500 / 25.4, 10450 / 25.4, -250 / 25.4), Geom::Point3d.new(9040 / 25.4, 11380 / 25.4, 95 / 25.4), Geom::Vector3d.new(0, 0, 1))
out << "capture=#{v.write_image('E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_capR3.png', 1400, 900, true)}"
puts out.join("\n")
hb("END")
"RIDGEFIX"
