枫叶棋语 发表于 2023-7-28 21:06:37

【pycad】让Cpython在CAD中开发

本帖最后由 枫叶棋语 于 2023-8-22 22:37 编辑

继上次的实验,我们初步实现了Ironpython环境加载Cpython环境,经过一番苦战,实现了在cad上运行Cpython,包括CAD注册命令,兼容Ipy脚本,本次更新实现了Cpython在CAD上运行,并依靠第三方模块开发的功能。相当于80%成品,因为本人能力有限,未能脱离借助Ironpython,直接使用c#调用Cpython,望后续有看到的兄弟助理一把。
这个是Cpython引擎
import clr
import sys
import os
clr.AddReference("Python.Runtime")
clr.AddReference("acmgd")
clr.AddReference("AcCoreMgd")
clr.AddReference("AcDbMgd")
clr.AddReference("System")

import System
import Autodesk.AutoCAD.ApplicationServices as acap
from Python.Runtime import *

dllpath = System.Reflection.Assembly.GetExecutingAssembly().Location
dlldir = os.path.dirname(dllpath)
supportdir = os.path.join(dlldir, "support")
extensionsdir = os.path.join(dlldir, "extensions")
referencedir = os.path.join(dlldir,"Reference")


class CpyEngine:
    def __init__(self,pydll):
      self.pydll =pydll
      self.pyhome =os.path.dirname(pydll)
      self.pypath = "{self.pyhome}\\Lib;{self.pyhome}\\Lib\\site-packages"
      self.SetCpyEnviromnent()
      self.Initilize()
    @staticmethod
    def prinf(msg):
      acap.Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage(f"\n{msg}\n")
    def SetCpyEnviromnent(self):
      '''配置Cpython环境变量'''
      os.environ["PYTHONHOME"]=self.pyhome
      os.environ["PYTHONNET_PYDLL"] = self.pydll
      os.environ["PYTHONPATH"] = self.pypath
      
    def Initilize(self):
      '''初始化Cpython引擎'''
      if not PythonEngine.IsInitialized:
            PythonEngine.Initialize()
      PythonEngine.PythonPath =self.pypath

    def RunCpyScript(self,scriptpath):
      '''运行脚本'''
      import traceback
      self.scope = Py.CreateScope()
      pysys=Py.Import("sys")
      pysys.path.append(dlldir)
      pysys.path.append(supportdir)
      pysys.path.append(extensionsdir)
      pysys.path.append(referencedir)
      with open(scriptpath,"r",encoding="utf-8") as file:
            pythoncode= file.read()
      with Py.GIL():
            try:
                self.scope.Exec(pythoncode)
            except:
                error =traceback.format_exc()
                self.prinf(error)



import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")
import System
from System.Drawing import *
from System.Windows.Forms import *
import os
from pycad.system.acad import acap, prinf
from pycad.system.CpyEngine import CpyEngine
def check_and_create_file(filename):
    '''判断文件是否存在,不存在就创建空文件'''
    if not os.path.exists(filename):
      open(filename, 'w')
class MainForm(Form):
    def __init__(self):
      self.InitializeComponent()
      self.Resize += self.FormChanged
      self.MinimumSize = Size(500, 500)
      self.dllpath = System.Reflection.Assembly.GetExecutingAssembly().Location
      self.dlldir = os.path.dirname(self.dllpath)
      self.history = self.dlldir+"\CpyHistory.txt"
      check_and_create_file(self.history)
      self.HistoryPaths = self.ReadHitory()
      self.ImportPaths(self.HistoryPaths)
      self.engine = CpyEngine(r"D:\Program Files\Python310\python310.dll")
    @staticmethod
    def SetButton(button,anchor, x, y,name, text, func):
      button.Anchor =anchor
      button.Location = Point(x, y)
      button.Size = Size(75, 25)
      button.Name = name
      button.Text = text
      button.UseVisualStyleBackColor = True
      button.Click += func
      
    @staticmethod
    def AddItem(listview: ListView, lst):
      item = ListViewItem(items=tuple(lst))
      listview.Items.Add(item)
      
    def InitializeComponent(self):
      self._Add = Button()
      self._Delete = Button()
      self._Clear = Button()
      self._Apply = Button()
      self._Cancel = Button()
      self._Reload = Button()
      self._ScriptPaths = ListView()
      self._FileName = ColumnHeader()
      self._FilePath = ColumnHeader()
      self.Size = Size(500, 550)
      self.ButtonWidth = 75
      self.ButtonHeight = 25
      self.ButtonX = 425
      self.ListViewWidth =self.ButtonX-75
      self.ListViewHeight =self.Size.Height -200
      
      # ScriptPaths
      self._ScriptPaths.View = View.Details
      self._ScriptPaths.Anchor = AnchorStyles.Left|AnchorStyles.Top|AnchorStyles.Bottom|AnchorStyles.Right
      self._ScriptPaths.Location = Point(30, 50)
      self._ScriptPaths.Name = "ScriptPaths"
      self._ScriptPaths.Size = Size(self.ListViewWidth,self.ListViewHeight)
      self._ScriptPaths.GridLines =True
      self._ScriptPaths.UseCompatibleStateImageBehavior = False
      self._ScriptPaths.Columns.Add("文件")
      self._ScriptPaths.Columns.Add("路径")
      self._ScriptPaths.Columns.Width= 150
      self._ScriptPaths.Columns.Width = self.ListViewWidth-150
      self._ScriptPaths.BorderStyle = BorderStyle.FixedSingle
      self._ScriptPaths.FullRowSelect =True
            
      self.SetButton(self._Add,    AnchorStyles.Right| AnchorStyles.Top,400,70,    "Add", "添加", self.AddClick)
      self.SetButton(self._Delete, AnchorStyles.Right| AnchorStyles.Top,400, 140, "Delete", "删除", self.DeleteClick)
      self.SetButton(self._Clear,AnchorStyles.Right| AnchorStyles.Top,400, 210,"Clear","清空", self.ClearClick)
      self.SetButton(self._Reload, AnchorStyles.Bottom|AnchorStyles.Right,200, 450,"Reload", "重载", self.ReloadClick)
      self.SetButton(self._Apply,AnchorStyles.Bottom|AnchorStyles.Right,300, 450,"Apply","确定", self.ApplyClick)
      self.SetButton(self._Cancel, AnchorStyles.Bottom|AnchorStyles.Right,400, 450, "Cancel","取消", self.CancelClick)

      # 窗体添加控件
      self.Controls.Add(self._ScriptPaths)
      self.Controls.Add(self._Cancel)
      self.Controls.Add(self._Apply)
      self.Controls.Add(self._Clear)
      self.Controls.Add(self._Reload)
      self.Controls.Add(self._Delete)
      self.Controls.Add(self._Add)
      self.Text = "加载CPython脚本"
      self.ResumeLayout(False)
      
    #读取history文本内容
    def ReadHitory(self):
      with open(self.history,"r",encoding="utf-8") as file:
            existing_paths = file.read().splitlines()
            return existing_paths
    #读取listview内容
    def ReadListView(self):
      paths = []
      for item in self._ScriptPaths.Items:
            path = item.SubItems.Text
            paths.append(path)
      return paths
    #导入路径
    def ImportPaths(self,paths):
      for path in paths:
            basename = os.path.basename(path)
            self.AddItem(self._ScriptPaths,)
    #导出路径   
    def ExportPaths(self,paths):
      with open(self.history, "w") as file:
            for item in self._ScriptPaths.Items:
                path = item.SubItems.Text
                if path not in existing_paths:
                  file.write(path + '\n')

    def AddClick(self, sender, e):
      openFileDialog = OpenFileDialog()
      openFileDialog.Multiselect = True
      if openFileDialog.ShowDialog() == DialogResult.OK:
            self._ScriptPaths.Items.Clear()
            file_names = openFileDialog.FileNames
            result = set(file_names)|set(self.HistoryPaths)
            self.ImportPaths(result)
            self.HistoryPaths =list(result)
            for path in file_names:
                self.engine.RunCpyScript(path)
            
    def DeleteClick(self, sender, e):
      selected_items = self._ScriptPaths.SelectedItems
      for item in selected_items:
            self._ScriptPaths.Items.Remove(item)
      self.HistoryPaths =self.ReadListView()

    def ClearClick(self, sender, e):
      self._ScriptPaths.Items.Clear()
      self.HistoryPaths = []
      
    def ApplyClick(self, sender, e):
      with open(self.history, "w") as file:
            for path in self.HistoryPaths:
                file.write(f"{path}\n")
      self.Close()
            
    def ReloadClick(self, sender, e):
      selected_items = self._ScriptPaths.SelectedItems
      for item in selected_items:
            path = item.SubItems.Text
            self.engine.RunCpyScript(path)
            
    def CancelClick(self, sender, e):
      self.Close()

    def FormChanged(self, sender, e):
      self._ScriptPaths.Columns.Width = self.Size.Width - 200
      
loadform = MainForm()
acap.Application.ShowModelessDialog(loadform)
与此同时,我们建立一个CpyReload脚本,用于命令重载所有脚本命令,代码如下:
from pycad.system import prinf
from pycad.system.CpyEngine import CpyEngine
import System
import os,traceback
import re
def check_and_create_file(filename):
    '''判断文件是否存在,不存在就创建空文件'''
    if not os.path.exists(filename):
      open(filename, 'w')
class Reload:
    def __init__(self):
      self.engine = CpyEngine(r"D:\Program Files\Python310\python310.dll")
      self.dllpath = System.Reflection.Assembly.GetExecutingAssembly().Location
      self.dlldir = os.path.dirname(self.dllpath)
      self.history = self.dlldir+"\CpyHistory.txt"
      check_and_create_file(self.history)
      self.reload()

    def reload(self):
      with open(self.history,"r",encoding="utf-8") as file:
            paths = file.read().splitlines()
            for path in paths:
                self.engine.RunCpyScript(path)

Reload()至此,CpyCAD的框架的python代码基本搭设完成,由于权限问题,暂时不能上传文件。有需要的可以联系。


枫叶棋语 发表于 2023-7-29 10:46:14

本帖最后由 枫叶棋语 于 2023-8-22 21:23 编辑

同样我们可以调用python的opencv,scipy,matplot,这里就不一一展示,到这一步,我们就基本完成了cpython 开发CAD方法,同时避免了com方式
向CAD中添加图元

from pycad.system import *
from pycad.runtime import *
from typing import *

               
@Command()
def addline(doc):
    pt1 = getpoint().value
    pt2 = getpoint().value
    line = Line(pt1,pt2)
    with dbtrans(doc) as tr:
      tr.addentity(tr.btr,line)

@Command()
def addcircle(doc):
    center = getpoint().value
    radius = getreal().value
    cir = Circle()
    cir.Center ,cir.Radius =center,radius
    with dbtrans(doc) as tr:
      tr.addentity(tr.btr,cir)
import numpy as np
@Command()
def testnp(doc):
    lst = np.array()
    prinf(f"numpy数组{lst}")


枫叶棋语 发表于 2023-7-29 21:41:13

枫叶棋语 发表于 2023-7-29 00:47
这里面还是有一些问题需要处理,这个只是一个开始

我的pycad使用Ironpython3.4,除了c#构造一个简单的环境加载,其余所有功能全部由py代码构成,注册命令方式也不一样,目前方向往Cpython进行拓展

caoliu023 发表于 2023-7-28 22:06:43

如果能用py第三方库,那就有有很多想象空间了,就看有没有人发掘出潜力让我涨涨知识,还有个期待是希望有人能把Javascript API重新带回cad

桌子好像是放弃Javascript API了

[飞马系列] AutoCAD 2014 之Javascript API编程初探
http://bbs.mjtd.com/forum.php?mod=viewthread&tid=108034&fromuid=287566
(出处: 明经CAD社区)


zlf2008 发表于 2023-7-28 22:06:22

谢谢分享!认真学习,想自己搞一个自己熟悉的常用的工具

枫叶棋语 发表于 2023-7-29 00:47:31

caoliu023 发表于 2023-7-28 22:06
如果能用py第三方库,那就有有很多想象空间了,就看有没有人发掘出潜力让我涨涨知识,还有个期待是希望有人 ...

这里面还是有一些问题需要处理,这个只是一个开始

edata 发表于 2023-7-29 15:46:02

不知道和版主的pycad有什么区别http://bbs.mjtd.com/thread-180587-1-1.html

陨落 发表于 2023-7-30 10:42:50

edata 发表于 2023-7-29 15:46
不知道和版主的pycad有什么区别http://bbs.mjtd.com/thread-180587-1-1.html

我还以为就是基于版主的pycad呢?如果不是,那枫总一开始所有能嵌入cad的ironpython是从哪来的呢?难道是自己用C#写了个dll,通过c#传递参数给ironpython并调用他?

枫叶棋语 发表于 2023-7-30 18:44:31

陨落 发表于 2023-7-30 10:42
我还以为就是基于版主的pycad呢?如果不是,那枫总一开始所有能嵌入cad的ironpython是从哪来的呢?难道是 ...

c#写了一个dll,实现cad能够加载运行第一个py脚本,其他功能全部用py脚本实现,相当于99%的Ironpython

caoliu023 发表于 2023-8-22 21:38:04

楼主也可以发网盘链接,发个百度网盘永久的链接
页: [1] 2
查看完整版本: 【pycad】让Cpython在CAD中开发