■ 가능 할까?
CSG(Constructive Solid Geometry)는 우리말로 '구성적 입체 기하학'이라고 합니다. 쉽게 말해, 복잡한 3D 모델을 만들 때 가장 기본적인 형태의 입체 도형(Primitive)인 정육면체(Box), 원기둥(Cylinder), 구(Sphere) 등을 논리 연산(Boolean Operation)으로 결합하여 형상을 완성하는 방식입니다.
|
마치 클레이(찰흙)를 뭉치고, 도구로 파내어 원하는 조각품을 만드는 것과 매우 유사한 직관적인 모델링 방식입니다. 현재까지는 기계는 CSG(Constructive Solid Geometry) 방식을 사용하면 안정적으로 코드를 만들어 줍니다.

🛡️ 왜 CSG 방식이 더 안정적인가
사용자가 스케치 기반(PartDesign) 모델링을 하다가 종종 마주치는 "Topological Naming Problem(위상 명명 문제)"이나 "구속 조건 충돌(Constraint Conflict)"로부터 자유롭기 때문입니다. 아직 기계는 이것에 대한 "학습(?)"하는데 어려움이 있나 봅니다. 그 구체적인 이유는 다음과 같습니다.
1. '히스토리'와 '구속 조건'의 부재
스케치 기반 모델은 "이 선은 저 선과 평행해야 한다", "이 점은 저 면에 붙어 있어야 한다"는 수많은 구속 조건(Constraints)을 기억해야 합니다. 만약 설계자가 치수를 크게 바꾸면, 기존에 설정했던 조건들이 꼬이면서 모델이 터지거나(오류 발생) 형상이 뭉개집니다. 하지만 CSG 방식은 치수나 위치를 도형 그 자체의 속성으로 처리하므로, 구속 조건이 꼬여서 모델이 파괴될 일이 없습니다.
2. 위상(Topology) 변화에 강함
스케치 기반에서는 특정 면(Face)이나 선(Edge)을 참조하여 다음 작업을 수행합니다. 그런데 치수를 바꾸어 면의 이름이나 번호가 바뀌면 FreeCAD는 "방금 참조한 그 면이 어디 갔지?"라며 길을 잃어버립니다. 반면 CSG는 면을 직접 참조하는 대신 공간 좌표(Vector)를 활용하므로, 모델의 크기를 아무리 크게 수정해도 논리 연산의 경로가 유지되어 훨씬 안정적입니다.
3. 코드 작성의 직관성
아래 이미지처럼 makeBox로 만들고 cut으로 파내는 방식은 코드가 매우 짧고 명확합니다. 결과물의 형상을 수학적 좌표로 정의하기 때문에, 소프트웨어가 해석하는 과정에서 오류를 일으킬 확률이 현저히 낮습니다.

Freecad로 3D 모델 만들기 (학습 자료)
CSG(Constructive Solid Geometry) 파이썬 코드
"""
Parametric L-Bracket v2 — CSG + Spreadsheet 연동
FreeCAD 1.1 호환
[치수 수정 방법]
방법 1 (추천): 모델 트리에서 "Dimensions" 스프레드시트를 더블클릭
→ B열 숫자를 수정 → 저장 후 매크로 재실행
방법 2: 이 파일 상단 dims 딕셔너리 값을 직접 수정 후 재실행
"""
import FreeCAD as App
import FreeCADGui as Gui
import Part
from FreeCAD import Vector
import math
# ── 문서 초기화 ────────────────────────────────────────────
doc = App.activeDocument()
if not doc:
doc = App.newDocument("Parametric_Bracket")
# Shape만 삭제 (스프레드시트 유지하여 치수 보존)
for obj in doc.Objects:
if obj.Name != "Dimensions":
doc.removeObject(obj.Name)
doc.recompute()
# ══════════════════════════════════════════════════════════
# 스프레드시트에서 치수 읽기 (없으면 기본값으로 생성)
# ══════════════════════════════════════════════════════════
default_dims = {
"Base_L": 54.0, # 베이스 가로 (X)
"Base_W": 36.0, # 베이스 세로 (Y)
"Base_T": 6.0, # 베이스 두께 (Z)
"Fillet_R": 10.0, # 앞 모서리 필렛 반경
"Hole_D": 9.0, # 구멍 지름
"Hole_X1": 10.0, # 좌 구멍 중심 X
"Hole_X2": 44.0, # 우 구멍 중심 X
"Hole_Y": 9.0, # 구멍 중심 Y
"Wall_T": 9.0, # 수직벽 두께
"Wall_H": 37.0, # 수직벽 높이
"Slot_D": 9.0, # 장공 지름
"Slot_X": 10.0, # 장공 중심 X
"Slot_Z1": 18.0, # 장공 하단 호 중심 Z
"Slot_Z2": 27.0, # 장공 상단 호 중심 Z
}
ss = doc.getObject("Dimensions")
if ss is None:
ss = doc.addObject("Spreadsheet::Sheet", "Dimensions")
ss.Label = "Dimensions"
# 헤더
ss.set("A1", "항목")
ss.set("B1", "값(mm)")
ss.set("C1", "설명")
descriptions = {
"Base_L": "베이스 가로",
"Base_W": "베이스 세로",
"Base_T": "베이스 두께",
"Fillet_R": "앞 모서리 R",
"Hole_D": "구멍 지름",
"Hole_X1": "좌 구멍 중심 X",
"Hole_X2": "우 구멍 중심 X",
"Hole_Y": "구멍 중심 Y",
"Wall_T": "수직벽 두께",
"Wall_H": "수직벽 높이",
"Slot_D": "장공 지름",
"Slot_X": "장공 중심 X",
"Slot_Z1": "장공 하단 Z",
"Slot_Z2": "장공 상단 Z",
}
for i, (key, val) in enumerate(default_dims.items()):
row = i + 2
ss.set(f"A{row}", key)
ss.set(f"B{row}", str(val))
ss.setAlias(f"B{row}", key)
ss.set(f"C{row}", descriptions[key])
doc.recompute()
print("📋 Dimensions 스프레드시트가 새로 생성되었습니다.")
else:
print("📋 기존 Dimensions 스프레드시트를 사용합니다.")
doc.recompute()
# 스프레드시트에서 값 읽기
def get_dim(key):
try:
val = ss.get(key)
return float(val)
except Exception:
return default_dims[key]
BL = get_dim("Base_L")
BW = get_dim("Base_W")
BT = get_dim("Base_T")
FR = get_dim("Fillet_R")
HD = get_dim("Hole_D")
HX1 = get_dim("Hole_X1")
HX2 = get_dim("Hole_X2")
HY = get_dim("Hole_Y")
VT = get_dim("Wall_T")
VH = get_dim("Wall_H")
SD = get_dim("Slot_D")
SX = get_dim("Slot_X")
SZ1 = get_dim("Slot_Z1")
SZ2 = get_dim("Slot_Z2")
SR = SD / 2
VY = BW - VT # 수직벽 Y 시작점
print(f"치수: 베이스 {BL}×{BW}×{BT}, 벽 두께 {VT}, 높이 {VH}")
# ══════════════════════════════════════════════════════════
# 1. 베이스 플레이트
# ══════════════════════════════════════════════════════════
base = Part.makeBox(BL, BW, BT)
# 앞 모서리 R 필렛 (CSG)
c1 = Part.makeBox(FR, FR, BT, Vector(0, 0, 0))
cy1 = Part.makeCylinder(FR, BT, Vector(FR, FR, 0))
base = base.cut(c1.cut(cy1))
c2 = Part.makeBox(FR, FR, BT, Vector(BL-FR, 0, 0))
cy2 = Part.makeCylinder(FR, BT, Vector(BL-FR, FR, 0))
base = base.cut(c2.cut(cy2))
# 바닥 구멍
hole1 = Part.makeCylinder(HD/2, BT, Vector(HX1, HY, 0))
hole2 = Part.makeCylinder(HD/2, BT, Vector(HX2, HY, 0))
base = base.cut(hole1).cut(hole2)
# ══════════════════════════════════════════════════════════
# 2. 수직 브라켓
# ══════════════════════════════════════════════════════════
# 상단 아크 접점 계산
cx, cz = FR, VH
px, pz = BL-FR, BT
dx = px - cx
dz = pz - cz
d = math.hypot(dx, dz)
theta = math.atan2(dz, dx)
angle_ct = theta + math.acos(FR / d)
tx = cx + FR * math.cos(angle_ct)
tz = cz + FR * math.sin(angle_ct)
mid_t = (math.pi + angle_ct) / 2
mx = cx + FR * math.cos(mid_t)
mz = cz + FR * math.sin(mid_t)
e1 = Part.makeLine(Vector(0, VY, BT), Vector(0, VY, VH))
e2 = Part.Arc(Vector(0, VY, VH), Vector(mx, VY, mz), Vector(tx, VY, tz)).toShape()
e3 = Part.makeLine(Vector(tx, VY, tz), Vector(px, VY, BT))
e4 = Part.makeLine(Vector(px, VY, BT), Vector(0, VY, BT))
wire = Part.Wire([e1, e2, e3, e4])
face = Part.Face(wire)
bracket = face.extrude(Vector(0, VT, 0))
# 장공 컷팅
slot_dir = Vector(0, 1, 0)
sc1 = Part.makeCylinder(SR, VT, Vector(SX, VY, SZ1), slot_dir)
sc2 = Part.makeCylinder(SR, VT, Vector(SX, VY, SZ2), slot_dir)
sbox = Part.makeBox(SR*2, VT, SZ2-SZ1, Vector(SX-SR, VY, SZ1))
slot_tool = sc1.fuse(sc2).fuse(sbox)
bracket = bracket.cut(slot_tool)
# ══════════════════════════════════════════════════════════
# 3. 병합 및 출력
# ══════════════════════════════════════════════════════════
final = base.fuse(bracket).removeSplitter()
Part.show(final)
doc.recompute()
Gui.activeDocument().activeView().viewIsometric()
Gui.SendMsgToActiveView("ViewFit")
print("✅ 완료! 치수 변경: 모델 트리의 'Dimensions' 스프레드시트를 더블클릭하세요.")
프로그램 실행 결과
| 3D 모델 | 치수 수정 Sheet |
![]() |
![]() |
기계 설계 분야에서 그 방향은 '도면의 디지털 트랜스포메이션(DX)'을 위한 차세대 핵심 기술입니다. 현재 기술 수준에서도 충분히 구현 가능한 구조이며, 다음과 같은 아키텍처가 됩니다.

- Frontend (사용자 인터페이스):
- 사용자가 2D 도면 이미지(PDF/PNG)를 업로드합니다.
- AI가 도면을 해석하는 동안 진행 상황을 시각적으로 보여주며, 최종 결과물(STEP 파일)을 다운로드받는 창구 역할을 합니다.
- AI Insight Engine (데이터 추출부):
- Vision AI: 도면의 선, 원, 필렛 등의 기하학적 특징(Feature)을 파악합니다.
- OCR Engine: 치수 기입(54, R10 등)과 주석을 텍스트 데이터로 변환합니다.
- 이 과정에서 추출된 데이터는 JSON 형태의 '구조화된 도면 정보'로 변환됩니다.
- Parametric Generator (로직 변환부):
- LLM(거대 언어 모델)이 도면 정보를 바탕으로 FreeCAD 파이썬 코드를 작성합니다.
- 이때 CSG(Constructive Solid Geometry) 방식의 코드를 우선 생성하여 형상의 안정성을 확보합니다.
- FreeCAD Headless Server (변환 엔진):
- 서버 내부에서 GUI가 없는 Headless 모드의 FreeCAD가 구동됩니다.
- 생성된 파이썬 코드를 서버 내에서 실행하여 3D 모델을 생성하고, 이를 표준 교환 포맷인 STEP(또는 IGES) 파일로 최종 익스포트합니다.
- Data Persistence (메타 데이터 저장소):
- 모델의 형상 정보뿐만 아니라 도면의 공차, 재질, 작성일 등의 PMI(Product Manufacturing Information)를 데이터베이스에 메타 정보로 저장합니다.
by korealionkk@gmail.com
'업무 자동화 > FreeCAD' 카테고리의 다른 글
| 2D(PDF) 파일 → 3D로 변환 : JSON 파일 고도화 (0) | 2026.06.27 |
|---|---|
| 2D(PDF) 파일 → 3D로 변환 (0) | 2026.06.27 |
| 설계 검증 체크 리스트 데이터베이스 (0) | 2026.06.24 |
| Feature 기반 3D CAD를 데이터베이스로 바라보기 (0) | 2026.06.23 |
| 모델 유사성 평가 프로그램 컨 (1) | 2026.06.13 |

