EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / AI / ทำชุดแผ่นแบบจากโมเดล SketchUp วางเลขกับเส้นระยะอัตโนมัติ
AI

ทำชุดแผ่นแบบจากโมเดล SketchUp วางเลขกับเส้นระยะอัตโนมัติ

โดย Ton-Or ·2 ต.ค. 2026·อ่าน 10 นาที
ทำชุดแผ่นแบบจากโมเดล SketchUp วางเลขกับเส้นระยะอัตโนมัติ

ขั้นที่คนมองข้ามที่สุดของงานเขียนแบบด้วยคอมพิวเตอร์ คือขั้นสุดท้าย ตอนที่ต้องย่อโมเดลสามมิติทั้งก้อนลงมาเป็นกระดาษ A4 ที่คนอ่านรู้เรื่อง มีเลขกำกับครบ เส้นบอกระยะไม่ทับกัน และบอกได้ว่ามาตราส่วนเท่าไร

พอดีผมทำโมเดลผังโรงงานเสร็จแล้ว ลูกค้าถามคำถามที่ตอบไม่ได้ทันทีว่า "แล้วเอาไปยื่นยังไง" เพราะสิ่งที่หน่วยงานรับคือชุดเอกสารกระดาษ ที่มีผังรวมของทั้งที่ดิน มีเลขกำกับอาคารทุกหลังพร้อมรายการว่าหลังไหนใช้ทำอะไร มีระยะกำกับ มีทิศเหนือ มีมาตราส่วน และมีหัวกระดาษกับช่องลงนาม

จะไล่ลากเส้นบอกระยะทีละเส้นในโปรแกรมก็ทำได้ แต่พอลูกค้าขอแก้ผังรอบที่สามผมก็ยอมแพ้ครับ เลยเปลี่ยนเป็นเขียนสคริปต์ให้มันสร้างชุดแผ่นแบบใหม่ทั้งชุดจากโมเดลปัจจุบันทุกครั้งที่สั่ง

ซีรีส์ที่ผมเขียนไว้ก่อนหน้าเป็นเรื่องการสร้างโมเดล — ตั้งแต่ เตรียมเครื่อง ต่อปลั๊กอิน MCP ไปจนถึง สั่งสร้างโครงเหล็กแล้ววิเคราะห์ FEA ส่วนบทความนี้คือปลายทางอีกด้าน คือออกแบบเสร็จแล้วทำแผ่นแบบส่งราชการ คนละงานกันคนละเครื่องมือกัน

ขั้นแรก ดึงรูปทรงออกจากโมเดลมาเป็นตัวเลข

ผมไม่ให้สคริปต์วาดจากโมเดลตรง ๆ แต่ให้ดึงข้อมูลออกมาเป็นตัวเลขก่อน แล้วค่อยเอาตัวเลขไปทำงานต่อในฝั่ง Python เหตุผลคือรันซ้ำเร็ว แก้ง่าย และไม่ต้องเปิด SketchUp ค้างไว้ตลอดเวลาที่นั่งจัดหน้ากระดาษ

โมเดลที่ลากด้วยมือมักจะเป็นเส้นล้วน ไม่มีหน้า เพราะคนวาดลากขอบอาคารแล้วจบ เลยต้องสร้างหน้าชั่วคราวขึ้นมาก่อนถึงจะถามพื้นที่กับกรอบได้

m = Sketchup.active_model
e = m.entities
q = 25.4                                  # inch -> mm
hide = /Terrain|Map |3D Bldgs|Footprints/ # เลเยอร์ภาพถ่ายดาวเทียม ไม่เอามาคิด

m.start_operation('tmp faces', true)
e.grep(Sketchup::Edge).each { |ed| ed.find_faces if ed.valid? }
e.grep(Sketchup::Group).each { |g|
  next if g.layer.name =~ hide
  g.entities.grep(Sketchup::Edge).each { |ed| ed.find_faces if ed.valid? }
}

out = []
e.grep(Sketchup::Face).each do |f|
  next if f.layer.name =~ hide
  b = f.bounds
  a = f.area * 0.00064516                 # ตร.นิ้ว -> ตร.ม.
  next if a < 3                           # ตัดเศษเส้นเล็ก ๆ ทิ้ง
  out << [(b.center.x.to_f * q).round, (b.center.y.to_f * q).round, a.round,
          ((b.max.x.to_f - b.min.x.to_f) * q).round,
          ((b.max.y.to_f - b.min.y.to_f) * q).round]
end
m.abort_operation                          # ทิ้งหน้าชั่วคราวทั้งหมด โมเดลกลับเป็นเหมือนเดิม
"N=#{out.size} :: " + out.map { |a| a.join(',') }.join(' ; ')

สองอย่างที่ต้องระวัง หนึ่งคือ 0.00064516 ตัวคูณแปลงตารางนิ้วเป็นตารางเมตร SketchUp คิดทุกอย่างเป็นนิ้วข้างใน ไม่ว่าจะตั้งหน่วยหน้าจอเป็นอะไร สองคือ abort_operation ตอนท้าย ทำให้หน้าที่สร้างขึ้นมาชั่วคราวหายไปหมด ไม่ทิ้งขยะไว้ในไฟล์ลูกค้า

อย่าลืมกลุ่มที่ถูกขยับ

กรอบของหน้าที่อยู่ในกลุ่มเป็นพิกัด local ของกลุ่มนั้น ถ้ากลุ่มเคยถูกเลื่อนหรือหมุนมาก่อน ต้อง b.center.transform(g.transformation) ก่อนใช้ ไม่งั้นอาคารจะไปโผล่ผิดที่บนแผ่นแบบ ทั้งที่ในโมเดลอยู่ถูกทุกอย่าง

สี่เหลี่ยมที่เอียง: กรอบสี่เหลี่ยมมันโกหก

ผังที่ดินจริงไม่ค่อยตั้งฉากกับทิศเหนือ อาคารเลยเอียงตามแนวรั้วไปด้วย ปัญหาคือ bounds คืนกรอบที่ขนานแกนเสมอ อาคารกว้าง 20 ยาว 30 ที่เอียง 30 องศา จะได้กรอบราว 32 x 36 ซึ่งไม่ใช่ขนาดที่จะเขียนลงแบบ

แต่เรามีของสองอย่างคือกรอบ กับพื้นที่จริง พอจะแก้ย้อนกลับหาด้านจริงได้ ถ้ารู้ว่ามันเป็นสี่เหลี่ยมมุมฉากที่เอียงอยู่ในกรอบนั้น

อาคารเอียง 30 องศาด้านจริง 20 x 30กรอบ bounds ขนานแกน ราว 32 x 36กรอบ boundsราว 32 x 36 ใหญ่เกินพื้นที่ของหน้าFace#areaแก้สมการย้อนหาด้านจริง 20 x 30ปัดให้ลงท้าย .0 หรือ .5 แล้วเขียนลงแบบ
กรอบ bounds ของอาคารที่เอียงใหญ่กว่าด้านจริงเสมอ เอาไปเขียนลงแบบตรง ๆ ไม่ได้ ต้องเอากรอบกับพื้นที่ที่โมเดลบอกมาแก้ย้อนหาด้านจริง
import math


def side_lengths(w_mm, h_mm, area_m2):
    """คืนด้านกว้าง-ยาวจริงของสี่เหลี่ยมที่เอียง จากกรอบ bbox + พื้นที่"""
    W, H, A = w_mm / 1000.0, h_mm / 1000.0, float(area_m2)
    q = 4 * A
    p = -(W * W + H * H)
    r = W * H - A
    disc = p * p - 4 * q * r
    if A <= 0 or disc < 0 or q == 0:
        return W, H                       # ไม่เอียง หรือแก้ไม่ได้ -> ใช้กรอบไปเลย
    k = max(0.0, min(0.5, (-p - math.sqrt(disc)) / (2 * q)))
    s2 = W * W + H * H - 4 * A * k
    su = math.sqrt(max(s2 + 2 * A, 1.0))
    d = math.sqrt(su * su - 4 * A) if su * su > 4 * A else 0.0
    b1, b2 = (su + d) / 2.0, (su - d) / 2.0
    return max(b1, b2), min(b1, b2)
ใช้ bbox ตรง ๆ

เขียนตัวเลขผิดลงแบบ

อาคารที่เอียงจะได้ขนาดใหญ่เกินจริงทุกหลัง และยิ่งเอียงใกล้ 45 องศายิ่งเพี้ยนมาก คนที่เอาแบบไปใช้ต่อจะกะพื้นที่ผิดตั้งแต่ต้น

แก้ย้อนจากพื้นที่

ได้ด้านจริงทั้งสองด้าน

ใช้กรอบกับพื้นที่ที่โมเดลบอกมา แก้สมการหาด้านจริง แล้วปัดให้ลงท้าย .0 หรือ .5 ตามธรรมเนียมที่ช่างอ่านแล้วใช้ได้จริง

ตอนแสดงผมปัดระยะให้ลงท้ายศูนย์หรือห้าเสมอ เพราะแบบระดับผังรวมไม่มีใครไปวัดกันที่เซนติเมตร และตัวเลขทศนิยมสองตำแหน่งบนผัง 1:1250 อ่านไม่ออกอยู่ดี

def r5(v):
    """ปัดระยะให้ลงท้าย .0 หรือ .5"""
    return round(v * 2.0) / 2.0

เรนเดอร์ผังเป็นภาพเส้นดำบนพื้นขาว

ภาพที่จะเอาไปแปะบนกระดาษต้องเป็นผังเส้นล้วน ไม่มีภาพถ่ายดาวเทียม ไม่มีพื้นดิน ไม่มีเงา จุดที่หลายคนติดคือภาพถ่ายดาวเทียมไม่ได้คุมด้วย RenderMode มันถูกแปะอยู่บนกลุ่มและเลเยอร์ชื่อกลุ่ม Terrain ต้องปิดเลเยอร์เอา

m  = Sketchup.active_model
ro = m.rendering_options
v  = m.active_view
c  = v.camera
q  = 25.4

# เก็บสภาพเดิมไว้ในตัวแปร global ก่อนแตะอะไรทั้งนั้น
$wsk ||= {
  'lay' => m.layers.map { |l| [l.name, l.visible?] },
  'rm'  => ro['RenderMode'], 'bg' => ro['BackgroundColor'],
  'gr'  => ro['DrawGround'], 'ho' => ro['DrawHorizon'],
  'ax'  => ro['DisplaySketchAxes'],
  'cam' => [c.eye.to_a, c.target.to_a, c.up.to_a, c.perspective?,
            c.perspective? ? c.fov : c.height.to_f]
}

m.layers.each { |l| l.visible = false if l.name =~ /Terrain|Map |3D Bldgs|Footprints/ }
ro['RenderMode'] = 1
ro['DrawGround'] = false
ro['DrawHorizon'] = false
ro['DisplaySketchAxes'] = false
ro['BackgroundColor'] = Sketchup::Color.new(255, 255, 255)

# กล้อง top ortho: up = [1,0,0] ทำให้แกน X ของโมเดลชี้ขึ้นบนของภาพ
c.set([CX / q, CY / q, 2_000_000 / q], [CX / q, CY / q, 0], [1, 0, 0])
c.perspective = false
c.height = FRAME_H / q                     # ความสูงกรอบภาพ หน่วยเดียวกับโมเดล
v.refresh
v.write_image({ :filename => OUT, :width => PW, :height => PH,
                :antialias => true, :transparent => false })
สายพานผลิตแผ่นแบบโมเดลSketchUp->dump ตัวเลขruby -> json->render top orthopng เส้นดำ->วางเลข + ระยะpython->HTML A4/A3มม. ล้วน->PDFสั่งพิมพ์

เส้นหนาบนโมเดลที่มีแต่เส้น

ผังที่ย่อลงมาเหลือกว้างสิบกว่าเซนติเมตรบนกระดาษ เส้นบาง ๆ จะจางจนแทบมองไม่เห็นตอนพิมพ์ ทางแก้ปกติคือเปิด DrawSilhouettes แล้วเพิ่ม SilhouetteWidth แต่มันจะไม่เกิดอะไรขึ้นเลยถ้าโมเดลไม่มีหน้า เพราะ silhouette คือขอบเงาของหน้า ไม่ใช่ของเส้น

ลำดับที่ใช้ได้คือแบบนี้ครับ

  1. เช็กก่อนว่าโมเดลเป็นเส้นล้วนจริงไหม เทียบจำนวน grep(Edge).length กับ grep(Face).length
  2. สร้างหน้าชั่วคราวบนวงปิดทุกวงด้วย find_faces
  3. ทำวัสดุโปร่งใสสนิท alpha = 0.0 แล้วใส่ทั้ง material และ back_material ผลคือข้างในยังโปร่ง เห็นทะลุเหมือนเดิม แต่ขอบมีเงาให้ตีเส้นหนาแล้ว
  4. เปิด DrawSilhouettes แล้วตั้ง SilhouetteWidth ตามขนาดภาพ ภาพใหญ่ต้องเลขมากขึ้นตาม
  5. เรนเดอร์ แล้วลบเฉพาะหน้าชั่วคราวทิ้ง เส้นเดิมอยู่ครบ
m.start_operation('bold outline', true)
made = []
m.entities.grep(Sketchup::Edge).each { |e| e.find_faces if e.valid? }
mt = m.materials.add('tmp_clear')
mt.alpha = 0.0                                   # โปร่งสนิท ไม่ใช่แค่จาง
m.entities.grep(Sketchup::Face).each { |f|
  next unless f.valid?
  made << f
  f.material = mt
  f.back_material = mt                           # ต้องใส่ทั้งสองด้าน ไม่งั้นทึบข้างหนึ่ง
}
ro['DrawSilhouettes'] = true
ro['SilhouetteWidth'] = 8                        # ภาพ 2200 px ใช้ราว 5-14
v.refresh
v.write_image({ :filename => OUT, :width => PW, :height => PH, :antialias => true })
m.entities.erase_entities(made.select(&:valid?)) # เก็บกวาด เหลือแต่เส้นเดิม
m.commit_operation
ต้องคืนสภาพหน้าจอให้เจ้าของไฟล์เสมอ

ผมเคยโดนมาแล้ว คือระหว่างที่สคริปต์ทยอยเรนเดอร์ทีละแผ่น ลูกค้าเปิดโมเดลอยู่ด้วยแล้วสลับ view ไปมา ทำให้แผ่นที่ออกมาสภาพไม่เหมือนกัน วิธีแก้คือบังคับสภาพที่ต้องการทุกครั้งก่อนเรนเดอร์ และคืนค่าเดิมทุกตัวหลังจบ ทั้งเลเยอร์ที่ปิดไป RenderMode สีพื้นหลัง และกล้อง ตัวแปร global ของ Ruby อยู่ข้ามการเรียกคนละครั้งได้ เก็บสภาพเดิมไว้ในนั้นได้เลย

s = $wsk
if s
  h = {}
  s['lay'].each { |n, vis| h[n] = vis }
  m.layers.each { |l| l.visible = h[l.name] if h.key?(l.name) }
  ro['RenderMode'] = s['rm']
  ro['BackgroundColor'] = s['bg']
  ro['DrawGround'] = s['gr']
  ro['DrawHorizon'] = s['ho']
  ro['DisplaySketchAxes'] = s['ax']
  eye, tgt, up, persp, val = s['cam']
  c.set(Geom::Point3d.new(*eye), Geom::Point3d.new(*tgt), Geom::Vector3d.new(*up))
  c.perspective = persp
  persp ? (c.fov = val) : (c.height = val)
  v.refresh
  "restored rendermode=#{ro['RenderMode']}"
end

อีกข้อที่ต้องรู้คือแยกขั้นสร้างกับขั้นเรนเดอร์เป็นคนละคำสั่ง ถ้ายัด find_faces กับ write_image ไว้ก้อนเดียวกัน SketchUp ค้างได้ และภาพใหญ่ ๆ บางทีคำสั่งคืนค่ามาว่าไม่มีข้อมูล ทั้งที่ไฟล์กำลังเขียนอยู่ ให้วนดูขนาดไฟล์จนนิ่งแล้วค่อยไปอ่าน

วางเลขกำกับเองโดยไม่ให้ทับกัน

เลขกำกับอาคารต้องอยู่กลางรูปถ้ารูปใหญ่พอ ถ้ารูปเล็กก็ต้องเลื่อนออกไปข้าง ๆ แล้วลากเส้นชี้กลับเข้ามา และห้ามทับเลขตัวอื่น ห้ามทับรูปอื่น ห้ามหลุดขอบกระดาษ

วิธีที่ผมใช้ตรงไปตรงมามาก คือเรียงจากรูปใหญ่ไปเล็ก ใครมาก่อนได้ที่ก่อน แล้วลองตำแหน่งรอบ ๆ ไปเรื่อย ๆ

LW, LH, PAD = 5.0, 3.4, 0.5          # ขนาดป้ายเลข (มม.) และระยะกันชน
occupied = []


def hit(cx, cy, w, h, items, pad=0.0):
    for (ox, oy, ow, oh) in items:
        if abs(cx - ox) < (w + ow) / 2.0 + pad and abs(cy - oy) < (h + oh) / 2.0 + pad:
            return True
    return False


place = {}
for i in sorted(range(len(S)), key=lambda k: -(boxes[k][2] * boxes[k][3])):
    cxm, cym, wm, hm = boxes[i]
    # รูปใหญ่พอ และตรงกลางยังว่าง -> วางกลางรูปเลย
    if wm >= LW + 0.8 and hm >= LH + 0.8 and not hit(cxm, cym, LW, LH, occupied, PAD):
        place[i] = (cxm, cym, False)
        occupied.append((cxm, cym, LW, LH))
        continue
    best = None
    for r in (0.7, 1.0, 1.4, 1.9, 2.5, 3.2):            # ไล่ออกจากรูปทีละวง
        for ang in (0, 45, 315, 90, 270, 135, 225, 180): # ขวาก่อน แล้วค่อยไล่รอบ
            dx = (wm / 2.0 + LW / 2.0 + 0.7) * r * math.cos(math.radians(ang))
            dy = (hm / 2.0 + LH / 2.0 + 0.7) * r * math.sin(math.radians(ang))
            px, py = cxm + dx, cym - dy
            if px < LW / 2 or px > W - LW / 2 or py < LH / 2 or py > H - LH / 2:
                continue                                 # หลุดขอบกระดาษ
            if hit(px, py, LW, LH, occupied, PAD):
                continue                                 # ทับเลขตัวอื่น
            if hit(px, py, LW * 0.55, LH * 0.55,
                   [b for j, b in enumerate(boxes) if j != i], 0.0):
                continue                                 # ไปนั่งทับรูปอื่น
            best = (px, py, True)
            break
        if best:
            break
    place[i] = best or (cxm + wm / 2.0 + LW / 2.0 + 0.8, cym, True)
    occupied.append((place[i][0], place[i][1], LW, LH))

พอเลขถูกเลื่อนออกไปไกลกว่าครึ่งหนึ่งของรูป ผมลากเส้นชี้กลับเข้าไปหาจุดกลางรูป ทำด้วย div บาง ๆ หมุนตามมุมที่คำนวณได้ ไม่ต้องใช้ svg ให้ยุ่ง

เส้นบอกระยะ: อัตโนมัติเป็นหลัก แต่ต้องสั่งทับได้

ตัวเลือกด้านอัตโนมัติทำงานได้ดีประมาณแปดสิบเปอร์เซ็นต์ ที่เหลือคือกรณีที่คนดูแล้วรู้ว่าควรไปอยู่อีกด้าน แต่กติกาอัตโนมัติมองไม่เห็น ผมเลยทำช่องสั่งทับเป็นรายชิ้นไว้ ไม่ใช่ไปแก้ตรรกะกลางให้พัง

# (เลขอาคาร, ความยาวด้าน) -> (ทิศ x, ทิศ y, ระยะเยื้องต่ำสุด มม.)
DIM_EDGE_OPT = {
    (9,  15.0): (0,  1, 4.0),    # ด้าน 15 ม. ของอาคารชั่ง -> ดันลงล่าง
    (16, 73.5): (-1, 0, 2.8),    # ด้านยาวของอาคารซ่อมบำรุง -> ไปฝั่งซ้าย
    (16, 26.5): (0,  1, 7.0),    # ด้านกว้าง -> ลงล่างให้พ้นเลข 19
}
DIM_PREF = {18: (1, 1), 4: (-1, 1), 14: (-1, 0), 16: (0, 1)}   # ทิศเส้นระยะรวมของอาคารนั้น
DIM_OFF  = {18: 4.5, 4: 3.2, 9: 6.0, 16: 6.0}                  # ระยะเยื้องขั้นต่ำเฉพาะบางเลข
DIM_IGNORE_BUSY = {9, 5}     # เลขที่ให้วางตามทิศที่สั่ง โดยไม่สนสิ่งกีดขวาง
DIM_ALL4 = {21}              # เลขที่ต้องการระยะครบทั้งสี่ด้าน
NO_DIM   = {6, 10, 15}       # เลขที่ไม่ต้องใส่ระยะเลย

ตัวเลขในนี้เป็นค่าตัวอย่างนะครับ ประเด็นอยู่ที่โครง คือแยกกฎทั่วไปกับข้อยกเว้นออกจากกันคนละที่ เวลาลูกค้าขอแก้ "ระยะตัวนี้ย้ายไปอยู่ล่าง" ผมเติมบรรทัดเดียวในตารางแล้วรันใหม่ ไม่ต้องไปแตะโค้ดที่ทำงานถูกอยู่แล้ว

ส่วนตัวผมว่านี่คือความต่างระหว่างสคริปต์ที่ใช้ได้จริงกับสคริปต์ที่เขียนสวยแต่ต้องแก้มือทุกครั้ง

อีกเรื่องคือการย้ายเส้นระยะที่อยู่ในกลุ่ม ถ้าเลือกเก็บเส้นบอกระยะไว้ในโมเดลจริง (ไม่ใช่วาดทับตอนทำ HTML) จะเจอกับดักเดิมของ SketchUp คือกลุ่มมี transformation ของตัวเอง ใส่พิกัดโลกลงไปตรง ๆ เส้นจะไปโผล่ที่อื่น

m.start_operation("Re-place dimensions", true)
moves = {                     # ชื่อกลุ่ม => [ข้อความเดิม, จุดเริ่ม, จุดปลาย, ระยะเยื้อง] เมตร
  "A" => ["75.00 m", [10.0, 80.0],  [10.0, 155.0], [-0.8, 0.0]],
  "B" => ["40.00 m", [0.0, -10.0],  [0.0, 30.0],   [-0.8, 0.0]],
}
log = []
moves.each do |name, (txt, a, b, off)|
  g = m.entities.grep(Sketchup::Group).find { |x| x.name == name }
  next log << "#{name}: ไม่พบกลุ่ม" unless g
  ti = g.transformation.inverse                    # โลก -> local ของกลุ่มนี้
  old = g.entities.grep(Sketchup::DimensionLinear).select { |d| d.text.to_s == txt }
  old.each { |d| g.entities.erase_entities(d) if d.valid? }
  p1 = Geom::Point3d.new(a[0].m, a[1].m, 0).transform(ti)
  p2 = Geom::Point3d.new(b[0].m, b[1].m, 0).transform(ti)
  nd = g.entities.add_dimension_linear(p1, p2, [off[0].m, off[1].m, 0])
  log << "#{name}: #{old.size} -> #{nd.text}"
end
m.commit_operation
log.join(" | ")

ที่ผมทำเป็น "ลบตัวเดิมที่ข้อความตรงกัน แล้วสร้างใหม่" ไม่ใช่ "ขยับตัวเดิม" เพราะแบบนี้รันซ้ำกี่รอบก็ได้ผลเดิม ไม่มีเส้นซ้อนกันเป็นสิบเส้นเวลาลองปรับค่าไปมา

ธรรมเนียมการใส่ระยะที่ผมยึด: ยึดแนวศูนย์กลางเสาหรือขอบนอกอย่างใดอย่างหนึ่งให้สม่ำเสมอ ระยะที่เท่ากันหลายช่วงใส่เส้นเดียวเป็นตัวแทนพอ ไม่ต้องใส่ครบทุกช่วงเพราะรก และหน่วยกับความละเอียดตั้งให้เหมือนกันทั้งชุด

ประกอบเป็นแผ่น A4 กับ A3

ตรงนี้ผมเลือกทำเป็น HTML แล้วสั่งพิมพ์เป็น PDF ไม่ได้ทำใน LayOut เหตุผลคือควบคุมได้ทุกมิลลิเมตรจากสคริปต์ และสร้างใหม่ทั้งชุดได้ในคำสั่งเดียวเวลาผังเปลี่ยน

หัวใจคือคิดทุกอย่างเป็นมิลลิเมตรจริงบนกระดาษ ไม่ใช่ pixel แล้วเลือกมาตราส่วนจากรายการมาตรฐาน โดยหาตัวแรกที่ทำให้ผังลงกรอบพอดี

STD = [250, 500, 750, 1000, 1250, 2000, 2500, 4000, 5000]
MAXH, MAXW = 168.0, 178.0          # พื้นที่วางภาพบน A4 แนวตั้ง หลังหักขอบและหัวกระดาษ

def fit(fr):
    for s in STD:
        h, w = fr['hx'] / s, fr['wy'] / s     # fr เป็นขนาดกรอบจริง หน่วย มม.
        if h <= MAXH and w <= MAXW:
            return s, w, h
    s = STD[-1]
    return s, fr['wy'] / s, fr['hx'] / s

# มาตราบรรทัด: เลือกความยาวที่พิมพ์แล้วยาวไม่เกิน 62 มม.
barL = 100
for L in (200, 100, 50, 25, 10):
    if L * 1000.0 / sc <= 62.0:
        barL = L
        break
barmm = barL * 1000.0 / sc
1 : 1250มาตราส่วนผังรวม
178 มม.กว้างสุดบน A4
168 มม.สูงสุดบน A4
62 มม.ยาวสุดของสเกลบาร์

ของที่ต้องมีครบทุกแผ่นตามที่หน่วยงานเขาดู คือหัวกระดาษ (ชื่อผู้ยื่น ที่อยู่ เลขทะเบียน) ช่องเลขที่เอกสารกับวันที่ ชื่อแบบกับหมายเลขข้อที่อ้างถึง ผังพร้อมเลขกำกับ รายการเลขกับประโยชน์ใช้สอย ลูกศรทิศเหนือ มาตราบรรทัด ข้อความบอกมาตราส่วน และช่องลงนามผู้มีอำนาจ

ทิศเหนือกับมาตราบรรทัดสำคัญกว่าที่คิด เพราะเวลาแบบถูกถ่ายเอกสารย่อ ตัวเลขมาตราส่วนที่พิมพ์ไว้จะผิดทันที แต่มาตราบรรทัดย่อตามไปด้วย เลยยังใช้วัดได้อยู่

ส่วนการทำ HTML ให้พิมพ์ออกมาหลายหน้าไม่เพี้ยน มีกับดักของมันอีกชุด ผมตั้งใจจะเขียนแยกไว้อีกบทความ เพราะเรื่องความกว้างพิมพ์จริงกับหัว-ท้ายที่ต้องซ้ำทุกหน้ามีรายละเอียดของมันเอง

ข้อสุดท้าย และเป็นข้อที่ผมพลาดมาแล้ว

ห้ามบอกว่าเสร็จก่อนดูภาพจริง

งานที่ผลลัพธ์เป็นภาพ ต้องเปิดภาพขึ้นมาดูแล้วเทียบกับของอ้างอิงก่อนบอกว่าเสร็จเสมอ ผมเคยส่งงานทั้งที่ภาพที่ตัวเองเรนเดอร์ออกมาแสดงชัดว่าอาคารลอยอยู่นอกตำแหน่ง ความผิดพลาดจริงไม่ใช่การวางผิด แต่คือการบอกว่าจบทั้งที่ตัวเองเห็นแล้วว่าไม่ตรง ถ้ามันไม่ตรงให้บอกไปตรง ๆ ว่าไม่ตรงประมาณเท่าไร ดีกว่าเอาของหยาบไปเสนอว่าเสร็จแล้ว

อีกกับดักที่คู่กันคืออย่าเพิ่งคิดว่าผลที่ออกมาเรียบเกินคาดคือบั๊ก ครั้งหนึ่งผมเรนเดอร์ผังออกมาได้เป็นเส้นสะอาดบนพื้นเทา ไม่มีภาพถ่ายดาวเทียม ผมนึกว่าพัง เลยไล่แก้อยู่หลายรอบเพื่อเอาภาพดาวเทียมกลับมา สุดท้ายลูกค้าบอกว่าไม่ต้องการภาพแผนที่ ต้องการผังที่วาดชัด ๆ พอ ของที่ผมกำลังจะทิ้งคือของที่เขาต้องการอยู่แล้ว

วิธีที่ถูกคือเอาภาพให้ดูก่อนแล้วถามหนึ่งคำถาม แทนที่จะเดาแล้วเผาเวลาไปหลายรอบ

สรุป

โครงทั้งหมดคือ ดึงตัวเลขออกจากโมเดลก่อน แก้ด้านจริงของรูปที่เอียงจากพื้นที่ เรนเดอร์ top ortho โดยปิดเลเยอร์ภาพถ่ายและบังคับสภาพ view เอง ตีเส้นให้หนาด้วยหน้าโปร่งใสบวก SilhouetteWidth คืนสภาพเดิมให้เจ้าของไฟล์ทุกครั้ง แล้วปล่อยให้สคริปต์วางเลขกับเส้นระยะเองโดยมีตารางสั่งทับไว้สำหรับเคสที่ตาคนตัดสินดีกว่า

ส่วนตัวผมว่าสิ่งที่คุ้มที่สุดคือความสามารถในการสร้างใหม่ทั้งชุด ครับ ลูกค้าขอแก้ผังรอบที่หก ผมรันสามคำสั่งได้แบบชุดใหม่ครบทุกแผ่นในเวลาไม่กี่นาที เทียบกับการไล่ขยับเส้นระยะทีละเส้นในโปรแกรม ต่างกันคนละโลก

ใครจะเอาไปใช้กับงานอื่นก็ปรับตารางกับขนาดกระดาษเอาตามสะดวก ส่วนของที่หน่วยงานแต่ละแห่งต้องการไม่เหมือนกัน อันนั้นต้องไปถามเขาก่อนเสมอ อย่าเชื่อตัวอย่างในบทความรวมทั้งของผมด้วย ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย