Для кого: Новички без опыта программирования постпроцессоров Время: 10 минут Результат: Работающий макрос для вашего станка
- ✅ Как устроен постпроцессор
- ✅ Как написать первый макрос
- ✅ Как запустить и проверить
- ✅ Готовые шаблоны для использования
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ CAM-система │ │ Постпроцессор │ │ Станок ЧПУ │
│ (CATIA, NX) │────▶│ + Python макросы │────▶│ (Siemens, │
│ │ │ │ │ Fanuc...) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
APT-файл G-код Обработка
PostProcessor/
├── macros/
│ └── python/
│ ├── base/ # Базовые макросы (не трогать)
│ ├── mmill/ # Макросы для вашего станка
│ └── user/ # Ваши макросы (здесь работаем)
├── configs/
│ └── machines/ # Конфигурации станков
└── docs/ # Документация
Создайте файл macros/python/user/my_first_macro.py:
# -*- coding: ascii -*-
# Мой первый макрос
def execute(context, command):
"""Приветственный макрос"""
context.comment("=== Привет от Python макроса! ===")
context.write("G0 X0 Y0 Z50")| Строка | Что делает |
|---|---|
# -*- coding: ascii -*- |
Кодировка файла (обязательно) |
def execute(context, command): |
Функция макроса (обязательно) |
context.comment(...) |
Вывод комментария в скобках |
context.write(...) |
Вывод G-кода |
context.write("G01 X100") # Вывести G-код
context.comment("Текст") # Вывести комментарийСоздайте файл test.apt:
PARTNO/TEST_PART
GOTO/0, 0, 0
GOTO/100, 50, 10
SPINDL/ON, CLW, 1600
FEDRAT/500
FINI
Откройте командную строку в папке проекта:
dotnet run -- -i test.apt -o output.nc -c siemensОткройте output.nc:
(=== Привет от Python макроса! ===)
N1 G0 X0. Y0. Z50.
(Program continues...)🎉 Готово! Вы написали и запустили первый макрос!
Кэширование LAST_* переменных для модального вывода:
# -*- coding: ascii -*-
def execute(context, command):
feed = command.getNumeric(0, 0)
# Проверка изменения через кэш
if context.cacheHasChanged("LAST_FEED", feed):
context.registers.f = feed
context.writeBlock()
context.cacheSet("LAST_FEED", feed)Автоматический выбор: полное определение или вызов:
# -*- coding: ascii -*-
def execute(context, command):
params = {
'MODE': 1,
'X': 100.0,
'Y': 200.0,
'Z': 50.0
}
# Умный вывод
context.cycleWriteIfDifferent("CYCLE800", params)Результат:
; Первый вызов (полное определение)
CYCLE800(MODE=1, X=100.000, Y=200.000, Z=50.000)
; Второй вызов (те же параметры - только вызов)
CYCLE800()# -*- coding: ascii -*-
def execute(context, command):
x = command.getNumeric(0, 0)
# Установка с форматированием из конфига
context.setNumericValue('X', x)
# Получение отформатированной строки
xStr = context.getFormattedValue('X') # "X100.500"
context.writeBlock()# -*- coding: ascii -*-
def execute(context, command):
# Автоматически использует стиль из конфига
context.comment("Начало операции")
# Siemens: (Начало операции)
# Haas: ; Начало операцииИзменим макрос для обработки координат из APT:
# -*- coding: ascii -*-
# GOTO с координатами
def execute(context, command):
"""Обработка GOTO с координатами"""
# Проверяем наличие параметров
if not command.numeric or len(command.numeric) == 0:
return
# Получаем координаты
x = command.numeric[0]
y = command.numeric[1] if len(command.numeric) > 1 else 0
z = command.numeric[2] if len(command.numeric) > 2 else 0
# Обновляем регистры
context.registers.x = x
context.registers.y = y
context.registers.z = z
# Выводим G-код
context.write(f"G1 X{x:.3f} Y{y:.3f} Z{z:.3f}")APT:
GOTO/100, 50, 10
Вывод:
N1 G1 X100.000 Y50.000 Z10.000Копируйте эти шаблоны в macros/python/user/ и модифицируйте под ваши нужды.
# -*- coding: ascii -*-
# GOTO - Линейное перемещение
def execute(context, command):
"""
APT: GOTO/X, Y, Z
Вывод: G1 X... Y... Z...
"""
if not command.numeric:
return
x = command.numeric[0] if len(command.numeric) > 0 else context.registers.x
y = command.numeric[1] if len(command.numeric) > 1 else context.registers.y
z = command.numeric[2] if len(command.numeric) > 2 else context.registers.z
context.registers.x = x
context.registers.y = y
context.registers.z = z
context.write(f"G1 X{x:.3f} Y{y:.3f} Z{z:.3f}")# -*- coding: ascii -*-
# SPINDL - Управление шпинделем
def execute(context, command):
"""
APT: SPINDL/ON, CLW, 1600
SPINDL/OFF
Вывод: M3 S... / M5
"""
# Получаем обороты
rpm = command.numeric[0] if command.numeric else 0
context.registers.s = rpm
# Определяем состояние
state = "OFF"
if command.minorWords:
for word in command.minorWords:
w = word.upper()
if w in ["ON", "CLW", "CLOCKWISE"]:
state = "CW"
elif w in ["CCLW", "CCW"]:
state = "CCW"
elif w == "OFF":
state = "OFF"
# Вывод команд
if state == "CW":
context.write("M3")
if rpm > 0:
context.write(f"S{int(rpm)}")
elif state == "CCW":
context.write("M4")
if rpm > 0:
context.write(f"S{int(rpm)}")
else:
context.write("M5")# -*- coding: ascii -*-
# COOLNT - Управление охлаждением
def execute(context, command):
"""
APT: COOLNT/ON
COOLNT/FLOOD
COOLNT/MIST
COOLNT/OFF
Вывод: M8 / M7 / M9
"""
state = "FLOOD" # По умолчанию
if command.minorWords:
for word in command.minorWords:
w = word.upper()
if w in ["ON", "FLOOD"]:
state = "FLOOD"
elif w == "MIST":
state = "MIST"
elif w == "OFF":
state = "OFF"
if state == "FLOOD":
context.write("M8")
elif state == "MIST":
context.write("M7")
else:
context.write("M9")# -*- coding: ascii -*-
# FEDRAT - Подача (модальная)
def execute(context, command):
"""
APT: FEDRAT/500
Вывод: F... (только при изменении)
"""
if not command.numeric:
return
feed = command.numeric[0]
# Проверка изменения через StateCache
if context.cacheHasChanged("LAST_FEED", feed):
context.registers.f = feed
context.writeBlock()
context.cacheSet("LAST_FEED", feed)# -*- coding: ascii -*-
# RAPID - Быстрое перемещение
def execute(context, command):
"""
APT: RAPID/X, Y, Z
Вывод: G0 X... Y... Z...
"""
# Устанавливаем тип движения RAPID
context.system.MOTION = "RAPID"
context.currentMotionType = "RAPID"
if not command.numeric:
return
x = command.numeric[0] if len(command.numeric) > 0 else context.registers.x
y = command.numeric[1] if len(command.numeric) > 1 else context.registers.y
z = command.numeric[2] if len(command.numeric) > 2 else context.registers.z
context.registers.x = x
context.registers.y = y
context.registers.z = z
context.write(f"G0 X{x:.3f} Y{y:.3f} Z{z:.3f}")# -*- coding: ascii -*-
# LOADTL - Смена инструмента
def execute(context, command):
"""
APT: LOADTL/5
Вывод: T5 M6
"""
if not command.numeric:
context.warning("LOADTL требует номер инструмента")
return
new_tool = int(command.numeric[0])
# Проверка на тот же инструмент
if context.globalVars.TOOL == new_tool:
return
context.registers.t = new_tool
context.globalVars.TOOL = new_tool
context.write(f"T{new_tool}")
context.write("M6")# -*- coding: ascii -*-
# INIT - Начало программы
def execute(context, command):
"""
APT: PARTNO/NAME
Вывод: Заголовок, начальные G-коды
"""
# Инициализация счетчиков
context.globalVars.SetInt("BLOCK_NUMBER", 1)
context.globalVars.SetInt("BLOCK_INCREMENT", 2)
context.globalVars.SetDouble("LAST_FEED", 0.0)
# Заголовок
context.comment(f"Program: {command.getString(0, 'UNKNOWN')}")
context.comment(f"Date: {context.config.getParameterString('dateTime', 'N/A')}")
# Начальные команды
context.write("G54 G40 G90 G94 G17")
context.write("G0 Z100.")# -*- coding: ascii -*-
# FINI - Конец программы
def execute(context, command):
"""
APT: FINI
Вывод: Отвод, M5, M9, M30
"""
# Отвод по Z
context.write("G0 Z100.")
# Выключение шпинделя
context.write("M5")
# Выключение охлаждения
context.write("M9")
# Конец программы
context.write("M30")# -*- coding: ascii -*-
# CYCLE800 - Поворотная ось (с кэшированием)
def execute(context, command):
"""
APT: CYCLE800/MODE, X, Y, Z, ...
Вывод: CYCLE800(...) с автоматическим кэшированием
"""
params = {
'MODE': command.getNumeric(0, 1),
'X': command.getNumeric(1, 0.0),
'Y': command.getNumeric(2, 0.0),
'Z': command.getNumeric(3, 0.0)
}
# Умный вывод с кэшированием
context.cycleWriteIfDifferent("CYCLE800", params)# -*- coding: ascii -*-
# AXIS - Перемещение с форматированием из конфига
def execute(context, command):
"""
APT: AXIS/X, Y, Z
Вывод: X... Y... Z... с форматом из конфига
"""
if not command.numeric:
return
x = command.getNumeric(0, 0)
y = command.getNumeric(1, 0)
z = command.getNumeric(2, 0)
# Установка значений с форматированием из конфига
context.setNumericValue('X', x)
context.setNumericValue('Y', y)
context.setNumericValue('Z', z)
# Получение отформатированных строк
xStr = context.getFormattedValue('X') # "X100.500"
yStr = context.getFormattedValue('Y') # "Y200.750"
zStr = context.getFormattedValue('Z') # "Z50.250"
context.writeBlock()context.write("G01 X100") # С номером блока: N10 G01 X100
context.write("G01 X100", True) # Без номера блока: G01 X100
context.comment("Начало обработки") # (Начало обработки)
context.warning("Предупреждение!") # (WARNING: Предупреждение!)
context.writeln() # Пустая строка# Числовые параметры
if command.numeric and len(command.numeric) > 0:
x = command.numeric[0]
# С default значением
x = command.getNumeric(0, 0.0)
y = command.getNumeric(1, context.registers.y)
# Ключевые слова
if command.hasMinorWord("on"):
context.write("M3")
# Перебор ключевых слов
if command.minorWords:
for word in command.minorWords:
if word.upper() == "CLW":
...# Чтение
x = context.registers.x
feed = context.registers.f
# Запись
context.registers.x = 100.5
context.registers.f = 500.0
context.registers.s = 2000
context.registers.t = 5# Чтение
last_feed = context.globalVars.GetDouble("LAST_FEED", 0.0)
counter = context.globalVars.GetInt("COUNTER", 0)
# Запись
context.globalVars.SetDouble("LAST_FEED", 500.0)
context.globalVars.SetInt("COUNTER", 10)# Проверка изменения
if context.cacheHasChanged("LAST_FEED", feed):
context.writeBlock()
context.cacheSet("LAST_FEED", feed)
# Сброс кэша
context.cacheReset("LAST_FEED")# Умный вывод цикла
params = {'X': 100.0, 'Y': 200.0}
context.cycleWriteIfDifferent("CYCLE800", params)
# Принудительный полный вывод
context.cycleForceWrite("CYCLE800", params)
# Очистка кэша цикла
context.cycleCacheClear("CYCLE800")# Установка значения
context.setNumericValue('X', 100.5)
# Получение отформатированной строки
xStr = context.getFormattedValue('X') # "X100.500"
# Запись в блок
context.writeBlock()# Комментарий (стиль из конфига)
context.comment("Начало операции")
# Примечание
context.note("Примечание")
# Предупреждение
context.warning("Внимание!")def execute(context, command):
context.comment(f"DEBUG: {command.majorWord}")
context.comment(f"DEBUG: numeric={command.numeric}")
context.comment(f"DEBUG: minorWords={command.minorWords}")dotnet run -- -i test.apt -o output.nc -c siemens --debug- Файл макроса в папке
macros/python/user/ - Файл называется
*.py - Есть функция
def execute(context, command): - Кодировка
# -*- coding: ascii -*-в начале - Проверка наличия параметров перед использованием
- Обновление регистров после изменения координат
- PYTHON_MACROS_GUIDE.md — полное руководство по макросам
- CONFIGURATION_GUIDE.md — настройка конфигов
- ARCHITECTURE.md — архитектура постпроцессора
- StateCache для модального вывода
- CycleCache для кэширования циклов
- NumericNCWord для форматирования
- TextNCWord для комментариев
Изучите готовые макросы в macros/python/base/:
goto.py— линейные перемещенияspindl.py— управление шпинделемcoolnt.py— охлаждениеcycle81.py— циклы сверления
A: Проверьте:
- Файл в правильной папке?
- Есть функция
execute? - Нет синтаксических ошибок?
A: Добавьте отладочный вывод:
context.comment(f"DEBUG: numeric={command.numeric}")
context.comment(f"DEBUG: minorWords={command.minorWords}")A: Да, стандартные библиотеки работают:
import math
angle = math.degrees(math.atan2(j, k))A: Положите макрос в macros/python/mmill/ вместо user/.
Добавьте проверку безопасности по Z:
if z < 5:
context.warning(f"Z={z} ниже безопасной высоты!")
z = 5if rpm > 1000:
context.write("G04 P0.5") # Пауза перед стартом# DWELL MACRO
# APT: DWELL/2.5
# Вывод: G04 P2.5Удачи в написании макросов! 🚀