LAB302
từng bước vững vàng, mở lối tương lai
UPLINK ESTABLISHED // Phần mềm kỹ thuật > App script
REC_20260611_admin

Hướng dẫn tích hợp chatbot vào autocad với python

DATA_CORE_READY

Trong kỷ nguyên trí tuệ nhân tạo đang bùng nổ mạnh mẽ, việc ứng dụng AI vào các công việc kỹ thuật không còn là điều xa vời. Bài viết này sẽ hướng dẫn bạn xây dựng một hệ thống chatbot thông minh tích hợp trực tiếp vào AutoCAD — phần mềm thiết kế kỹ thuật hàng đầu thế giới — thông qua ngôn ngữ lập trình Python.
Thay vì phải gõ từng lệnh, chọn từng công cụ, hay viết script thủ công, bạn chỉ cần mô tả bằng ngôn ngữ tự nhiên điều mình muốn vẽ, chatbot sẽ hiểu và gợi ý code python, sau đó thực thi trực tiếp lên bản vẽ trên AutoCAD trong tích tắc.

Tuy nhiên, cần lưu ý rằng đây là một dự án mang tính khám phá và thử nghiệm — hãy xem nó như một trò chơi thú vị để hiểu cách AI và CAD có thể "nói chuyện" với nhau, hơn là một công cụ thay thế hoàn toàn cho quy trình thiết kế chuyên nghiệp. Đừng lạm dụng quá mức, vì AI vẫn có thể sinh ra code sai hoặc bản vẽ không đúng tiêu chuẩn kỹ thuật — người dùng vẫn cần kiểm tra và chịu trách nhiệm với kết quả cuối cùng.

Cài đặt môi trường làm việc
  • Trước hết, bạn cần cài đặt môi trường làm việc là python, tải trực tiếp từ trang chủ.
  • Hoặc có thể sử dụng thông qua laragon, tuy nhiên cần lưu ý rằng phiên bản python trong laragon đã quá cũ rồi nên có thể có nhiều thư viện không còn tương thích nữa.
  • Tải xuống laragon: Tại đây - Khuyến nghị cài đặt phiên bản 6.0.0

Cài đặt thư viện cần thiết
pip install pyautocad==0.2.0
pip install requests==2.27.1
pip install comtypes==1.1.10
pip install pywin32
Điều khiển autocad
  • Tạo một tệp tin trong \laragon\www, ví dụ: draw.groq.py.
  • Copy toàn bộ code trên và dán vào trình duyệt mã nguồn của bạn, ví dụ Notepad++. Lưu lại.
  • Sửa API key phù hợp, (với chatbot loại khác, bạn làm tương tự).
  • Mở cửa sổ Terminal/cmd thông qua laragon, chạy: py draw.groq.py.
  • Sau đó quét vùng bản vẽ cần hỏi bot, và đặt câu hỏi cho bot.
  • Kết quả:

Mã nguồn tham khảo

Groq
import json, requests, re, time, math
from pyautocad import Autocad, APoint

# ================== CẤU HÌNH ==================
acad = Autocad(create_if_not_exists=True, visible=True)

GROQ_API_KEY = "nhập API key của bạn vào đây"
API_URL = "https://api.groq.com/openai/v1/chat/completions"
MODEL = "llama-3.3-70b-versatile"
# =============================================

# ---- Danh sách fix lỗi API sai phổ biến do AI sinh ra ----
CODE_FIXES = [
    # add_polyline → dùng AddLightWeightPolyline qua win32com (không có trong pyautocad)
    # Thay bằng wrapper an toàn gọi hàm draw_polyline bên dưới
    (r'acad\.model\.add_polyline\(', 'draw_polyline(acad, '),
    (r'acad\.ActiveDocument\.ModelSpace\.AddPolyline\(', 'draw_polyline(acad, '),
    # APoint nhầm sang tuple
    (r'APoint\((\d+\.?\d*),\s*(\d+\.?\d*),\s*(\d+\.?\d*)\)', r'APoint(\1, \2, \3)'),
    # add_line sai
    (r'acad\.model\.add_line\(', 'acad.model.AddLine('),
    # add_circle sai
    (r'acad\.model\.add_circle\(', 'acad.model.AddCircle('),
    # add_text sai
    (r'acad\.model\.add_text\(', 'acad.model.AddText('),
    # add_mtext sai
    (r'acad\.model\.add_mtext\(', 'acad.model.AddMText('),
    # Sai hàm zoom
    (r'acad\.ZoomExtents\(\)', 'acad.app.ZoomExtents()'),
    # acad.Application thay bằng acad.app
    (r'acad\.Application\.', 'acad.app.'),
]

HELPER_CODE = """
import win32com.client
import array as arr_module

def draw_polyline(acad, points_list):
    \"\"\"Vẽ polyline từ danh sách điểm [(x,y), (x,y), ...]\"\"\"
    flat = []
    for p in points_list:
        flat.extend([float(p[0]), float(p[1])])
    pts = arr_module.array('d', flat)
    return acad.model.AddLightWeightPolyline(pts)

def draw_rect(acad, x, y, w, h):
    \"\"\"Vẽ hình chữ nhật từ góc trái-dưới (x,y), width w, height h\"\"\"
    pts = [(x,y),(x+w,y),(x+w,y+h),(x,y+h),(x,y)]
    pl = draw_polyline(acad, pts)
    pl.Closed = True
    return pl

def draw_line(acad, x1, y1, x2, y2):
    return acad.model.AddLine(APoint(x1,y1,0), APoint(x2,y2,0))

def draw_circle(acad, cx, cy, r):
    return acad.model.AddCircle(APoint(cx,cy,0), r)

def draw_text(acad, text, x, y, height=2):
    return acad.model.AddText(text, APoint(x,y,0), height)

def draw_arrow(acad, x1, y1, x2, y2):
    \"\"\"Vẽ đường thẳng có mũi tên cuối\"\"\"
    line = draw_line(acad, x1, y1, x2, y2)
    # Mũi tên đơn giản
    import math
    angle = math.atan2(y2-y1, x2-x1)
    alen = 3
    aangle = math.pi/6
    ax1 = x2 - alen*math.cos(angle-aangle)
    ay1 = y2 - alen*math.sin(angle-aangle)
    ax2 = x2 - alen*math.cos(angle+aangle)
    ay2 = y2 - alen*math.sin(angle+aangle)
    draw_line(acad, x2, y2, ax1, ay1)
    draw_line(acad, x2, y2, ax2, ay2)
    return line
"""

SYSTEM_PROMPT = """Bạn là chuyên gia AutoCAD automation dùng Python + pyautocad.

=== CÁC HÀM HELPER SẴN CÓ (ĐƯỢC INJECT TỰ ĐỘNG, DÙNG TRỰC TIẾP) ===
- draw_rect(acad, x, y, w, h)          → vẽ hình chữ nhật
- draw_line(acad, x1, y1, x2, y2)      → vẽ đường thẳng
- draw_circle(acad, cx, cy, r)         → vẽ hình tròn
- draw_text(acad, text, x, y, height)  → thêm text
- draw_arrow(acad, x1, y1, x2, y2)    → vẽ đường có mũi tên
- draw_polyline(acad, [(x,y),...])     → vẽ polyline từ list điểm

=== API PYAUTOCAD ĐÚNG ===
- acad.model.AddLine(APoint(x1,y1,0), APoint(x2,y2,0))
- acad.model.AddCircle(APoint(cx,cy,0), radius)
- acad.model.AddText("text", APoint(x,y,0), height)
- APoint(x, y, z)  ← luôn có 3 tham số

=== TUYỆT ĐỐI KHÔNG DÙNG ===
- add_polyline(), add_line(), add_circle(), add_text()  ← SAI, không tồn tại
- acad.model.AddPolyline()  ← SAI
- acad.ZoomExtents()  ← SAI, phải là acad.app.ZoomExtents()

=== YÊU CẦU ===
- Code phải chạy được ngay, KHÔNG cần import thêm
- Biến acad đã được tạo sẵn: acad = Autocad()
- Đặt toàn bộ code trong 1 block ```python ... ```
- KHÔNG dùng hàm không có trong danh sách trên
"""

def remove_markdown(text):
    text = re.sub(r'```[\s\S]*?```', '', text)
    text = re.sub(r'`([^`]+)`', r'\1', text)
    text = re.sub(r'\*\*(.+?)\*\*', r'\1', text)
    text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE)
    return text.strip()

def extract_python_code(response_text):
    """Trích xuất code Python từ câu trả lời"""
    match = re.search(r'```(?:python|py)?\s*(.+?)\s*```', response_text, re.DOTALL | re.IGNORECASE)
    if match:
        return match.group(1).strip()
    return None

def sanitize_code(code_str):
    """Tự động sửa các lỗi API phổ biến trong code AI sinh ra"""
    for pattern, replacement in CODE_FIXES:
        code_str = re.sub(pattern, replacement, code_str)
    return code_str

def run_pyautocad_code(code_str):
    """Chạy code Python (pyautocad) với helper functions được inject"""
    try:
        # Inject helper code
        full_code = HELPER_CODE + "\n" + code_str

        # Đảm bảo có import cần thiết
        header = ""
        if "from pyautocad" not in full_code and "import pyautocad" not in full_code:
            header = "from pyautocad import Autocad, APoint\n"
        if "acad = Autocad" not in full_code:
            header += "acad = Autocad()\n"

        full_code = header + full_code

        print("\n--- Code sẽ chạy ---")
        print(full_code[:800], "...(truncated)" if len(full_code) > 800 else "")
        print("--------------------\n")

        exec(full_code, {"acad": acad, "APoint": APoint, "__builtins__": __builtins__})
        print("Đã chạy xong code vẽ!")

        # Zoom extents sau khi vẽ
        try:
            acad.app.ZoomExtents()
        except Exception:
            pass

        return True
    except Exception as e:
        print(f"Lỗi khi chạy code: {str(e)}")

        # Gợi ý debug
        if "not found" in str(e).lower() or "attribute" in str(e).lower():
            print("Gợi ý: Có thể dùng hàm không tồn tại. Kiểm tra code và dùng helper functions.")
        elif "apoint" in str(e).lower():
            print("Gợi ý: APoint cần 3 tham số: APoint(x, y, 0)")
        elif "dispatch" in str(e).lower() or "com" in str(e).lower():
            print("Gợi ý: AutoCAD chưa mở hoặc không phản hồi. Hãy mở AutoCAD trước.")
        return False

def ask_groq(user_text):
    prompt_text = f"""
{SYSTEM_PROMPT}

Người dùng mô tả ý tưởng vẽ:
"{user_text}"

Hãy:
1. Giải thích ngắn gọn bằng tiếng Việt sẽ vẽ gì.
2. Viết code Python dùng các hàm helper ở trên để vẽ trong block ```python.
"""

    payload = {
        "model": MODEL,
        "messages": [{"role": "user", "content": prompt_text}],
        "temperature": 0.4,
        "max_tokens": 2500
    }

    headers = {
        "Authorization": f"Bearer {GROQ_API_KEY}",
        "Content-Type": "application/json"
    }

    try:
        response = requests.post(API_URL, json=payload, headers=headers, timeout=40)
        if response.status_code == 200:
            return response.json()['choices'][0]['message']['content']
        else:
            return f"Lỗi API ({response.status_code}): {response.text}"
    except Exception as e:
        return f"Lỗi kết nối: {str(e)}"


# ===================== MAIN =====================
print("\n=== Groq AI - Vẽ bằng Python pyautocad ===")
print("Đã kết nối AutoCAD:", acad.doc.Name if acad.doc else "Chưa có file")

while True:
    user_input = input("\nNhập ý tưởng vẽ (gõ 'exit' để thoát): ").strip()

    if user_input.lower() in ['exit', 'thoát', 'quit', 'q']:
        print("Đã thoát chương trình.")
        break

    if not user_input:
        continue

    print(f"\nĐang hỏi Groq về: {user_input}")
    ai_response = ask_groq(user_input)

    clean_text = remove_markdown(ai_response)
    print("\n=== Groq gợi ý ===")
    print(clean_text)
    print("==================")

    # Trích xuất và sanitize code
    python_code = extract_python_code(ai_response)

    if python_code:
        python_code = sanitize_code(python_code)
        print("\n=> Phát hiện code Python.")
        #choice = input("Bạn muốn chạy tự động? (y/n/s để xem code): ").strip().lower()
        choice = 'y'

        if choice == 's':
            print("\n--- Code (sau sanitize) ---")
            print(python_code)
            print("--------------------------")
            #choice = input("Chạy code này? (y/n): ").strip().lower()
            choice = 'y'
            

        if choice == 'y':
            run_pyautocad_code(python_code)
    else:
        print("Không tìm thấy code Python trong câu trả lời.")
        print("Thử lại với mô tả cụ thể hơn, ví dụ: 'vẽ hình chữ nhật 100x50'")
Deepseek

Với con bot này thì cần phải fake useragent:

from pyautocad import Autocad, APoint
import re, json, requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# ================== CẤU HÌNH ==================
acad = Autocad(create_if_not_exists=True, visible=True)

DEEPSEEK_API_KEY = "nhập API key của bạn vào đây"
API_URL = "https://api.deepseek.com/chat/completions"
MODEL   = "deepseek-v4-pro"

BROWSER_HEADERS = {
    "Content-Type":    "application/json",
    "Authorization":   f"Bearer {DEEPSEEK_API_KEY}",
    "User-Agent":      (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/125.0.0.0 Safari/537.36"
    ),
    "Accept":          "application/json",
    "Accept-Language": "en-US,en;q=0.9",
    # requests tự xử lý decompress
    "Origin":          "https://platform.deepseek.com",
    "Referer":         "https://platform.deepseek.com/",
    "Connection":      "keep-alive",
    "Sec-Fetch-Dest":  "empty",
    "Sec-Fetch-Mode":  "cors",
    "Sec-Fetch-Site":  "same-site",
}
# =============================================

CODE_FIXES = [
    (r'acad\.model\.add_polyline\(', 'draw_polyline(acad, '),
    (r'acad\.ActiveDocument\.ModelSpace\.AddPolyline\(', 'draw_polyline(acad, '),
    (r'APoint\((\d+\.?\d*),\s*(\d+\.?\d*),\s*(\d+\.?\d*)\)', r'APoint(\1, \2, \3)'),
    (r'acad\.model\.add_line\(',   'acad.model.AddLine('),
    (r'acad\.model\.add_circle\(', 'acad.model.AddCircle('),
    (r'acad\.model\.add_text\(',   'acad.model.AddText('),
    (r'acad\.model\.add_mtext\(',  'acad.model.AddMText('),
    (r'acad\.ZoomExtents\(\)',     'acad.app.ZoomExtents()'),
    (r'acad\.Application\.',       'acad.app.'),
]

HELPER_CODE = """
import win32com.client
import array as arr_module

def draw_polyline(acad, points_list):
    flat = []
    for p in points_list:
        flat.extend([float(p[0]), float(p[1])])
    pts = arr_module.array('d', flat)
    return acad.model.AddLightWeightPolyline(pts)

def draw_rect(acad, x, y, w, h):
    pts = [(x,y),(x+w,y),(x+w,y+h),(x,y+h),(x,y)]
    pl = draw_polyline(acad, pts)
    pl.Closed = True
    return pl

def draw_line(acad, x1, y1, x2, y2):
    return acad.model.AddLine(APoint(x1,y1,0), APoint(x2,y2,0))

def draw_circle(acad, cx, cy, r):
    return acad.model.AddCircle(APoint(cx,cy,0), r)

def draw_text(acad, text, x, y, height=2):
    return acad.model.AddText(text, APoint(x,y,0), height)

def draw_arrow(acad, x1, y1, x2, y2):
    import math
    line = draw_line(acad, x1, y1, x2, y2)
    angle = math.atan2(y2-y1, x2-x1)
    alen, aangle = 3, math.pi/6
    draw_line(acad, x2, y2, x2 - alen*math.cos(angle-aangle), y2 - alen*math.sin(angle-aangle))
    draw_line(acad, x2, y2, x2 - alen*math.cos(angle+aangle), y2 - alen*math.sin(angle+aangle))
    return line
"""

SYSTEM_PROMPT = """Bạn là chuyên gia AutoCAD automation dùng Python + pyautocad.

=== CÁC HÀM HELPER SẴN CÓ ===
- draw_rect(acad, x, y, w, h)
- draw_line(acad, x1, y1, x2, y2)
- draw_circle(acad, cx, cy, r)
- draw_text(acad, text, x, y, height)
- draw_arrow(acad, x1, y1, x2, y2)
- draw_polyline(acad, [(x,y),...])

=== API ĐÚNG ===
- acad.model.AddLine(APoint(x1,y1,0), APoint(x2,y2,0))
- acad.model.AddCircle(APoint(cx,cy,0), radius)
- acad.model.AddText("text", APoint(x,y,0), height)
- APoint(x, y, z)  ← luôn 3 tham số

=== KHÔNG DÙNG ===
- add_polyline/add_line/add_circle/add_text  ← không tồn tại
- acad.ZoomExtents()  ← SAI, dùng acad.app.ZoomExtents()

=== YÊU CẦU ===
- Đặt toàn bộ code trong 1 block ```python ... ```
- Biến acad đã có sẵn, KHÔNG khai báo lại
"""

# ─── Helpers
def remove_markdown(text):
    text = re.sub(r'```[\s\S]*?```', '', text)
    text = re.sub(r'`([^`]+)`', r'\1', text)
    text = re.sub(r'\*\*(.+?)\*\*', r'\1', text)
    text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE)
    return text.strip()

def extract_python_code(text):
    m = re.search(r'```(?:python|py)?\s*(.+?)\s*```', text, re.DOTALL | re.IGNORECASE)
    return m.group(1).strip() if m else None

def sanitize_code(code):
    for pattern, replacement in CODE_FIXES:
        code = re.sub(pattern, replacement, code)
    return code

def run_pyautocad_code(code_str):
    try:
        full_code = HELPER_CODE + "\n" + code_str
        header = ""
        if "from pyautocad" not in full_code:
            header = "from pyautocad import Autocad, APoint\n"
        if "acad = Autocad" not in full_code:
            header += "acad = Autocad()\n"
        full_code = header + full_code

        print("\n--- Code sẽ chạy ---")
        print(full_code[:800], "...(truncated)" if len(full_code) > 800 else "")
        print("--------------------\n")

        exec(full_code, {"acad": acad, "APoint": APoint, "__builtins__": __builtins__})
        print("Đã chạy xong!")
        try:
            acad.app.ZoomExtents()
        except Exception:
            pass
        return True
    except Exception as e:
        print(f"✗ Lỗi khi chạy code: {e}")
        if "attribute" in str(e).lower():
            print("  → Có thể dùng hàm không tồn tại.")
        elif "apoint" in str(e).lower():
            print("  → APoint cần 3 tham số: APoint(x, y, 0)")
        elif "com" in str(e).lower() or "dispatch" in str(e).lower():
            print("  → AutoCAD chưa mở hoặc không phản hồi.")
        return False

# ─── Session dùng chung 
def make_session():
    s = requests.Session()
    retry = Retry(total=3, backoff_factor=1,
                  status_forcelist=[429, 500, 502, 503, 504])
    s.mount("https://", HTTPAdapter(max_retries=retry))
    return s

SESSION = make_session()

# ─── API call 
def ask_deepseek(user_text):
    payload = {
        "model": MODEL,
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": (
                f'Người dùng mô tả ý tưởng vẽ:\n"{user_text}"\n\n'
                "Hãy:\n"
                "1. Giải thích ngắn gọn bằng tiếng Việt sẽ vẽ gì.\n"
                "2. Viết code Python dùng các hàm helper trong block ```python."
            )},
        ],
        "thinking":         {"type": "enabled"},
        "reasoning_effort": "high",
        "stream":           False,
        "max_tokens":       4096,
    }

    headers = {**BROWSER_HEADERS, "Authorization": f"Bearer {DEEPSEEK_API_KEY}"}

    try:
        resp = SESSION.post(
            API_URL,
            headers=headers,
            data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
            timeout=120,
        )

        # ── decode thủ công, tránh lỗi encoding
        raw_bytes = resp.content
        try:
            raw_text = raw_bytes.decode("utf-8")
        except UnicodeDecodeError:
            raw_text = raw_bytes.decode("latin-1")

        print(f"[HTTP {resp.status_code}]  Content-Type: {resp.headers.get('Content-Type','?')}"
              f"  Content-Encoding: {resp.headers.get('Content-Encoding','none')}"
              f"  Size: {len(raw_bytes)} bytes")

        if resp.status_code != 200:
            try:
                err = json.loads(raw_text).get("error", {}).get("message", raw_text[:300])
            except Exception:
                err = raw_text[:300]
            return f"Lỗi API HTTP {resp.status_code}: {err}"

        data    = json.loads(raw_text)
        message = data["choices"][0]["message"]

        reasoning = message.get("reasoning_content", "")
        if reasoning:
            print("\n[Thinking]:", reasoning[:300],
                  "..." if len(reasoning) > 300 else "")

        content = message.get("content", "")
        if isinstance(content, list):
            text = " ".join(
                b.get("text", "") for b in content if b.get("type") == "text"
            )
        else:
            text = content

        return text

    except requests.exceptions.SSLError as e:
        return f"Lỗi SSL: {e}"
    except requests.exceptions.ConnectionError as e:
        return f"Lỗi kết nối: {e}\n→ Kiểm tra mạng / firewall chặn api.deepseek.com:443"
    except requests.exceptions.Timeout:
        return "Lỗi: DeepSeek API timeout (>120s)"
    except json.JSONDecodeError as e:
        return f"Lỗi parse JSON: {e}\nRaw (200 bytes): {raw_bytes[:200]}"
    except Exception as e:
        return f"Lỗi không xác định: {e}"

# ─── Main loop
if __name__ == "__main__":
    print("\n=== DeepSeek AI — Vẽ AutoCAD bằng Python ===")
    print(f"Model : {MODEL}")
    print(f"AutoCAD: {acad.doc.Name if acad.doc else 'Chưa có file'}\n")

    while True:
        user_input = input("Nhập ý tưởng vẽ (exit để thoát): ").strip()

        if user_input.lower() in ("exit", "thoát", "quit", "q"):
            print("Đã thoát.")
            break
        if not user_input:
            continue

        print(f"\nĐang hỏi DeepSeek: {user_input}")
        ai_response = ask_deepseek(user_input)

        print("\n=== DeepSeek trả lời ===")
        print(remove_markdown(ai_response))
        print("========================\n")

        code = extract_python_code(ai_response)
        if code:
            run_pyautocad_code(sanitize_code(code))
        else:
            print("Không tìm thấy code Python. Thử mô tả cụ thể hơn.")