Ver3.0 ๐จ๏ธ 3D ํ๋ฆฐํฐ G-code ์ ์ด ์์ค์ฝ๋ ์์ ๋ก ๋ฐฐ์ฐ๋ ์ค์ ํ๋ก๊ทธ๋๋ฐ ๊ฐ์ด๋

๐จ๏ธ 3D ํ๋ฆฐํฐ G-code ์ ์ด ์์ค์ฝ๋ ์์ ๋ก ๋ฐฐ์ฐ๋ ์ค์ ํ๋ก๊ทธ๋๋ฐ ๊ฐ์ด๋
์ด๋ณด์๋ ์ฝ๊ฒ ๋ฐ๋ผํ๋ G-code ์ ์ด ์๋ฒฝ ๋ง์คํฐ
์๋ ! ๐ ์ค๋์ 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
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.