์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ–จ๏ธ 3D ํ”„๋ฆฐํ„ฐ G-code ์ œ์–ด ์†Œ์Šค์ฝ”๋“œ ์˜ˆ์ œ๋กœ ๋ฐฐ์šฐ๋Š” ์‹ค์ „ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€์ด๋“œ

๐Ÿ–จ๏ธ 3D ํ”„๋ฆฐํ„ฐ G-code ์ œ์–ด ์†Œ์Šค์ฝ”๋“œ ์˜ˆ์ œ๋กœ ๋ฐฐ์šฐ๋Š” ์‹ค์ „ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€์ด๋“œ

์ดˆ๋ณด์ž๋„ ์‰ฝ๊ฒŒ ๋”ฐ๋ผํ•˜๋Š” G-code ์ œ์–ด ์™„๋ฒฝ ๋งˆ์Šคํ„ฐ

G-CODE CONTROL G28 ; Home All G1 X100 Y100 G1 Z10 F1500 M104 S200 M109 S200 G1 E10 F300 3D Printer Control System

์•ˆ๋…•! ๐Ÿ‘‹ ์˜ค๋Š˜์€ 3D ํ”„๋ฆฐํ„ฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ํ•ต์‹ฌ ์–ธ์–ด์ธ G-code๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ๋‹ค๋ฃจ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊ฒŒ. 3D ํ”„๋ฆฐํŒ…์— ๊ด€์‹ฌ์ด ์žˆ๊ฑฐ๋‚˜, ์ž๋™ํ™” ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ๊ธ€์ด ์ •๋ง ์œ ์šฉํ•  ๊ฑฐ์•ผ! ๐Ÿš€

G-code๋Š” CNC ๊ธฐ๊ณ„๋‚˜ 3D ํ”„๋ฆฐํ„ฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ํ‘œ์ค€ ์–ธ์–ด์ธ๋ฐ, ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด. ๋งˆ์น˜ ํ”„๋ฆฐํ„ฐ์—๊ฒŒ "์—ฌ๊ธฐ๋กœ ์ด๋™ํ•ด", "์ด ์˜จ๋„๋กœ ๊ฐ€์—ดํ•ด", "ํ•„๋ผ๋ฉ˜ํŠธ๋ฅผ ์ด๋งŒํผ ๋ฐ€์–ด๋‚ด" ๊ฐ™์€ ๋ช…๋ น์„ ๋‚ด๋ฆฌ๋Š” ๊ฑฐ์ง€. ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ์ด๊ฑธ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด? ์™„์ „ ์ž๋™ํ™”๋œ 3D ํ”„๋ฆฐํŒ… ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿ˜Ž

๐ŸŽฏ G-code๊ฐ€ ๋ญ๊ธธ๋ž˜?

G-code๋Š” 1950๋…„๋Œ€ MIT์—์„œ ๊ฐœ๋ฐœ๋œ ์ˆ˜์น˜ ์ œ์–ด(Numerical Control) ์–ธ์–ด์•ผ. ์›๋ž˜๋Š” CNC ๋ฐ€๋ง ๋จธ์‹ ์„ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์ง€๋งŒ, ์ง€๊ธˆ์€ 3D ํ”„๋ฆฐํ„ฐ์˜ ํ‘œ์ค€ ์ œ์–ด ์–ธ์–ด๋กœ ์ž๋ฆฌ์žก์•˜์–ด. ๊ฐ ๋ช…๋ น์–ด๋Š” ์•ŒํŒŒ๋ฒณ ๋ฌธ์ž์™€ ์ˆซ์ž์˜ ์กฐํ•ฉ์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๊ณ , ํ•œ ์ค„์— ํ•˜๋‚˜์˜ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” ๊ตฌ์กฐ์•ผ.

๐Ÿ“Œ G-code์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ

โ€ข G ๋ช…๋ น์–ด: ์ด๋™, ํ˜ธ๋ฐ ๋“ฑ ๊ธฐ๋ณธ ๋™์ž‘
โ€ข M ๋ช…๋ น์–ด: ๋ชจํ„ฐ, ํžˆํ„ฐ ๋“ฑ ๊ธฐ๊ณ„ ์ œ์–ด
โ€ข T ๋ช…๋ น์–ด: ํˆด(๋…ธ์ฆ) ๋ณ€๊ฒฝ
โ€ข ํŒŒ๋ผ๋ฏธํ„ฐ: X, Y, Z(์œ„์น˜), F(์†๋„), S(์˜จ๋„) ๋“ฑ

์˜ˆ๋ฅผ ๋“ค์–ด G1 X100 Y50 Z10 F1500์ด๋ผ๋Š” ๋ช…๋ น์–ด๋ฅผ ๋ณด๋ฉด:
- G1: ์ง์„  ์ด๋™ ๋ช…๋ น
- X100: X์ถ•์œผ๋กœ 100mm ์œ„์น˜๋กœ
- Y50: Y์ถ•์œผ๋กœ 50mm ์œ„์น˜๋กœ
- Z10: Z์ถ•์œผ๋กœ 10mm ์œ„์น˜๋กœ
- F1500: ์†๋„๋Š” 1500mm/min์œผ๋กœ

์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•œ ํ…์ŠคํŠธ ๋ช…๋ น์œผ๋กœ 3D ํ”„๋ฆฐํ„ฐ์˜ ๋ชจ๋“  ๋™์ž‘์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒŒ ์ •๋ง ๋งค๋ ฅ์ ์ด์ง€ ์•Š์•„? ๐ŸŽจ

๐Ÿ’ป Python์œผ๋กœ G-code ์ œ์–ดํ•˜๊ธฐ

๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์€ Python์„ ์ด์šฉํ•œ ์‹œ๋ฆฌ์–ผ ํ†ต์‹ ์ด์•ผ. ๋Œ€๋ถ€๋ถ„์˜ 3D ํ”„๋ฆฐํ„ฐ๋Š” USB๋ฅผ ํ†ตํ•ด ์‹œ๋ฆฌ์–ผ ํฌํŠธ๋กœ ์—ฐ๊ฒฐ๋˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด G-code ๋ช…๋ น์„ ์ „์†กํ•  ์ˆ˜ ์žˆ์–ด. pySerial ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€! ๐Ÿ

๊ธฐ๋ณธ ์—ฐ๊ฒฐ ์„ค์ •

import serial
import time

class Printer3D:
    def __init__(self, port='/dev/ttyUSB0', baudrate=115200):
        """
        3D ํ”„๋ฆฐํ„ฐ ์—ฐ๊ฒฐ ์ดˆ๊ธฐํ™”
        port: ์‹œ๋ฆฌ์–ผ ํฌํŠธ (Windows: COM3, Linux: /dev/ttyUSB0)
        baudrate: ํ†ต์‹  ์†๋„ (๋ณดํ†ต 115200 ๋˜๋Š” 250000)
        """
        self.ser = serial.Serial(port, baudrate, timeout=1)
        time.sleep(2)  # ์—ฐ๊ฒฐ ์•ˆ์ •ํ™” ๋Œ€๊ธฐ
        print(f"ํ”„๋ฆฐํ„ฐ ์—ฐ๊ฒฐ๋จ: {port}")
        
    def send_gcode(self, command):
        """G-code ๋ช…๋ น ์ „์†ก"""
        self.ser.write(f"{command}\n".encode())
        time.sleep(0.1)
        
        # ์‘๋‹ต ์ฝ๊ธฐ
        response = self.ser.readline().decode().strip()
        print(f"์ „์†ก: {command} | ์‘๋‹ต: {response}")
        return response
    
    def wait_for_ok(self):
        """ํ”„๋ฆฐํ„ฐ์˜ 'ok' ์‘๋‹ต ๋Œ€๊ธฐ"""
        while True:
            response = self.ser.readline().decode().strip()
            if 'ok' in response.lower():
                return True
            time.sleep(0.1)
    
    def close(self):
        """์—ฐ๊ฒฐ ์ข…๋ฃŒ"""
        self.ser.close()
        print("ํ”„๋ฆฐํ„ฐ ์—ฐ๊ฒฐ ์ข…๋ฃŒ")

# ์‚ฌ์šฉ ์˜ˆ์ œ
printer = Printer3D('/dev/ttyUSB0', 115200)
printer.send_gcode('G28')  # ํ™ˆ ํฌ์ง€์…˜์œผ๋กœ ์ด๋™
printer.wait_for_ok()
printer.close()

๐Ÿ’ก TIP: Windows์—์„œ๋Š” ์žฅ์น˜ ๊ด€๋ฆฌ์ž์—์„œ COM ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด. Linux๋‚˜ Mac์—์„œ๋Š” ls /dev/tty* ๋ช…๋ น์œผ๋กœ ํ™•์ธ ๊ฐ€๋Šฅํ•ด!

์‹ค์ „ ์ œ์–ด ์˜ˆ์ œ

์ด์ œ ์‹ค์ œ๋กœ ํ”„๋ฆฐํ„ฐ๋ฅผ ์›€์ง์—ฌ๋ณด์ž! ๊ฐ„๋‹จํ•œ ์ •์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ฆฌ๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ. ๐ŸŽฏ

class PrinterController(Printer3D):
    def __init__(self, port, baudrate=115200):
        super().__init__(port, baudrate)
        self.current_pos = {'X': 0, 'Y': 0, 'Z': 0}
        
    def home_all(self):
        """๋ชจ๋“  ์ถ• ํ™ˆ ํฌ์ง€์…˜์œผ๋กœ"""
        print("๐Ÿ  ํ™ˆ ํฌ์ง€์…˜์œผ๋กœ ์ด๋™ ์ค‘...")
        self.send_gcode('G28')
        self.wait_for_ok()
        self.current_pos = {'X': 0, 'Y': 0, 'Z': 0}
        
    def set_temperature(self, nozzle_temp, bed_temp=0):
        """์˜จ๋„ ์„ค์ •"""
        print(f"๐Ÿ”ฅ ๋…ธ์ฆ ์˜จ๋„: {nozzle_temp}ยฐC, ๋ฒ ๋“œ ์˜จ๋„: {bed_temp}ยฐC")
        self.send_gcode(f'M104 S{nozzle_temp}')  # ๋…ธ์ฆ ์˜จ๋„ ์„ค์ •
        if bed_temp > 0:
            self.send_gcode(f'M140 S{bed_temp}')  # ๋ฒ ๋“œ ์˜จ๋„ ์„ค์ •
        
    def wait_temperature(self, nozzle_temp):
        """๋ชฉํ‘œ ์˜จ๋„ ๋„๋‹ฌ ๋Œ€๊ธฐ"""
        print(f"โณ ์˜จ๋„ ๋„๋‹ฌ ๋Œ€๊ธฐ ์ค‘...")
        self.send_gcode(f'M109 S{nozzle_temp}')
        self.wait_for_ok()
        
    def move_to(self, x=None, y=None, z=None, speed=1500):
        """์ง€์ • ์œ„์น˜๋กœ ์ด๋™"""
        command = 'G1'
        if x is not None:
            command += f' X{x}'
            self.current_pos['X'] = x
        if y is not None:
            command += f' Y{y}'
            self.current_pos['Y'] = y
        if z is not None:
            command += f' Z{z}'
            self.current_pos['Z'] = z
        command += f' F{speed}'
        
        self.send_gcode(command)
        
    def extrude(self, amount, speed=300):
        """ํ•„๋ผ๋ฉ˜ํŠธ ์••์ถœ"""
        self.send_gcode(f'G1 E{amount} F{speed}')
        
    def draw_square(self, size=50, z_height=0.2):
        """์ •์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ"""
        print(f"๐Ÿ“ {size}mm ์ •์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๋Š” ์ค‘...")
        
        # ์‹œ์ž‘ ์œ„์น˜๋กœ ์ด๋™
        self.move_to(x=10, y=10, z=z_height)
        self.wait_for_ok()
        
        # ์ •์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ
        positions = [
            (10 + size, 10),
            (10 + size, 10 + size),
            (10, 10 + size),
            (10, 10)
        ]
        
        for x, y in positions:
            self.move_to(x=x, y=y)
            self.extrude(5)  # ์ด๋™ํ•˜๋ฉด์„œ ํ•„๋ผ๋ฉ˜ํŠธ ์••์ถœ
            self.wait_for_ok()
            
        print("โœ… ์ •์‚ฌ๊ฐํ˜• ์™„์„ฑ!")

# ์‹ค์ œ ์‚ฌ์šฉ
controller = PrinterController('/dev/ttyUSB0')

# ์ดˆ๊ธฐํ™”
controller.home_all()
controller.set_temperature(nozzle_temp=200, bed_temp=60)
controller.wait_temperature(200)

# ์ •์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ
controller.draw_square(size=50, z_height=0.2)

# ์ข…๋ฃŒ
controller.move_to(z=50)  # Z์ถ• ์˜ฌ๋ฆฌ๊ธฐ
controller.send_gcode('M104 S0')  # ๋…ธ์ฆ ํžˆํ„ฐ ๋„๊ธฐ
controller.send_gcode('M140 S0')  # ๋ฒ ๋“œ ํžˆํ„ฐ ๋„๊ธฐ
controller.close()

์ด ์ฝ”๋“œ๋Š” ์‹ค์ œ๋กœ 3D ํ”„๋ฆฐํ„ฐ๋ฅผ ์ œ์–ดํ•ด์„œ 50mm ํฌ๊ธฐ์˜ ์ •์‚ฌ๊ฐํ˜•์„ ๊ทธ๋ ค. ์˜จ๋„ ์„ค์ •๋ถ€ํ„ฐ ์ด๋™, ์••์ถœ๊นŒ์ง€ ๋ชจ๋“  ๊ณผ์ •์ด ์ž๋™ํ™”๋˜์–ด ์žˆ์ง€! ๐Ÿ˜Š

๐Ÿ”ง ์ฃผ์š” G-code ๋ช…๋ น์–ด ์™„๋ฒฝ ์ •๋ฆฌ

3D ํ”„๋ฆฐํ„ฐ ์ œ์–ด์— ํ•„์ˆ˜์ ์ธ G-code ๋ช…๋ น์–ด๋“ค์„ ์ •๋ฆฌํ•ด๋ดค์–ด. ์ด๊ฒƒ๋งŒ ์•Œ์•„๋„ ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์ด ๊ฐ€๋Šฅํ•ด! ๐Ÿ“š

๋ช…๋ น์–ด ๊ธฐ๋Šฅ ์˜ˆ์ œ
G28 ํ™ˆ ํฌ์ง€์…˜์œผ๋กœ ์ด๋™ G28 (๋ชจ๋“  ์ถ•), G28 X Y (X, Y์ถ•๋งŒ)
G1 ์ง์„  ์ด๋™ (์••์ถœ ํฌํ•จ) G1 X100 Y50 Z10 F1500
G0 ๋น ๋ฅธ ์ด๋™ (์••์ถœ ์—†์Œ) G0 X50 Y50 Z20
G92 ํ˜„์žฌ ์œ„์น˜ ์„ค์ • G92 E0 (์••์ถœ๊ธฐ ์œ„์น˜ ๋ฆฌ์…‹)
M104 ๋…ธ์ฆ ์˜จ๋„ ์„ค์ • (๋Œ€๊ธฐ ์•ˆํ•จ) M104 S200
M109 ๋…ธ์ฆ ์˜จ๋„ ์„ค์ • (๋„๋‹ฌ ๋Œ€๊ธฐ) M109 S200
M140 ๋ฒ ๋“œ ์˜จ๋„ ์„ค์ • (๋Œ€๊ธฐ ์•ˆํ•จ) M140 S60
M190 ๋ฒ ๋“œ ์˜จ๋„ ์„ค์ • (๋„๋‹ฌ ๋Œ€๊ธฐ) M190 S60
M106 ๋ƒ‰๊ฐ ํŒฌ ์ผœ๊ธฐ M106 S255 (์ตœ๋Œ€ ์†๋„)
M107 ๋ƒ‰๊ฐ ํŒฌ ๋„๊ธฐ M107

๐ŸŽ“ ๊ณ ๊ธ‰ ๋ช…๋ น์–ด

โ€ข G2/G3: ์›ํ˜ธ ์ด๋™ (์‹œ๊ณ„๋ฐฉํ–ฅ/๋ฐ˜์‹œ๊ณ„๋ฐฉํ–ฅ)
โ€ข M82: ์••์ถœ๊ธฐ ์ ˆ๋Œ€ ์ขŒํ‘œ ๋ชจ๋“œ
โ€ข M83: ์••์ถœ๊ธฐ ์ƒ๋Œ€ ์ขŒํ‘œ ๋ชจ๋“œ
โ€ข M84: ๋ชจํ„ฐ ๋น„ํ™œ์„ฑํ™”
โ€ข M117: LCD์— ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
โ€ข M300: ๋น„ํ”„์Œ ์žฌ์ƒ

๐ŸŽจ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: G-code ์ƒ์„ฑ๊ธฐ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์ข€ ๋” ์‹ค์šฉ์ ์ธ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ๊ฐ„๋‹จํ•œ ๋„ํ˜•์„ ์ž…๋ ฅ๋ฐ›์•„์„œ G-code๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด์•ผ! ์ด๊ฑธ ์‘์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ํŒจํ„ด๋„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๐ŸŽช

import math

class GCodeGenerator:
    def __init__(self, filename='output.gcode'):
        self.filename = filename
        self.gcode_lines = []
        self.current_e = 0  # ํ˜„์žฌ ์••์ถœ ์œ„์น˜
        
    def add_comment(self, comment):
        """์ฃผ์„ ์ถ”๊ฐ€"""
        self.gcode_lines.append(f'; {comment}')
        
    def add_command(self, command):
        """G-code ๋ช…๋ น ์ถ”๊ฐ€"""
        self.gcode_lines.append(command)
        
    def start_print(self, nozzle_temp=200, bed_temp=60):
        """ํ”„๋ฆฐํŠธ ์‹œ์ž‘ ์„ค์ •"""
        self.add_comment('=== ํ”„๋ฆฐํŠธ ์‹œ์ž‘ ===')
        self.add_command('G28')  # ํ™ˆ
        self.add_command('G92 E0')  # ์••์ถœ๊ธฐ ๋ฆฌ์…‹
        self.add_command(f'M104 S{nozzle_temp}')  # ๋…ธ์ฆ ์˜จ๋„
        self.add_command(f'M140 S{bed_temp}')  # ๋ฒ ๋“œ ์˜จ๋„
        self.add_command(f'M109 S{nozzle_temp}')  # ์˜จ๋„ ๋Œ€๊ธฐ
        self.add_command(f'M190 S{bed_temp}')
        self.add_command('G1 Z2.0 F3000')  # Z์ถ• ์˜ฌ๋ฆฌ๊ธฐ
        self.add_comment('ํ”„๋ผ์ž„ ๋ผ์ธ ๊ทธ๋ฆฌ๊ธฐ')
        self.add_command('G1 X10 Y10 Z0.3 F5000')
        self.add_command('G1 X100 Y10 E10 F1500')
        self.add_command('G92 E0')
        self.add_comment('')
        
    def end_print(self):
        """ํ”„๋ฆฐํŠธ ์ข…๋ฃŒ ์„ค์ •"""
        self.add_comment('=== ํ”„๋ฆฐํŠธ ์ข…๋ฃŒ ===')
        self.add_command('G91')  # ์ƒ๋Œ€ ์ขŒํ‘œ
        self.add_command('G1 E-2 F2700')  # ๋ฆฌํŠธ๋ž™์…˜
        self.add_command('G1 Z10 F3000')  # Z์ถ• ์˜ฌ๋ฆฌ๊ธฐ
        self.add_command('G90')  # ์ ˆ๋Œ€ ์ขŒํ‘œ
        self.add_command('G1 X0 Y200 F3000')  # ์•ž์œผ๋กœ ์ด๋™
        self.add_command('M104 S0')  # ๋…ธ์ฆ ํžˆํ„ฐ ๋„๊ธฐ
        self.add_command('M140 S0')  # ๋ฒ ๋“œ ํžˆํ„ฐ ๋„๊ธฐ
        self.add_command('M84')  # ๋ชจํ„ฐ ๋„๊ธฐ
        
    def move_to(self, x, y, z=None, speed=3000, extrude=False):
        """์œ„์น˜๋กœ ์ด๋™"""
        command = 'G1'
        command += f' X{x:.3f} Y{y:.3f}'
        if z is not None:
            command += f' Z{z:.3f}'
        
        if extrude:
            # ์ด๋™ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
            distance = math.sqrt(x**2 + y**2)
            extrusion = distance * 0.1  # ๊ฐ„๋‹จํ•œ ์••์ถœ๋Ÿ‰ ๊ณ„์‚ฐ
            self.current_e += extrusion
            command += f' E{self.current_e:.5f}'
            
        command += f' F{speed}'
        self.add_command(command)
        
    def draw_circle(self, center_x, center_y, radius, z_height=0.2, segments=36):
        """์› ๊ทธ๋ฆฌ๊ธฐ"""
        self.add_comment(f'์› ๊ทธ๋ฆฌ๊ธฐ: ์ค‘์‹ฌ({center_x}, {center_y}), ๋ฐ˜์ง€๋ฆ„ {radius}mm')
        
        for i in range(segments + 1):
            angle = (2 * math.pi * i) / segments
            x = center_x + radius * math.cos(angle)
            y = center_y + radius * math.sin(angle)
            
            if i == 0:
                self.move_to(x, y, z_height, extrude=False)
            else:
                self.move_to(x, y, extrude=True)
                
    def draw_rectangle(self, x, y, width, height, z_height=0.2):
        """์ง์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ"""
        self.add_comment(f'์ง์‚ฌ๊ฐํ˜• ๊ทธ๋ฆฌ๊ธฐ: ({x}, {y}), {width}x{height}mm')
        
        corners = [
            (x, y),
            (x + width, y),
            (x + width, y + height),
            (x, y + height),
            (x, y)
        ]
        
        for i, (px, py) in enumerate(corners):
            if i == 0:
                self.move_to(px, py, z_height, extrude=False)
            else:
                self.move_to(px, py, extrude=True)
                
    def draw_star(self, center_x, center_y, outer_radius, inner_radius, 
                  points=5, z_height=0.2):
        """๋ณ„ ๊ทธ๋ฆฌ๊ธฐ"""
        self.add_comment(f'{points}๊ฐ ๋ณ„ ๊ทธ๋ฆฌ๊ธฐ: ์ค‘์‹ฌ({center_x}, {center_y})')
        
        for i in range(points * 2 + 1):
            angle = (2 * math.pi * i) / (points * 2) - math.pi / 2
            radius = outer_radius if i % 2 == 0 else inner_radius
            x = center_x + radius * math.cos(angle)
            y = center_y + radius * math.sin(angle)
            
            if i == 0:
                self.move_to(x, y, z_height, extrude=False)
            else:
                self.move_to(x, y, extrude=True)
                
    def draw_spiral(self, center_x, center_y, start_radius, end_radius, 
                    turns=3, z_height=0.2, segments=100):
        """๋‚˜์„  ๊ทธ๋ฆฌ๊ธฐ"""
        self.add_comment(f'๋‚˜์„  ๊ทธ๋ฆฌ๊ธฐ: {turns}ํšŒ์ „')
        
        for i in range(segments + 1):
            progress = i / segments
            angle = 2 * math.pi * turns * progress
            radius = start_radius + (end_radius - start_radius) * progress
            x = center_x + radius * math.cos(angle)
            y = center_y + radius * math.sin(angle)
            
            if i == 0:
                self.move_to(x, y, z_height, extrude=False)
            else:
                self.move_to(x, y, extrude=True)
                
    def save(self):
        """G-code ํŒŒ์ผ๋กœ ์ €์žฅ"""
        with open(self.filename, 'w') as f:
            f.write('\n'.join(self.gcode_lines))
        print(f'โœ… G-code ์ €์žฅ ์™„๋ฃŒ: {self.filename}')
        print(f'๐Ÿ“Š ์ด {len(self.gcode_lines)}์ค„ ์ƒ์„ฑ๋จ')

# ์‚ฌ์šฉ ์˜ˆ์ œ
generator = GCodeGenerator('pattern.gcode')

# ํ”„๋ฆฐํŠธ ์‹œ์ž‘
generator.start_print(nozzle_temp=200, bed_temp=60)

# ๋‹ค์–‘ํ•œ ๋„ํ˜• ๊ทธ๋ฆฌ๊ธฐ
generator.draw_rectangle(20, 20, 40, 30, z_height=0.2)
generator.draw_circle(100, 50, 20, z_height=0.2)
generator.draw_star(150, 50, 25, 12, points=5, z_height=0.2)
generator.draw_spiral(100, 120, 5, 30, turns=5, z_height=0.2)

# ํ”„๋ฆฐํŠธ ์ข…๋ฃŒ
generator.end_print()

# ํŒŒ์ผ ์ €์žฅ
generator.save()

์ด ์ฝ”๋“œ๋Š” ์›, ์‚ฌ๊ฐํ˜•, ๋ณ„, ๋‚˜์„  ๋“ฑ ๋‹ค์–‘ํ•œ ๋„ํ˜•์„ G-code๋กœ ๋ณ€ํ™˜ํ•ด์ค˜. ์‹ค์ œ๋กœ ์ด ํŒŒ์ผ์„ 3D ํ”„๋ฆฐํ„ฐ์— ๋„ฃ์œผ๋ฉด ๋ฉ‹์ง„ ํŒจํ„ด์ด ๊ทธ๋ ค์งˆ ๊ฑฐ์•ผ! ๐ŸŒŸ

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ: ์‹ค์ œ ํ”„๋ฆฐํ„ฐ์—์„œ ํ…Œ์ŠคํŠธํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ํ”„๋ฆฐํ„ฐ ์„ค์ •(๋ฒ ๋“œ ํฌ๊ธฐ, ๋…ธ์ฆ ์ง๊ฒฝ ๋“ฑ)์„ ํ™•์ธํ•˜๊ณ , ์ฒ˜์Œ์—๋Š” ๋‚ฎ์€ ์†๋„๋กœ ํ…Œ์ŠคํŠธํ•ด๋ด!

๐ŸŒ ์›น ๊ธฐ๋ฐ˜ ์ œ์–ด ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ

์ด์ œ ์ข€ ๋” ๋ฉ‹์ง„ ๊ฑธ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? Flask๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ 3D ํ”„๋ฆฐํ„ฐ๋ฅผ ์ œ์–ดํ•˜๋Š” ์‹œ์Šคํ…œ์ด์•ผ! ์Šค๋งˆํŠธํฐ์œผ๋กœ๋„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ •๋ง ํŽธ๋ฆฌํ•ด. ๐Ÿ“ฑ

from flask import Flask, render_template, request, jsonify
import serial
import threading
import time

app = Flask(__name__)

class WebPrinterController:
    def __init__(self, port='/dev/ttyUSB0', baudrate=115200):
        self.port = port
        self.baudrate = baudrate
        self.ser = None
        self.is_connected = False
        self.status = {
            'temperature': {'nozzle': 0, 'bed': 0},
            'position': {'x': 0, 'y': 0, 'z': 0},
            'state': 'idle'
        }
        self.lock = threading.Lock()
        
    def connect(self):
        """ํ”„๋ฆฐํ„ฐ ์—ฐ๊ฒฐ"""
        try:
            self.ser = serial.Serial(self.port, self.baudrate, timeout=1)
            time.sleep(2)
            self.is_connected = True
            # ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง ์Šค๋ ˆ๋“œ ์‹œ์ž‘
            threading.Thread(target=self._monitor_status, daemon=True).start()
            return True
        except Exception as e:
            print(f"์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
            return False
            
    def disconnect(self):
        """์—ฐ๊ฒฐ ์ข…๋ฃŒ"""
        if self.ser:
            self.ser.close()
            self.is_connected = False
            
    def send_command(self, command):
        """๋ช…๋ น ์ „์†ก"""
        if not self.is_connected:
            return {'success': False, 'message': 'ํ”„๋ฆฐํ„ฐ๊ฐ€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค'}
            
        try:
            with self.lock:
                self.ser.write(f"{command}\n".encode())
                time.sleep(0.1)
                response = self.ser.readline().decode().strip()
                return {'success': True, 'response': response}
        except Exception as e:
            return {'success': False, 'message': str(e)}
            
    def _monitor_status(self):
        """์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง (๋ฐฑ๊ทธ๋ผ์šด๋“œ)"""
        while self.is_connected:
            try:
                # ์˜จ๋„ ํ™•์ธ
                self.send_command('M105')
                # ์œ„์น˜ ํ™•์ธ
                self.send_command('M114')
                time.sleep(2)
            except:
                pass
                
    def home(self, axes=''):
        """ํ™ˆ ํฌ์ง€์…˜"""
        command = f'G28 {axes}'
        return self.send_command(command)
        
    def move(self, x=None, y=None, z=None, speed=3000):
        """์ด๋™"""
        command = 'G1'
        if x is not None:
            command += f' X{x}'
        if y is not None:
            command += f' Y{y}'
        if z is not None:
            command += f' Z{z}'
        command += f' F{speed}'
        return self.send_command(command)
        
    def set_temperature(self, nozzle=None, bed=None):
        """์˜จ๋„ ์„ค์ •"""
        results = []
        if nozzle is not None:
            results.append(self.send_command(f'M104 S{nozzle}'))
        if bed is not None:
            results.append(self.send_command(f'M140 S{bed}'))
        return results

# ์ „์—ญ ์ปจํŠธ๋กค๋Ÿฌ ์ธ์Šคํ„ด์Šค
controller = WebPrinterController()

@app.route('/')
def index():
    """๋ฉ”์ธ ํŽ˜์ด์ง€"""
    return '''
    
    
    
        <title>3D Printer Web Control</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
       
    
        <h1>๐Ÿ–จ๏ธ 3D Printer Web Control</h1>
        
        <div class="control-panel">
            <h2>์—ฐ๊ฒฐ ๊ด€๋ฆฌ</h2>
            <button class="button" onclick="connect()">์—ฐ๊ฒฐ</button>
            <button class="button" onclick="disconnect()">์—ฐ๊ฒฐ ํ•ด์ œ</button>
            <div class="status" id="connection-status">์ƒํƒœ: ์—ฐ๊ฒฐ ์•ˆ๋จ</div>
        </div>
        
        <div class="control-panel">
            <h2>์ด๋™ ์ œ์–ด</h2>
            <button class="button" onclick="home()">๐Ÿ  ํ™ˆ</button>
            <button class="button" onclick="homeZ()">๐Ÿ  Z ํ™ˆ</button>
            <br><br>
            X: <input type="number" id="move-x" value="0">
            Y: <input type="number" id="move-y" value="0">
            Z: <input type="number" id="move-z" value="0">
            <button class="button" onclick="move()">์ด๋™</button>
        </div>
        
        <div class="control-panel">
            <h2>์˜จ๋„ ์ œ์–ด</h2>
            ๋…ธ์ฆ: <input type="number" id="temp-nozzle" value="200">ยฐC
            ๋ฒ ๋“œ: <input type="number" id="temp-bed" value="60">ยฐC
            <button class="button" onclick="setTemperature()">์„ค์ •</button>
            <button class="button" onclick="coolDown()">๋ƒ‰๊ฐ</button>
        </div>
        
        <div class="control-panel">
            <h2>์ปค์Šคํ…€ ๋ช…๋ น</h2>
            <input type="text" id="custom-command" placeholder="G-code ์ž…๋ ฅ" style="width: 300px;">
            <button class="button" onclick="sendCustom()">์ „์†ก</button>
            <div class="status" id="response"></div>
        </div>
        
        <script>
            function connect() {
                fetch('/api/connect', {method: 'POST'})
                    .then(r => r.json())
                    .then(data => {
                        document.getElementById('connection-status').innerText = 
                            data.success ? '์ƒํƒœ: ์—ฐ๊ฒฐ๋จ โœ…' : '์ƒํƒœ: ์—ฐ๊ฒฐ ์‹คํŒจ โŒ';
                    });
            }
            
            function disconnect() {
                fetch('/api/disconnect', {method: 'POST'})
                    .then(r => r.json())
                    .then(data => {
                        document.getElementById('connection-status').innerText = 
                            '์ƒํƒœ: ์—ฐ๊ฒฐ ํ•ด์ œ๋จ';
                    });
            }
            
            function home() {
                fetch('/api/home', {method: 'POST'});
            }
            
            function homeZ() {
                fetch('/api/home?axes=Z', {method: 'POST'});
            }
            
            function move() {
                const x = document.getElementById('move-x').value;
                const y = document.getElementById('move-y').value;
                const z = document.getElementById('move-z').value;
                fetch('/api/move', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({x: x, y: y, z: z})
                });
            }
            
            function setTemperature() {
                const nozzle = document.getElementById('temp-nozzle').value;
                const bed = document.getElementById('temp-bed').value;
                fetch('/api/temperature', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({nozzle: nozzle, bed: bed})
                });
            }
            
            function coolDown() {
                fetch('/api/temperature', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({nozzle: 0, bed: 0})
                });
            }
            
            function sendCustom() {
                const command = document.getElementById('custom-command').value;
                fetch('/api/command', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({command: command})
                })
                .then(r => r.json())
                .then(data => {
                    document.getElementById('response').innerText = 
                        '์‘๋‹ต: ' + data.response;
                });
            }
        </script>
    
    
    '''

@app.route('/api/connect', methods=['POST'])
def api_connect():
    success = controller.connect()
    return jsonify({'success': success})

@app.route('/api/disconnect', methods=['POST'])
def api_disconnect():
    controller.disconnect()
    return jsonify({'success': True})

@app.route('/api/home', methods=['POST'])
def api_home():
    axes = request.args.get('axes', '')
    result = controller.home(axes)
    return jsonify(result)

@app.route('/api/move', methods=['POST'])
def api_move():
    data = request.json
    result = controller.move(
        x=data.get('x'),
        y=data.get('y'),
        z=data.get('z')
    )
    return jsonify(result)

@app.route('/api/temperature', methods=['POST'])
def api_temperature():
    data = request.json
    results = controller.set_temperature(
        nozzle=data.get('nozzle'),
        bed=data.get('bed')
    )
    return jsonify({'success': True, 'results': results})

@app.route('/api/command', methods=['POST'])
def api_command():
    data = request.json
    result = controller.send_command(data['command'])
    return jsonify(result)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

์ด ์›น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:5000์œผ๋กœ ์ ‘์†ํ•ด์„œ ํ”„๋ฆฐํ„ฐ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด! ์Šค๋งˆํŠธํฐ์œผ๋กœ๋„ ๊ฐ™์€ ๋„คํŠธ์›Œํฌ์—์„œ ์ ‘์† ๊ฐ€๋Šฅํ•˜์ง€. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€ ์•Š์•„? ๐ŸŽ‰

๐Ÿ›ก๏ธ ์•ˆ์ „ํ•œ ์ œ์–ด๋ฅผ ์œ„ํ•œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

3D ํ”„๋ฆฐํ„ฐ๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ์ œ์–ดํ•  ๋•Œ๋Š” ์•ˆ์ „์ด ์ •๋ง ์ค‘์š”ํ•ด. ์ž˜๋ชป๋œ ๋ช…๋ น์œผ๋กœ ํ”„๋ฆฐํ„ฐ๊ฐ€ ์†์ƒ๋  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ๊ผญ ์ฒดํฌํ•ด์ค˜! ๐Ÿ”’

โœ… ํ•„์ˆ˜ ์•ˆ์ „ ์ฒดํฌ์‚ฌํ•ญ

1. ์ด๋™ ๋ฒ”์œ„ ์ œํ•œ
ํ”„๋ฆฐํ„ฐ์˜ ์ตœ๋Œ€ ์ด๋™ ๋ฒ”์œ„๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๋„๋ก ๊ฒ€์ฆ ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด.

class SafePrinterController:
    def __init__(self, max_x=200, max_y=200, max_z=200):
        self.max_x = max_x
        self.max_y = max_y
        self.max_z = max_z
        
    def validate_position(self, x, y, z):
        """์œ„์น˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ"""
        if x < 0 or x > self.max_x:
            raise ValueError(f"X ์ขŒํ‘œ ๋ฒ”์œ„ ์ดˆ๊ณผ: {x} (0-{self.max_x})")
        if y < 0 or y > self.max_y:
            raise ValueError(f"Y ์ขŒํ‘œ ๋ฒ”์œ„ ์ดˆ๊ณผ: {y} (0-{self.max_y})")
        if z < 0 or z > self.max_z:
            raise ValueError(f"Z ์ขŒํ‘œ ๋ฒ”์œ„ ์ดˆ๊ณผ: {z} (0-{self.max_z})")
        return True
        
    def safe_move(self, x, y, z):
        """์•ˆ์ „ํ•œ ์ด๋™"""
        try:
            self.validate_position(x, y, z)
            # ์ด๋™ ๋ช…๋ น ์‹คํ–‰
            print(f"โœ… ์•ˆ์ „ํ•œ ์ด๋™: X{x} Y{y} Z{z}")
        except ValueError as e:
            print(f"โŒ ์ด๋™ ๊ฑฐ๋ถ€: {e}")

2. ์˜จ๋„ ์ œํ•œ
๊ณผ๋„ํ•œ ์˜จ๋„ ์„ค์ •์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ด. ํ•„๋ผ๋ฉ˜ํŠธ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ์˜จ๋„ ๋ฒ”์œ„๊ฐ€ ์žˆ๊ฑฐ๋“ .

TEMP_LIMITS = {
    'PLA': {'nozzle': (180, 220), 'bed': (50, 70)},
    'ABS': {'nozzle': (220, 250), 'bed': (80, 110)},
    'PETG': {'nozzle': (220, 250), 'bed': (70, 90)},
    'TPU': {'nozzle': (210, 230), 'bed': (40, 60)}
}

def validate_temperature(material, nozzle_temp, bed_temp):
    """์˜จ๋„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ"""
    if material not in TEMP_LIMITS:
        raise ValueError(f"์ง€์›ํ•˜์ง€ ์•Š๋Š” ์žฌ๋ฃŒ: {material}")
        
    limits = TEMP_LIMITS[material]
    
    if not (limits['nozzle'][0] <= nozzle_temp <= limits['nozzle'][1]):
        raise ValueError(
            f"{material} ๋…ธ์ฆ ์˜จ๋„ ๋ฒ”์œ„: "
            f"{limits['nozzle'][0]}-{limits['nozzle'][1]}ยฐC"
        )
        
    if not (limits['bed'][0] <= bed_temp <= limits['bed'][1]):
        raise ValueError(
            f"{material} ๋ฒ ๋“œ ์˜จ๋„ ๋ฒ”์œ„: "
            f"{limits['bed'][0]}-{limits['bed'][1]}ยฐC"
        )
        
    return True

3. ๊ธด๊ธ‰ ์ •์ง€ ๊ธฐ๋Šฅ
๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ฆ‰์‹œ ํ”„๋ฆฐํ„ฐ๋ฅผ ๋ฉˆ์ถœ ์ˆ˜ ์žˆ์–ด์•ผ ํ•ด.

def emergency_stop(printer):
    """๊ธด๊ธ‰ ์ •์ง€"""
    print("๐Ÿšจ ๊ธด๊ธ‰ ์ •์ง€ ์‹คํ–‰!")
    printer.send_gcode('M112')  # ๊ธด๊ธ‰ ์ •์ง€
    printer.send_gcode('M104 S0')  # ๋…ธ์ฆ ํžˆํ„ฐ ๋„๊ธฐ
    printer.send_gcode('M140 S0')  # ๋ฒ ๋“œ ํžˆํ„ฐ ๋„๊ธฐ
    printer.send_gcode('M84')  # ๋ชจํ„ฐ ๋น„ํ™œ์„ฑํ™”
    print("โœ… ๋ชจ๋“  ์‹œ์Šคํ…œ ์ •์ง€ ์™„๋ฃŒ")

4. ์—ฐ๊ฒฐ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
ํ†ต์‹ ์ด ๋Š์–ด์กŒ์„ ๋•Œ๋ฅผ ๋Œ€๋น„ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด.

import time

def send_with_retry(printer, command, max_retries=3):
    """์žฌ์‹œ๋„ ๋กœ์ง์ด ์žˆ๋Š” ๋ช…๋ น ์ „์†ก"""
    for attempt in range(max_retries):
        try:
            response = printer.send_gcode(command)
            if 'ok' in response.lower():
                return True
            print(f"โš ๏ธ ์žฌ์‹œ๋„ {attempt + 1}/{max_retries}")
            time.sleep(0.5)
        except Exception as e:
            print(f"โŒ ์ „์†ก ์‹คํŒจ: {e}")
            if attempt < max_retries - 1:
                time.sleep(1)
            else:
                print("๐Ÿšจ ์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜ ์ดˆ๊ณผ")
                return False
    return False

๐ŸŽฏ ์‹ค์ „ ํ™œ์šฉ ์‚ฌ๋ก€

์ด์ œ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ช‡ ๊ฐ€์ง€ ์‚ฌ๋ก€๋ฅผ ๋ณด์—ฌ์ค„๊ฒŒ! ์ด๋Ÿฐ ํ”„๋กœ์ ํŠธ๋“ค์€ ์ •๋ง ์œ ์šฉํ•˜๊ณ  ์žฌ๋ฏธ์žˆ์–ด. ๐Ÿ˜Š

1. ์ž๋™ ๋ฒ ๋“œ ๋ ˆ๋ฒจ๋ง ์‹œ์Šคํ…œ

๋ฒ ๋“œ ๋ ˆ๋ฒจ๋ง์€ 3D ํ”„๋ฆฐํŒ…์—์„œ ์ •๋ง ์ค‘์š”ํ•œ๋ฐ, ์ด๊ฑธ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์–ด!

class AutoBedLeveling:
    def __init__(self, printer):
        self.printer = printer
        self.probe_points = [
            (20, 20),   # ์ขŒ์ธก ํ•˜๋‹จ
            (180, 20),  # ์šฐ์ธก ํ•˜๋‹จ
            (180, 180), # ์šฐ์ธก ์ƒ๋‹จ
            (20, 180),  # ์ขŒ์ธก ์ƒ๋‹จ
            (100, 100)  # ์ค‘์•™
        ]
        
    def probe_point(self, x, y):
        """ํŠน์ • ์ง€์  ํ”„๋กœ๋น™"""
        self.printer.send_gcode(f'G1 X{x} Y{y} Z10 F3000')
        self.printer.send_gcode('G30')  # ํ”„๋กœ๋ธŒ ์‹คํ–‰
        # ํ”„๋กœ๋ธŒ ๊ฒฐ๊ณผ ์ฝ๊ธฐ
        response = self.printer.wait_for_response()
        # Z ๋†’์ด ์ถ”์ถœ (์‘๋‹ต ํŒŒ์‹ฑ)
        return self.parse_z_height(response)
        
    def level_bed(self):
        """๋ฒ ๋“œ ๋ ˆ๋ฒจ๋ง ์‹คํ–‰"""
        print("๐Ÿ”ง ์ž๋™ ๋ฒ ๋“œ ๋ ˆ๋ฒจ๋ง ์‹œ์ž‘...")
        heights = []
        
        for i, (x, y) in enumerate(self.probe_points):
            print(f"๐Ÿ“ ํฌ์ธํŠธ {i+1}/5: ({x}, {y})")
            height = self.probe_point(x, y)
            heights.append(height)
            print(f"   ๋†’์ด: {height:.3f}mm")
            
        # ๋†’์ด ์ฐจ์ด ๊ณ„์‚ฐ
        max_diff = max(heights) - min(heights)
        print(f"\n๐Ÿ“Š ์ตœ๋Œ€ ๋†’์ด ์ฐจ์ด: {max_diff:.3f}mm")
        
        if max_diff > 0.2:
            print("โš ๏ธ ๋ฒ ๋“œ ๋ ˆ๋ฒจ ์กฐ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค!")
            self.suggest_adjustments(heights)
        else:
            print("โœ… ๋ฒ ๋“œ ๋ ˆ๋ฒจ์ด ์–‘ํ˜ธํ•ฉ๋‹ˆ๋‹ค!")
            
    def suggest_adjustments(self, heights):
        """์กฐ์ • ์ œ์•ˆ"""
        avg = sum(heights) / len(heights)
        corners = ['์ขŒํ•˜', '์šฐํ•˜', '์šฐ์ƒ', '์ขŒ์ƒ']
        
        print("\n๐Ÿ”ง ์กฐ์ • ์ œ์•ˆ:")
        for i, (height, corner) in enumerate(zip(heights[:4], corners)):
            diff = height - avg
            if abs(diff) > 0.1:
                direction = "์˜ฌ๋ฆฌ๊ธฐ" if diff < 0 else "๋‚ด๋ฆฌ๊ธฐ"
                print(f"   {corner} ์ฝ”๋„ˆ: {abs(diff):.2f}mm {direction}")

2. ํ”„๋ฆฐํŠธ ์ง„ํ–‰ ์ƒํ™ฉ ๋ชจ๋‹ˆํ„ฐ๋ง

ํ”„๋ฆฐํŠธ๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ง„ํ–‰๋๋Š”์ง€, ๋‚จ์€ ์‹œ๊ฐ„์€ ์–ผ๋งˆ์ธ์ง€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด!

import time
from datetime import datetime, timedelta

class PrintMonitor:
    def __init__(self, printer, gcode_file):
        self.printer = printer
        self.gcode_file = gcode_file
        self.total_lines = 0
        self.current_line = 0
        self.start_time = None
        
    def load_gcode(self):
        """G-code ํŒŒ์ผ ๋กœ๋“œ"""
        with open(self.gcode_file, 'r') as f:
            self.gcode_lines = [
                line.strip() 
                for line in f.readlines() 
                if line.strip() and not line.startswith(';')
            ]
        self.total_lines = len(self.gcode_lines)
        print(f"๐Ÿ“„ G-code ๋กœ๋“œ ์™„๋ฃŒ: {self.total_lines}์ค„")
        
    def print_with_monitoring(self):
        """๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋ฉด์„œ ํ”„๋ฆฐํŠธ"""
        self.start_time = datetime.now()
        
        for i, line in enumerate(self.gcode_lines):
            self.current_line = i + 1
            
            # G-code ์ „์†ก
            self.printer.send_gcode(line)
            self.printer.wait_for_ok()
            
            # 10์ค„๋งˆ๋‹ค ์ง„ํ–‰ ์ƒํ™ฉ ์ถœ๋ ฅ
            if i % 10 == 0:
                self.print_progress()
                
        print("\n๐ŸŽ‰ ํ”„๋ฆฐํŠธ ์™„๋ฃŒ!")
        self.print_summary()
        
    def print_progress(self):
        """์ง„ํ–‰ ์ƒํ™ฉ ์ถœ๋ ฅ"""
        progress = (self.current_line / self.total_lines) * 100
        elapsed = datetime.now() - self.start_time
        
        # ์˜ˆ์ƒ ๋‚จ์€ ์‹œ๊ฐ„ ๊ณ„์‚ฐ
        if progress > 0:
            total_estimated = elapsed / (progress / 100)
            remaining = total_estimated - elapsed
        else:
            remaining = timedelta(0)
            
        # ์ง„ํ–‰ ๋ฐ” ์ƒ์„ฑ
        bar_length = 30
        filled = int(bar_length * progress / 100)
        bar = 'โ–ˆ' * filled + 'โ–‘' * (bar_length - filled)
        
        print(f"\r์ง„ํ–‰: [{bar}] {progress:.1f}% | "
              f"๊ฒฝ๊ณผ: {self.format_time(elapsed)} | "
              f"๋‚จ์Œ: {self.format_time(remaining)}", end='')
              
    def format_time(self, td):
        """์‹œ๊ฐ„ ํฌ๋งทํŒ…"""
        total_seconds = int(td.total_seconds())
        hours = total_seconds // 3600
        minutes = (total_seconds % 3600) // 60
        seconds = total_seconds % 60
        return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
        
    def print_summary(self):
        """ํ”„๋ฆฐํŠธ ์š”์•ฝ"""
        elapsed = datetime.now() - self.start_time
        print(f"\n๐Ÿ“Š ํ”„๋ฆฐํŠธ ์š”์•ฝ:")
        print(f"   ์ด ์‹œ๊ฐ„: {self.format_time(elapsed)}")
        print(f"   ์ด ๋ผ์ธ: {self.total_lines}")
        print(f"   ํ‰๊ท  ์†๋„: {self.total_lines / elapsed.total_seconds():.1f} ์ค„/์ดˆ")

# ์‚ฌ์šฉ ์˜ˆ์ œ
monitor = PrintMonitor(printer, 'model.gcode')
monitor.load_gcode()
monitor.print_with_monitoring()

3. ํ•„๋ผ๋ฉ˜ํŠธ ์„ผ์„œ ํ†ตํ•ฉ

ํ•„๋ผ๋ฉ˜ํŠธ๊ฐ€ ๋–จ์–ด์ง€๋ฉด ์ž๋™์œผ๋กœ ํ”„๋ฆฐํŠธ๋ฅผ ์ผ์‹œ์ •์ง€ํ•˜๋Š” ์‹œ์Šคํ…œ์ด์•ผ!

import RPi.GPIO as GPIO  # ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ์‚ฌ์šฉ์‹œ

class FilamentSensor:
    def __init__(self, printer, sensor_pin=17):
        self.printer = printer
        self.sensor_pin = sensor_pin
        self.is_printing = False
        
        # GPIO ์„ค์ •
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(sensor_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(
            sensor_pin, 
            GPIO.FALLING, 
            callback=self.on_filament_runout,
            bouncetime=300
        )
        
    def on_filament_runout(self, channel):
        """ํ•„๋ผ๋ฉ˜ํŠธ ๋ถ€์กฑ ๊ฐ์ง€"""
        if self.is_printing:
            print("\n๐Ÿšจ ํ•„๋ผ๋ฉ˜ํŠธ ๋ถ€์กฑ ๊ฐ์ง€!")
            self.pause_print()
            self.notify_user()
            
    def pause_print(self):
        """ํ”„๋ฆฐํŠธ ์ผ์‹œ์ •์ง€"""
        print("โธ๏ธ ํ”„๋ฆฐํŠธ ์ผ์‹œ์ •์ง€ ์ค‘...")
        self.printer.send_gcode('M600')  # ํ•„๋ผ๋ฉ˜ํŠธ ๊ต์ฒด ๋ช…๋ น
        # ๋˜๋Š” ์ˆ˜๋™ ์ผ์‹œ์ •์ง€
        self.printer.send_gcode('M25')  # SD ์นด๋“œ ํ”„๋ฆฐํŠธ ์ผ์‹œ์ •์ง€
        self.printer.send_gcode('G91')  # ์ƒ๋Œ€ ์ขŒํ‘œ
        self.printer.send_gcode('G1 Z10 F300')  # Z์ถ• ์˜ฌ๋ฆฌ๊ธฐ
        self.printer.send_gcode('G90')  # ์ ˆ๋Œ€ ์ขŒํ‘œ
        
    def notify_user(self):
        """์‚ฌ์šฉ์ž ์•Œ๋ฆผ"""
        # ์ด๋ฉ”์ผ, ํ‘ธ์‹œ ์•Œ๋ฆผ ๋“ฑ
        print("๐Ÿ“ง ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ์ „์†ก")
        # ์‹ค์ œ๋กœ๋Š” ์ด๋ฉ”์ผ์ด๋‚˜ ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ ๋“ฑ์„ ์‚ฌ์šฉ
        
    def resume_print(self):
        """ํ”„๋ฆฐํŠธ ์žฌ๊ฐœ"""
        print("โ–ถ๏ธ ํ”„๋ฆฐํŠธ ์žฌ๊ฐœ")
        self.printer.send_gcode('M24')  # SD ์นด๋“œ ํ”„๋ฆฐํŠธ ์žฌ๊ฐœ
        self.printer.send_gcode('G91')
        self.printer.send_gcode('G1 Z-10 F300')  # Z์ถ• ์›์œ„์น˜
        self.printer.send_gcode('G90')

๐Ÿ”Œ ๋‹ค์–‘ํ•œ ํŽŒ์›จ์–ด์™€์˜ ํ˜ธํ™˜์„ฑ

3D ํ”„๋ฆฐํ„ฐ๋Š” ๋‹ค์–‘ํ•œ ํŽŒ์›จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ๊ฐ๊ฐ ์•ฝ๊ฐ„์”ฉ ๋‹ค๋ฅธ ๋ช…๋ น์–ด๋ฅผ ์ง€์›ํ•ด. ์ฃผ์š” ํŽŒ์›จ์–ด๋“ค์„ ์•Œ์•„๋ณด์ž! ๐Ÿ”ง

ํŽŒ์›จ์–ด ํŠน์ง• ์ฃผ์š” ์ฐจ์ด์ 
Marlin ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ์˜คํ”ˆ์†Œ์Šค ํŽŒ์›จ์–ด ํ‘œ์ค€ G-code ์™„๋ฒฝ ์ง€์›, M503์œผ๋กœ ์„ค์ • ํ™•์ธ
Klipper ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด ๊ธฐ๋ฐ˜ ๊ณ ์„ฑ๋Šฅ ํŽŒ์›จ์–ด ๋งคํฌ๋กœ ์ง€์›, ๊ณ ์† ํ”„๋ฆฐํŒ… ์ตœ์ ํ™”
RepRapFirmware Duet ๋ณด๋“œ์šฉ ํŽŒ์›จ์–ด ์›น ์ธํ„ฐํŽ˜์ด์Šค ๋‚ด์žฅ, ๊ณ ๊ธ‰ ์„ค์ • ๊ฐ€๋Šฅ
Smoothieware 32๋น„ํŠธ ๋ณด๋“œ์šฉ ํŽŒ์›จ์–ด ๋ถ€๋“œ๋Ÿฌ์šด ๋™์ž‘, ์„ค์ • ํŒŒ์ผ ๊ธฐ๋ฐ˜

๐Ÿ” ํŽŒ์›จ์–ด ๊ฐ์ง€ ์ฝ”๋“œ

def detect_firmware(printer):
    """ํŽŒ์›จ์–ด ์ข…๋ฅ˜ ๊ฐ์ง€"""
    # M115 ๋ช…๋ น์œผ๋กœ ํŽŒ์›จ์–ด ์ •๋ณด ์š”์ฒญ
    response = printer.send_gcode('M115')
    
    if 'FIRMWARE_NAME:Marlin' in response:
        return 'Marlin'
    elif 'FIRMWARE_NAME:Klipper' in response:
        return 'Klipper'
    elif 'FIRMWARE_NAME:RepRapFirmware' in response:
        return 'RepRapFirmware'
    elif 'Smoothie' in response:
        return 'Smoothieware'
    else:
        return 'Unknown'
        
def get_firmware_capabilities(firmware):
    """ํŽŒ์›จ์–ด๋ณ„ ๊ธฐ๋Šฅ ํ™•์ธ"""
    capabilities = {
        'Marlin': {
            'auto_bed_leveling': True,
            'linear_advance': True,
            'thermal_protection': True,
            'power_loss_recovery': True
        },
        'Klipper': {
            'pressure_advance': True,
            'input_shaping': True,
            'macro_support': True,
            'multi_mcu': True
        },
        'RepRapFirmware': {
            'web_interface': True,
            'mesh_bed_leveling': True,
            'object_cancellation': True,
            'filament_sensors': True
        }
    }
    return capabilities.get(firmware, {})

๐ŸŽจ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ: ๋™์  G-code ์ƒ์„ฑ

์ด์ œ ์ •๋ง ๋ฉ‹์ง„ ๊ฑธ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋”ฐ๋ผ ์‹ค์‹œ๊ฐ„์œผ๋กœ G-code๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์‹œ์Šคํ…œ์ด์•ผ! ์˜ˆ๋ฅผ ๋“ค์–ด ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ทธ๊ฑธ 3D๋กœ ํ”„๋ฆฐํŠธํ•˜๋Š” ๊ฑฐ์ง€. ๐Ÿš€

from PIL import Image, ImageDraw, ImageFont
import numpy as np

class TextTo3D:
    def __init__(self, generator):
        self.generator = generator
        
    def text_to_gcode(self, text, font_size=50, layer_height=0.2, 
                      extrusion_width=0.4):
        """ํ…์ŠคํŠธ๋ฅผ G-code๋กœ ๋ณ€ํ™˜"""
        # ์ด๋ฏธ์ง€๋กœ ํ…์ŠคํŠธ ๋ Œ๋”๋ง
        font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 
                                   font_size)
        
        # ํ…์ŠคํŠธ ํฌ๊ธฐ ๊ณ„์‚ฐ
        dummy_img = Image.new('L', (1, 1))
        draw = ImageDraw.Draw(dummy_img)
        bbox = draw.textbbox((0, 0), text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]
        
        # ์‹ค์ œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
        img = Image.new('L', (text_width + 20, text_height + 20), color=0)
        draw = ImageDraw.Draw(img)
        draw.text((10, 10), text, fill=255, font=font)
        
        # ์ด๋ฏธ์ง€๋ฅผ numpy ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
        img_array = np.array(img)
        
        # G-code ์ƒ์„ฑ ์‹œ์ž‘
        self.generator.add_comment(f'ํ…์ŠคํŠธ ํ”„๋ฆฐํŠธ: {text}')
        self.generator.add_command('G92 E0')
        
        # ์ด๋ฏธ์ง€๋ฅผ ์Šค์บ”ํ•˜๋ฉด์„œ G-code ์ƒ์„ฑ
        scale = 0.2  # 1ํ”ฝ์…€ = 0.2mm
        
        for y in range(0, img_array.shape[0], 2):
            line_has_content = False
            start_x = None
            
            for x in range(img_array.shape[1]):
                if img_array[y, x] > 128:  # ๋ฐ์€ ํ”ฝ์…€
                    if not line_has_content:
                        start_x = x
                        line_has_content = True
                        # ์ด๋™ (์••์ถœ ์—†์Œ)
                        self.generator.move_to(
                            x * scale, 
                            y * scale, 
                            z=layer_height,
                            extrude=False
                        )
                    # ์••์ถœํ•˜๋ฉด์„œ ์ด๋™
                    self.generator.move_to(
                        x * scale, 
                        y * scale,
                        extrude=True
                    )
                elif line_has_content:
                    line_has_content = False
                    
        self.generator.add_comment('ํ…์ŠคํŠธ ํ”„๋ฆฐํŠธ ์™„๋ฃŒ')
        
    def create_3d_text(self, text, height=5):
        """3D ํ…์ŠคํŠธ ์ƒ์„ฑ (์—ฌ๋Ÿฌ ๋ ˆ์ด์–ด)"""
        layers = int(height / 0.2)
        
        for layer in range(layers):
            z = layer * 0.2
            self.generator.add_comment(f'๋ ˆ์ด์–ด {layer + 1}/{layers}')
            self.text_to_gcode(text)
            
        print(f"โœ… 3D ํ…์ŠคํŠธ ์ƒ์„ฑ ์™„๋ฃŒ: {layers}๊ฐœ ๋ ˆ์ด์–ด")

# ์‚ฌ์šฉ ์˜ˆ์ œ
generator = GCodeGenerator('text_print.gcode')
text_printer = TextTo3D(generator)

generator.start_print()
text_printer.create_3d_text("HELLO 3D!", height=3)
generator.end_print()
generator.save()

๐Ÿ’ก ์‘์šฉ ์•„์ด๋””์–ด: ์ด ๊ธฐ์ˆ ์„ ์‘์šฉํ•˜๋ฉด QR ์ฝ”๋“œ, ๋กœ๊ณ , ์‹ฌ์ง€์–ด ์‚ฌ์ง„๊นŒ์ง€ 3D๋กœ ํ”„๋ฆฐํŠธํ•  ์ˆ˜ ์žˆ์–ด! ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๊ธฐ์ˆ ๊ณผ ๊ฒฐํ•ฉํ•˜๋ฉด ๋ฌด๊ถ๋ฌด์ง„ํ•œ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์ง€. ๐ŸŽจ

๐ŸŒŸ ์žฌ๋Šฅ๋„ท์—์„œ 3D ํ”„๋ฆฐํŒ… ์žฌ๋Šฅ ๊ณต์œ ํ•˜๊ธฐ

์ด๋ ‡๊ฒŒ ๋ฐฐ์šด G-code ์ œ์–ด ๊ธฐ์ˆ ์„ ์žฌ๋Šฅ๋„ท์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์–ด! ์˜ˆ๋ฅผ ๋“ค์–ด "์ปค์Šคํ…€ G-code ์ƒ์„ฑ ์„œ๋น„์Šค"๋‚˜ "3D ํ”„๋ฆฐํ„ฐ ์ž๋™ํ™” ์‹œ์Šคํ…œ ๊ตฌ์ถ•" ๊ฐ™์€ ์žฌ๋Šฅ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์ง€. ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ 3D ํ”„๋ฆฐํŒ…์„ ๊ฒฐํ•ฉํ•œ ๋…ํŠนํ•œ ์žฌ๋Šฅ์€ ์ •๋ง ๊ฐ€์น˜๊ฐ€ ๋†’์•„! ๐Ÿ’Ž

์žฌ๋Šฅ๋„ท์—์„œ๋Š” ์ด๋Ÿฐ ์ „๋ฌธ์ ์ธ ๊ธฐ์ˆ  ์žฌ๋Šฅ๋“ค์ด ํ™œ๋ฐœํ•˜๊ฒŒ ๊ฑฐ๋ž˜๋˜๊ณ  ์žˆ์–ด. 3D ํ”„๋ฆฐํŒ… ๊ด€๋ จ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ•์˜, ์ปค์Šคํ…€ ์Šฌ๋ผ์ด์„œ ๊ฐœ๋ฐœ, ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ ์ œ์ž‘ ๋“ฑ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ์žฌ๋Šฅ์„ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์ง€. ํŠนํžˆ ์š”์ฆ˜์€ ๊ฐœ์ธ ์ œ์ž‘์ž๋“ค์ด ๋งŽ์•„์ง€๋ฉด์„œ ์ด๋Ÿฐ ๊ธฐ์ˆ ์  ์ง€์›์— ๋Œ€ํ•œ ์ˆ˜์š”๊ฐ€ ์ •๋ง ๋†’์•„์ง€๊ณ  ์žˆ์–ด! ๐Ÿš€

๐Ÿ› ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ

์‹ค์ œ๋กœ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ์— ๋ถ€๋”ชํž ์ˆ˜ ์žˆ์–ด. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

โŒ ๋ฌธ์ œ 1: ์‹œ๋ฆฌ์–ผ ํฌํŠธ ์—ฐ๊ฒฐ ์‹คํŒจ

์ฆ์ƒ:

serial.serialutil.SerialException: could not open port

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:

# Linux/Mac์—์„œ ๊ถŒํ•œ ๋ฌธ์ œ ํ•ด๊ฒฐ
sudo usermod -a -G dialout $USER
# ์žฌ๋กœ๊ทธ์ธ ํ•„์š”

# ํฌํŠธ ์ž๋™ ๊ฐ์ง€
import serial.tools.list_ports

def find_printer_port():
    """ํ”„๋ฆฐํ„ฐ ํฌํŠธ ์ž๋™ ๊ฐ์ง€"""
    ports = serial.tools.list_ports.comports()
    
    for port in ports:
        print(f"๋ฐœ๊ฒฌ๋œ ํฌํŠธ: {port.device} - {port.description}")
        
        # USB ์‹œ๋ฆฌ์–ผ ์žฅ์น˜ ์ฐพ๊ธฐ
        if 'USB' in port.description or 'ACM' in port.device:
            try:
                # ํ…Œ์ŠคํŠธ ์—ฐ๊ฒฐ
                ser = serial.Serial(port.device, 115200, timeout=1)
                ser.close()
                print(f"โœ… ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํฌํŠธ: {port.device}")
                return port.device
            except:
                continue
                
    return None

# ์‚ฌ์šฉ
port = find_printer_port()
if port:
    printer = Printer3D(port)
else:
    print("โŒ ํ”„๋ฆฐํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค")

โŒ ๋ฌธ์ œ 2: ๋ช…๋ น ์‘๋‹ต ์—†์Œ

์ฆ์ƒ: G-code๋ฅผ ๋ณด๋‚ด๋„ ํ”„๋ฆฐํ„ฐ๊ฐ€ ๋ฐ˜์‘ํ•˜์ง€ ์•Š์Œ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:

def send_with_echo(printer, command, timeout=5):
    """์—์ฝ” ํ™•์ธ์ด ์žˆ๋Š” ๋ช…๋ น ์ „์†ก"""
    start_time = time.time()
    
    # ๋ช…๋ น ์ „์†ก
    printer.ser.write(f"{command}\n".encode())
    
    # ์‘๋‹ต ๋Œ€๊ธฐ
    while time.time() - start_time < timeout:
        if printer.ser.in_waiting:
            response = printer.ser.readline().decode().strip()
            print(f"์‘๋‹ต: {response}")
            
            if 'ok' in response.lower():
                return True
            elif 'error' in response.lower():
                print(f"โŒ ์—๋Ÿฌ: {response}")
                return False
                
        time.sleep(0.1)
        
    print("โฑ๏ธ ํƒ€์ž„์•„์›ƒ: ์‘๋‹ต ์—†์Œ")
    return False

โŒ ๋ฌธ์ œ 3: ์ขŒํ‘œ๊ณ„ ํ˜ผ๋ž€

์ฆ์ƒ: ํ”„๋ฆฐํ„ฐ๊ฐ€ ์˜ˆ์ƒ๊ณผ ๋‹ค๋ฅธ ์œ„์น˜๋กœ ์ด๋™

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:

class CoordinateManager:
    def __init__(self):
        self.mode = 'absolute'  # 'absolute' or 'relative'
        self.current_pos = {'X': 0, 'Y': 0, 'Z': 0, 'E': 0}
        
    def set_absolute(self):
        """์ ˆ๋Œ€ ์ขŒํ‘œ ๋ชจ๋“œ"""
        self.mode = 'absolute'
        return 'G90'
        
    def set_relative(self):
        """์ƒ๋Œ€ ์ขŒํ‘œ ๋ชจ๋“œ"""
        self.mode = 'relative'
        return 'G91'
        
    def calculate_move(self, x=None, y=None, z=None):
        """์ด๋™ ๊ณ„์‚ฐ"""
        if self.mode == 'absolute':
            # ์ ˆ๋Œ€ ์ขŒํ‘œ: ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
            return {'X': x, 'Y': y, 'Z': z}
        else:
            # ์ƒ๋Œ€ ์ขŒํ‘œ: ํ˜„์žฌ ์œ„์น˜์—์„œ ๋”ํ•จ
            return {
                'X': self.current_pos['X'] + (x or 0),
                'Y': self.current_pos['Y'] + (y or 0),
                'Z': self.current_pos['Z'] + (z or 0)
            }
            
    def update_position(self, x=None, y=None, z=None):
        """์œ„์น˜ ์—…๋ฐ์ดํŠธ"""
        if x is not None:
            self.current_pos['X'] = x
        if y is not None:
            self.current_pos['Y'] = y
        if z is not None:
            self.current_pos['Z'] = z

โš ๏ธ ์ค‘์š”: ํ•ญ์ƒ ํ”„๋ฆฐํŠธ ์‹œ์ž‘ ์ „์— G90(์ ˆ๋Œ€ ์ขŒํ‘œ)๊ณผ G92 E0(์••์ถœ๊ธฐ ๋ฆฌ์…‹)์„ ์‹คํ–‰ํ•ด์„œ ์ขŒํ‘œ๊ณ„๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

๐Ÿ“š ์ถ”๊ฐ€ ํ•™์Šต ๋ฆฌ์†Œ์Šค

๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ์ฐธ๊ณ ํ•ด๋ด! ๐Ÿ“–

๐Ÿ”— ์œ ์šฉํ•œ ๋งํฌ๋“ค

โ€ข Marlin ๊ณต์‹ ๋ฌธ์„œ: G-code ๋ช…๋ น์–ด ์™„๋ฒฝ ๋ ˆํผ๋Ÿฐ์Šค
โ€ข RepRap Wiki: 3D ํ”„๋ฆฐํŒ… ๊ธฐ์ˆ  ๋ฐฑ๊ณผ์‚ฌ์ „
โ€ข OctoPrint API: ์›น ๊ธฐ๋ฐ˜ ํ”„๋ฆฐํ„ฐ ์ œ์–ด ์‹œ์Šคํ…œ
โ€ข Klipper ๋ฌธ์„œ: ๊ณ ๊ธ‰ ํ”„๋ฆฐํ„ฐ ์ œ์–ด ๊ธฐ๋ฒ•
โ€ข GitHub: ์˜คํ”ˆ์†Œ์Šค 3D ํ”„๋ฆฐํŒ… ํ”„๋กœ์ ํŠธ๋“ค

๐Ÿ“ฆ ์ถ”์ฒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

โ€ข pySerial: ์‹œ๋ฆฌ์–ผ ํ†ต์‹ ์˜ ๊ธฐ๋ณธ
โ€ข printrun: ํ”„๋ฆฐํ„ฐ ์ œ์–ด ์œ ํ‹ธ๋ฆฌํ‹ฐ
โ€ข CuraEngine: ์Šฌ๋ผ์ด์‹ฑ ์—”์ง„
โ€ข trimesh: 3D ๋ฉ”์‹œ ์ฒ˜๋ฆฌ
โ€ข numpy-stl: STL ํŒŒ์ผ ์ฒ˜๋ฆฌ

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์ž, ์—ฌ๊ธฐ๊นŒ์ง€ 3D ํ”„๋ฆฐํ„ฐ G-code ์ œ์–ด์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์€ ๊ฑธ ๋ฐฐ์› ์–ด! ๊ธฐ๋ณธ์ ์ธ ์‹œ๋ฆฌ์–ผ ํ†ต์‹ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์›น ์ธํ„ฐํŽ˜์ด์Šค, ์ž๋™ํ™” ์‹œ์Šคํ…œ, ์‹ฌ์ง€์–ด ํ…์ŠคํŠธ๋ฅผ 3D๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ๊นŒ์ง€ ๋‹ค๋ค˜์ง€. ๐Ÿ˜Š

์ด ๊ธฐ์ˆ ๋“ค์„ ๋งˆ์Šคํ„ฐํ•˜๋ฉด ๋‹จ์ˆœํžˆ STL ํŒŒ์ผ์„ ํ”„๋ฆฐํŠธํ•˜๋Š” ๊ฒƒ์„ ๋„˜์–ด์„œ, ์™„์ „ํžˆ ์ž๋™ํ™”๋œ 3D ํ”„๋ฆฐํŒ… ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์–ด. ์ƒ์ƒํ•ด๋ด - ์›น์—์„œ ์ฃผ๋ฌธ์„ ๋ฐ›์œผ๋ฉด ์ž๋™์œผ๋กœ G-code๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ”„๋ฆฐํ„ฐ๊ฐ€ ์•Œ์•„์„œ ์ถœ๋ ฅํ•˜๊ณ , ์™„๋ฃŒ๋˜๋ฉด ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ์‹œ์Šคํ…œ์„! ๐Ÿš€

ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ ํ•˜๋“œ์›จ์–ด ์ œ์–ด๋ฅผ ๊ฒฐํ•ฉํ•˜๋Š” ๊ฑด ์ •๋ง ์žฌ๋ฏธ์žˆ๊ณ  ๋ณด๋žŒ์ฐฌ ์ผ์ด์•ผ. ์ฒ˜์Œ์—๋Š” ์–ด๋ ค์›Œ ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผํ•˜๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด ์žˆ์„ ๊ฑฐ์•ผ. ๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ๊ธฐ์ˆ ์€ 3D ํ”„๋ฆฐํŒ…๋ฟ๋งŒ ์•„๋‹ˆ๋ผ CNC, ๋ ˆ์ด์ € ์ปคํ„ฐ, ๋กœ๋ด‡ ํŒ” ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—๋„ ์‘์šฉํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ช

ํ˜น์‹œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ฑฐ๋‚˜ ๋” ๊ถ๊ธˆํ•œ ๊ฒŒ ์žˆ๋‹ค๋ฉด, ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜ ์ง์ ‘ ์‹คํ—˜ํ•ด๋ณด๋Š” ๊ฑธ ์ถ”์ฒœํ•ด. ์‹ค์ œ๋กœ ํ”„๋ฆฐํ„ฐ๋ฅผ ์›€์ง์—ฌ๋ณด๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅด๊ณ  ์žฌ๋ฏธ์žˆ๊ฑฐ๋“ ! ์•ˆ์ „๋งŒ ์ž˜ ์ง€ํ‚ค๋ฉด์„œ ๋ง์ด์•ผ. ๐Ÿ˜‰

์ž, ์ด์ œ ๋‹น์‹ ๋„ 3D ํ”„๋ฆฐํ„ฐ ์ œ์–ด ๋งˆ์Šคํ„ฐ๊ฐ€ ๋  ์ค€๋น„๊ฐ€ ๋์–ด! ๋ฉ‹์ง„ ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค์–ด๋ณด๊ณ , ํ˜น์‹œ ์žฌ๋Šฅ๋„ท์—์„œ ์ด ๊ธฐ์ˆ ์„ ๊ณต์œ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ๋„์ „ํ•ด๋ด. ๋‹น์‹ ์˜ ์ฐฝ์˜์ ์ธ ์•„์ด๋””์–ด๊ฐ€ ๋ˆ„๊ตฐ๊ฐ€์—๊ฒŒ๋Š” ์ •๋ง ํฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ! ๐ŸŒŸ

ํ–‰๋ณตํ•œ ํ”„๋ฆฐํŒ… ๋˜๊ธธ ๋ฐ”๋ผ! ํ™”์ดํŒ…! ๐ŸŽ‰๐Ÿ–จ๏ธโœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.