본문 바로가기
  • You find inspiration to create your own path !
업무 자동화/FreeCAD

Python] 사각기둥 만들기 코드 분석

by ToolBOX01 2026. 5. 28.
반응형

가로 200, 세로 100, 좌우 대칭, 위 아래 대칭인 사각형을 그리고 높이 30인 사각기둥을 만듭니다.

FreeCAD (App 메인 엔진)
 └── korea_200x100 (Document 문서)
      └── Body (PartDesign::Body 컨테이너)
           ├── XY_Plane (기준 평면 - 부착 지지대)
           ├── Sketch (Sketcher::SketchObject)
           │    ├── Geometry (도형 데이터: Line 4개)
           │    └── Constraints (구속조건: Horizontal, Vertical, Coincident, Distance, Symmetric)
           └── Pad (PartDesign::Pad 3D 피처)
                └── Profile ── (Sketch 참조)


■ 핵심 라이브러리(모듈) 불러오기

FreeCAD에서 파이썬(Python)을 이용해 자동 모델링 시스템이나 GUI 매크로 프로그램을 만들 때 가장 기본적으로 선언하는 핵심 라이브러리(모듈) 불러오기 문장

import FreeCAD as App
import FreeCADGui as Gui
import Part
import Sketcher
from PySide6 import QtWidgets, QtCore, QtGui

1. import FreeCAD as App

  • 역할: FreeCAD의 '데이터/연산 메인 엔진'을 불러옵니다.
  • 상세 설명: 눈에 보이지 않는 백그라운드에서 새 문서를 만들고(newDocument), 데이터를 계산하고, 객체를 관리하는 핵심 라이브러리입니다.
  • 왜 as App을 쓰나요?: 코드 안에서 매번 FreeCAD.something이라고 길게 쓰는 번거로움을 줄이기 위해, 전 세계 개발자들 사이의 약속(표준 규약)으로 App이라는 짧은 별명을 붙여서 사용합니다.

2. import FreeCADGui as Gui

  • 역할: FreeCAD의 '시각화/화면 제어 엔진'을 불러옵니다.
  • 상세 설명: 3D 뷰포트의 카메라 각도를 바꾸거나(예: ISO 뷰), 화면 크기에 맞게 모델을 꽉 차게 정렬하거나(view.fitAll()), 작업 환경(워크벤치)을 전환할 때 사용합니다.
  • 왜 as Gui를 쓰나요?: 1번의 App(데이터 엔진)과 확실하게 구분하여 "이 기능은 화면(UI)을 제어하는 기능이다"라는 가독성을 높이기 위해 Gui라는 별명을 씁니다.

3. import Part

  • 역할: 3D 모델링의 기초가 되는 '기하학적 형상(Geometry) 엔진'을 불러옵니다.
  • 상세 설명: 점(Vector), 선분(LineSegment), 원(Circle) 등 실제 스케치 공간이나 3D 공간 상에 그려지는 수학적 도형 데이터를 생성하고 다룰 때 사용합니다.
  • 예: 스케치 안에 선을 그릴 때 Part.LineSegment(p1, p2) 형태로 사용됩니다.

4. import Sketcher

  • 역할: 2D 도면을 그리는 '스케치(Sketcher) 및 구속조건 엔진'을 불러옵니다.
  • 상세 설명: 선과 선을 이어주는 일치(Coincident), 수평(Horizontal), 수직(Vertical), 대칭(Symmetric) 및 치수 고정(DistanceX, DistanceY) 같은 구속조건(Constraints)을 스케치에 부여하여 도면을 완전 구속(Fully Constrained) 상태로 만들 때 필수적인 모듈입니다.

5. from PySide6 import QtWidgets, QtCore, QtGui

  • 역할: FreeCAD 내부에서 작동하는 '팝업창 및 GUI 그래픽 UI 제작 도구'를 불러옵니다.
  • 상세 설명: 3D 모델링 작업이 끝났을 때 "작업이 완료되었습니다!"라는 알림 메시지 창(QMessageBox)을 띄우거나, 사용자에게 치수를 입력받는 커스텀 대화상자(Dialog) 창을 만들 때 사용하는 Qt 라이브러리입니다.
  • 버전 참고: FreeCAD 최신 버전(v1.0 이상)은 내부적으로 PySide6를 공식 표준으로 사용하므로 최신 매크로 작성 시 필수적으로 포함됩니다.

■ 스케치 평면 선택 하기

FreeCAD의 PartDesign 워크벤치에서 새로운 2D 스케치 창을 만들고, 이를 3D 공간 상의 XY 평면(XY-Plane)에 부착(Mapping)하여 그림을 그릴 준비를 마치는 핵심 자동화 프로세스

sketch = body.newObject("Sketcher::SketchObject", "Sketch")
xy_plane = body.Origin.OriginFeatures[3]
sketch.AttachmentSupport = [(xy_plane, '')]
sketch.MapMode = 'FlatFace'
doc.recompute()

1. sketch = body.newObject("Sketcher::SketchObject", "Sketch")

  • 의미: body(PartDesign Body)라는 3D 부품 컨테이너 안에 새로운 스케치 객체를 생성합니다.
  • 상세 설명: * "Sketcher::SketchObject"는 FreeCAD가 내부적으로 사용하는 스케치 객체의 고유 유형 이름(Type ID)입니다.
    • 두 번째 인자인 "Sketch"는 트리 보기(Tree View)에 표시될 이 객체의 기본 이름(Label)입니다.
    • GUI에서 'Create sketch (새 스케치 생성)' 아이콘 버튼을 클릭한 것과 같은 동작입니다.

2. xy_plane = body.newObject.Origin.OriginFeatures[3] 또는 body.Origin.OriginFeatures[3]

  • 의미: 바디가 가지고 있는 기본 고유 기준 좌표계(Origin) 항목 중에서 XY 평면(XY-Plane) 데이터를 찾아와 xy_plane이라는 변수에 저장합니다.
  • 상세 설명: FreeCAD의 PartDesign Body를 만들면 내부에 기본적으로 원점(Point), X/Y/Z축(Axis), 그리고 XY/XZ/YZ평면(Plane)이 자동으로 생성됩니다.
    • OriginFeatures 리스트에서 일반적으로 3번째 인덱스([3])가 XY 평면을 가리킵니다. (버전이나 환경에 따라 인덱스는 다를 수 있지만, 관례적으로 XY 평면을 참조할 때 주로 사용됩니다.)

3. sketch.AttachmentSupport = [(xy_plane, '')]

  • 의미: 방금 만든 스케치(sketch)를 어떤 바닥(평면)에 대고 그릴지 지지대(Support) 역할을 할 평면을 지정합니다.
  • 상세 설명: 여기서는 앞에서 추출한 xy_plane(XY 평면)을 타겟으로 지정해주었습니다. 튜플 형태 (xy_plane, '')로 전달하여, 스케치가 XY 평면 위에 딱 달라붙도록 링크(연결)해주는 역할을 합니다.

4. sketch.MapMode = 'FlatFace'

  • 의미: 평면에 스케치를 부착할 때의 매핑 모드(Mapping Mode)를 'FlatFace'로 설정합니다.
  • 상세 설명: FreeCAD에서 스케치를 공간 상에 배치하는 방법은 여러 가지가 있습니다. 'FlatFace' 모드는 지정한 평평한 면(여기서는 XY 평면)을 2D 스케치판으로 삼아 평평하게 밀착시켜 그리겠다는 가장 기본적이고 안정적인 설정입니다.
    • GUI에서 새 스케치를 만들 때 어떤 평면을 선택하고 "OK"를 누르는 매핑 매커니즘을 코드로 구현한 것입니다.

5. doc.recompute()

  • 의미: 지금까지 파이썬 코드로 명령한 내용(스케치 생성, 평면 부착 등)을 문서(doc) 전체에 반영하여 3D 화면과 내부 데이터 구조를 최신 상태로 새로고침(다시 계산)합니다.
  • 상세 설명: FreeCAD 파이썬 API는 코드가 실행될 때 실시간으로 화면을 바꾸면 컴퓨터가 느려지기 때문에, 백그라운드에서 계산을 멈추고 대기합니다. 모든 설정이 끝나고 recompute()를 호출해 주어야만 비로소 FreeCAD 화면에 스케치가 평면에 정상적으로 부착된 모습이 짜잔 하고 나타나게 됩니다. (상단 툴바의 파란색/새로고침 회전 화살표 아이콘을 누른 것과 같습니다.)

■ 사각형 커브 만들기

FreeCAD 스케치 모드에서 가로 200mm, 세로 100mm 크기의 사각형을 정확히 원점(0,0)을 중심으로 상하좌우 대칭이 되도록 완벽하게 구속(Fully Constrained)하는 파이썬 스크립트입니다.

1. 꼭짓점 좌표 정의 및 선분 생성 (addGeometry)

# ============================================================
# 4. 200 x 100 사각형 스케치 (원점 중심 대칭)
# ============================================================
p1 = App.Vector(-100.0, -50.0, 0.0)
p2 = App.Vector( 100.0, -50.0, 0.0)
p3 = App.Vector( 100.0,  50.0, 0.0)
p4 = App.Vector(-100.0,  50.0, 0.0)

sketch.addGeometry(Part.LineSegment(p1, p2), False)  # 0: 하단 가로선
sketch.addGeometry(Part.LineSegment(p2, p3), False)  # 1: 우측 세로선
sketch.addGeometry(Part.LineSegment(p3, p4), False)  # 2: 상단 가로선
sketch.addGeometry(Part.LineSegment(p4, p1), False)  # 3: 좌측 세로선
  • 좌표 정의: 원점(0,0)을 중심으로 가로 200(좌우 -100~100), 세로 100(상하 -50~50) 위치에 4개의 점(Vector)을 만듭니다.
  • 도형 등록: addGeometry를 통해 이 점들을 잇는 4개의 독립된 선분(LineSegment)을 스케치에 배치합니다. 이때 각각 0, 1, 2, 3번이라는 고유한 요소(Element) 번호가 부여됩니다.

 

2. 수평 및 수직 구속조건 (Horizontal, Vertical)

# ★ 수정: 수평/수직 각 1개씩만 적용 (대변은 Coincident로 자동 제약됨)
sketch.addConstraint(Sketcher.Constraint('Horizontal', 0))  # 0번 하단 선 수평
sketch.addConstraint(Sketcher.Constraint('Vertical', 1))    # 1번 우측 선 수직
  • 사각형이 비틀어지지 않게 만드는 핵심 최적화 단계입니다.
  • 0번 선에 'Horizontal(수평)'을 주고, 1번 선에 'Vertical(수직)'을 줍니다. 뒤이어 나오는 꼭짓점 일치 조건 덕분에, 대변(마주 보는 선)들까지 자동으로 완벽한 수평/수직 상태를 유지하게 되므로 모든 선에 구속을 줄 필요가 없어 과잉 구속(Over-constrained)을 방지합니다.

 

3. 꼭짓점 일치 구속조건 (Coincident)

# Coincident (꼭짓점 연결)
sketch.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1))
sketch.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1))
sketch.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 3, 1))
sketch.addConstraint(Sketcher.Constraint('Coincident', 3, 2, 0, 1))
  • 따로 떨어져 있는 4개의 선분을 서로 묶어 '닫힌 사각형'으로 만드는 작업입니다.
  • 인자의 의미는 ( 'Coincident', 선번호A, 정점번호A, 선번호B, 정점번호B ) 입니다.
    • 참고: 선분의 시작점(Start point)은 1, 끝점(End point)은 2 번입니다.
  • 예컨대 첫 줄은 0번 선의 끝점(2)과 1번 선의 시작점(1)을 완전히 하나로 붙이라는 명령어입니다. 이처럼 사방의 꼭짓점을 돌아가며 연결합니다.

 

4. 치수(크기) 구속조건 (DistanceX, DistanceY)

# 치수 구속
sketch.addConstraint(Sketcher.Constraint('DistanceY', 1, 1, 1, 2, 100.0))
sketch.addConstraint(Sketcher.Constraint('DistanceX', 0, 1, 0, 2, 200.0))
  • 사각형의 정확한 크기를 고정하는 치수 입력 단계입니다.
  • DistanceY (세로 치수): 1번 우측 세로선의 시작점(1)부터 끝점(2)까지의 세로 길이를 100.0mm로 고정합니다.
  • DistanceX (가로 치수): 0번 하단 가로선의 시작점(1)부터 끝점(2)까지의 가로 길이를 200.0mm로 고정합니다.

 

5. 원점 기준 대칭 구속조건 (Symmetric) ★ 핵심

# Symmetric 구속 (원점 기준)
sketch.addConstraint(Sketcher.Constraint('Symmetric', 0, 1, 2, 1, -1, 1))
sketch.addConstraint(Sketcher.Constraint('Symmetric', 0, 2, 2, 2, -1, 1))

doc.recompute()
  • 이 부분이 사각형을 스케치 화면 정중앙(원점)에 묶어주는 대칭(Symmetric) 구속입니다.
  • 인자 구조: ( 'Symmetric', 대상선A, 대상점A, 대상선B, 대상점B, 대칭중심선/점, 중심점번호 )
    • 여기서 -1은 스케치의 절대 원점(Origin)을 뜻하며, 뒤의 1은 원점의 고유 포인트 번호입니다.
  • 첫 번째 줄: 0번 선의 시작점(1)과 2번 선의 시작점(1)이 원점(-1)을 기준으로 완벽히 대칭(서로 정반대 맞은편)에 위치하도록 묶습니다. (좌하단 점 ↔ 우상단 점 대칭)
  • 두 번째 줄: 0번 선의 끝점(2)과 2번 선의 끝점(2) 역시 원점(-1)을 기준으로 대칭이 되도록 묶습니다. (우하단 점 ↔ 좌상단 점 대칭)

■ 전체 코드

import FreeCAD as App
import FreeCADGui as Gui
import Part
import Sketcher
from PySide6 import QtWidgets, QtCore, QtGui

# ============================================================
# 1. 문서 생성
# ============================================================
doc_name = "korea_200x100"
doc = App.newDocument(doc_name)
Gui.activateWorkbench("PartDesignWorkbench")

# ============================================================
# 2. Body 생성
# ============================================================
body = doc.addObject("PartDesign::Body", "Body")
doc.recompute()

# ============================================================
# 3. Sketch 생성 및 XY-plane 부착
# ============================================================
sketch = body.newObject("Sketcher::SketchObject", "Sketch")
xy_plane = body.Origin.OriginFeatures[3]
sketch.AttachmentSupport = [(xy_plane, '')]
sketch.MapMode = 'FlatFace'
doc.recompute()

# ============================================================
# 4. 200 x 100 사각형 스케치 (원점 중심 대칭)
# ============================================================
p1 = App.Vector(-100.0, -50.0, 0.0)
p2 = App.Vector( 100.0, -50.0, 0.0)
p3 = App.Vector( 100.0,  50.0, 0.0)
p4 = App.Vector(-100.0,  50.0, 0.0)

sketch.addGeometry(Part.LineSegment(p1, p2), False)  # 0: 하단 가로선
sketch.addGeometry(Part.LineSegment(p2, p3), False)  # 1: 우측 세로선
sketch.addGeometry(Part.LineSegment(p3, p4), False)  # 2: 상단 가로선
sketch.addGeometry(Part.LineSegment(p4, p1), False)  # 3: 좌측 세로선

# ★ 수정: 수평/수직 각 1개씩만 적용 (대변은 Coincident로 자동 제약됨)
sketch.addConstraint(Sketcher.Constraint('Horizontal', 0))  # 0번 하단 선 수평
sketch.addConstraint(Sketcher.Constraint('Vertical', 1))    # 1번 우측 선 수직

# Coincident (꼭짓점 연결)
sketch.addConstraint(Sketcher.Constraint('Coincident', 0, 2, 1, 1))
sketch.addConstraint(Sketcher.Constraint('Coincident', 1, 2, 2, 1))
sketch.addConstraint(Sketcher.Constraint('Coincident', 2, 2, 3, 1))
sketch.addConstraint(Sketcher.Constraint('Coincident', 3, 2, 0, 1))

# 치수 구속
sketch.addConstraint(Sketcher.Constraint('DistanceY', 1, 1, 1, 2, 100.0))
sketch.addConstraint(Sketcher.Constraint('DistanceX', 0, 1, 0, 2, 200.0))

# Symmetric 구속 (원점 기준)
sketch.addConstraint(Sketcher.Constraint('Symmetric', 0, 1, 2, 1, -1, 1))
sketch.addConstraint(Sketcher.Constraint('Symmetric', 0, 2, 2, 2, -1, 1))

doc.recompute()

# ============================================================
# 5. Pad (높이 30mm)
# ============================================================
pad = body.newObject("PartDesign::Pad", "Pad")
pad.Profile = sketch
pad.Length = 30.0
doc.recompute()

# ============================================================
# 뷰 제어
# ============================================================
Gui.activeView().viewIsometric()
Gui.SendMsgToActiveView("ViewFit")

# ============================================================
# 6. 완료 팝업
# ============================================================
class SuccessDialog(QtWidgets.QDialog):
    def __init__(self):
        super().__init__(Gui.getMainWindow())
        self.setWindowTitle("✅ 생성 완료")
        self.setFixedSize(460, 310)
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowType.WindowStaysOnTopHint)
        self.setStyleSheet("""
            QDialog { background-color: #1e1e2e; border-radius: 12px; }
            QLabel#title { color: #cdd6f4; font-size: 18px; font-weight: bold; }
            QLabel#body  { color: #a6e3a1; font-size: 13px; }
            QLabel#detail{ color: #89b4fa; font-size: 12px; }
            QPushButton  {
                background-color: #89b4fa; color: #1e1e2e;
                border: none; border-radius: 8px;
                padding: 8px 32px; font-size: 13px; font-weight: bold;
            }
            QPushButton:hover { background-color: #b4d0ff; }
        """)

        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(30, 28, 30, 24)
        layout.setSpacing(12)

        title_row = QtWidgets.QHBoxLayout()
        icon_label = QtWidgets.QLabel("🎉")
        icon_label.setStyleSheet("font-size: 28px;")
        title_label = QtWidgets.QLabel("모델 생성이 완료되었습니다!")
        title_label.setObjectName("title")
        title_row.addWidget(icon_label)
        title_row.addSpacing(8)
        title_row.addWidget(title_label)
        title_row.addStretch()
        layout.addLayout(title_row)

        line = QtWidgets.QFrame()
        line.setFrameShape(QtWidgets.QFrame.Shape.HLine)
        line.setStyleSheet("color: #313244;")
        layout.addWidget(line)

        body_label = QtWidgets.QLabel(
            f"✔  문서명      : {doc_name}\n"
            "✔  스케치      : 200 × 100 mm (XY 평면, 원점 중심 대칭)\n"
            "✔  구속조건    : 수평×1 / 수직×1 / 일치×4 / 치수×2 / 대칭×2  완전 구속\n"
            "✔  패드 높이  : 30 mm\n"
            "✔  화면 뷰     : ISO 뷰 (Isometric) 자동 정렬"
        )
        body_label.setObjectName("body")
        layout.addWidget(body_label)

        detail_label = QtWidgets.QLabel("ISO 뷰 적용 및 ViewFit 처리가 완료되었습니다.")
        detail_label.setObjectName("detail")
        layout.addWidget(detail_label)
        layout.addStretch()

        btn = QtWidgets.QPushButton("확인")
        btn.clicked.connect(self.accept)
        btn_row = QtWidgets.QHBoxLayout()
        btn_row.addStretch()
        btn_row.addWidget(btn)
        btn_row.addStretch()
        layout.addLayout(btn_row)

        self.setLayout(layout)

SuccessDialog().exec_()

App.Console.PrintMessage("\n========================================\n")
App.Console.PrintMessage(f" 성공: '{doc_name}' 문서에 200x100 두께 30 패드 생성 완료!\n")
App.Console.PrintMessage(" 구속조건: 수평×1 / 수직×1 / 일치×4 / 치수×2 / 대칭×2 완전 구속\n")
App.Console.PrintMessage(" 뷰 제어: ISO 뷰(viewIsometric) 및 ViewFit 적용 완료\n")
App.Console.PrintMessage("========================================\n")
반응형