# encoding: UTF-8
# ridge6t — 하판 보강 능선을 "판이 접힌 성형부"로 재작성.
#   하판이 12T 이므로 능선도 12T 절곡(두께 일치). 통짜/원통 단면 아님.
#   단면 경로: 판 중립선 -> 하향 경사 -> 평평한 꼭대기(=바닥쪽 60) -> 상향 경사 -> 판 중립선
#   절곡 R: 중립선 R=12 (내R 6, 외R 18), K=0.42
HB = 'E:/도진팩토리/3D스캔및티칭시스템/_자동화/inbox/_보류/_r6t_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) * (25.4**3); 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
# 2D(x,z) 폴리라인 라운딩
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

m = Sketchup.active_model
out = []
hb("START")
won = m.entities.grep(Sketchup::Group).find { |g| g.name == "조립부원판_20260919" }
if won.nil?
  out << "하판 없음"
else
  fw = []; collect(won, won.transformation, fw)
  ys = 11375.0
  zt1 = zhit(fw, 8850.0 / MM, ys / MM, true)
  zt2 = zhit(fw, 9200.0 / MM, ys / MM, true)
  if zt1.empty? || zt2.empty?
    out << "하판 상면 실측 실패"
  else
    za = zt1.max * 25.4; zb = zt2.max * 25.4
    slope = (zb - za) / (9200.0 - 8850.0)
    ztop = lambda { |x| za + slope * (x - 8850.0) }
    zmid = lambda { |x| ztop.call(x) - THK / 2.0 * Math.cos(Math.atan(slope)) }
    out << "하판 상면 기울기 #{(Math.atan(-slope) * 180 / Math::PI).round(4)}도, 두께 #{THK}T -> 능선도 #{THK}T 로 통일(하판과 동일 판재이므로)"
    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')}_r6t직전.skp"))
      hb("BACKUP OK")
      m.start_operation("하판 능선 절곡 성형 재작성", true)
      body = won
      info = []
      xs_c.each_with_index do |xc, idx|
        # 1) 기존 능선 + 판 스트립 제거 (관통 창)
        cut = m.entities.add_group
        cf = cut.entities.add_face([
          Geom::Point3d.new((xc - wtop / 2) / MM, ry0 / MM, -200.0 / MM),
          Geom::Point3d.new((xc + wtop / 2) / MM, ry0 / MM, -200.0 / MM),
          Geom::Point3d.new((xc + wtop / 2) / MM, ry1 / MM, -200.0 / MM),
          Geom::Point3d.new((xc - wtop / 2) / MM, ry1 / MM, -200.0 / MM)
        ])
        cf.reverse! if cf.normal.z < 0
        cf.pushpull(600.0 / MM)
        r = cut.subtract(body)
        body = r if r
        hb("WINDOW#{idx} #{r.class}")
        # 2) 12T 절곡 밴드 생성
        path = [
          [xc - wtop / 2 - 1.0, zmid.call(xc - wtop / 2 - 1.0)],
          [xc - wtop / 2 + 8.0, zmid.call(xc - wtop / 2 + 8.0)],
          [xc - wbot / 2, zmid.call(xc) - dep],
          [xc + wbot / 2, zmid.call(xc) - dep],
          [xc + wtop / 2 - 8.0, zmid.call(xc + wtop / 2 - 8.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) }
        bandg = m.entities.add_group
        bf = bandg.entities.add_face(poly)
        bf.pushpull(bf.normal.y > 0 ? -(ry1 - ry0) / MM : (ry1 - ry0) / MM)
        hb("BAND#{idx} solid=#{bandg.manifold?}")
        if bandg.manifold?
          u = body.union(bandg)
          body = u if u
          hb("UNION#{idx} #{u.class}")
          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]
        else
          bandg.erase!
          out << "능선#{idx + 1}: 밴드 솔리드 실패"
        end
      end
      body.name = "조립부원판_20260919"
      m.commit_operation
      hb("COMMIT")
      info.each_with_index do |(xc, bends, flat, ba), i|
        out << "능선#{i + 1}: x중심 #{xc}, 상부폭 #{wtop}/하부폭 #{wbot}/깊이 #{dep}/길이 #{(ry1 - ry0).round(0)}mm, 판두께 #{THK}T 일정"
        out << "   절곡 #{bends.length}개 각도 #{bends.map { |a| a.round(1) }.inspect}도, 중립선R #{RC}(내R #{(RC - THK / 2).round(1)} / 외R #{(RC + THK / 2).round(1)}), K=#{KF}"
        out << "   전개 블랭크 폭 #{(flat + ba.inject(0.0) { |s, v| s + v } - bends.length * 0).round(1)}mm (윤곽 전개길이 #{flat.round(1)} 기준) × 길이 #{(ry1 - ry0).round(0)}mm"
      end
      out << "하판: solid=#{body.manifold?} 체적 #{vol(body).round(0)}mm3 면수 #{body.entities.grep(Sketchup::Face).length}"
    rescue Exception => e
      m.commit_operation rescue nil
      out << "ERR #{e.class}: #{e.message}"
      hb("ERR #{e.message}")
    end
  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/_보류/_capR2.png', 1400, 900, true)}"
puts out.join("\n")
hb("END")
"RIDGE6T"
