图形处理的问题V1.1

2023年05月31日 星期三

最近股份公司遇到的一个问题,说是激光切割时所采用的图经常会有多余的线,希望来处理一下。这里就采用pyautocad来做到这一点。……

[TOC]

CAD图形的处理V1.1

1 问题类型

(1)线局部重合、重叠

不仅仅包括直线段的重叠,同时包含有弧线的重合

image-20230301094405018

重合线2

(2)平行线

image-20230301094802170

平行线2

2 解决思路

(1) 分区域

​ 基于各个分块图形都具有一定区间,分块处理后互相不再打扰。每个图块外围会生成一个浅蓝色矩形框。

image-20230301135138954

(2)去重线

(3)获交点

​ 上述两步是为后续找外轮廓线奠定基础

(4)找外轮廓线

​ 这里所采用的是这个算法闭合线段找最外围线段

(5)合并外轮廓线

​ 此步可做可不做

3 使用方法

​ 将exe文件和同名lsp文件放到同一个文件夹中,然后在cad中加载该lsp文件。进而输入命令 2265(此命令可以自己在lsp文件中更改),在弹出的命令窗口提示”请选择图形“后在选择所需要处理的图形即可进行处理。此时会弹出窗口提示处理进程。若出现提示“第n未闭合”,则表明存在需要手动处理的图形,相应图形颜色会标红。当提示“多余线处理完成”后,程序执行完毕。对于弹出的窗口,按回车键可关闭。在程序处理过程中,cad中不建议执行其他操作,否则程序会报错。

遇到有闪退,一般重新运行两次即可。

image-20230314162619493

4 效果演示

4.1 图形效果

如下图所示:

  • (1)对于未能处理的图形会有红色矩形圈出
  • (2)上述未能处理的图形中,未能处理的接近重合线(或部分重合线)用粉色标出,后续可手动删除多余的线。

image-20230314153605419

image-20230314153954354

目前角度相差超过1e-1或与原点距离差大于1mm的并不会赋予粉色.如下图中右边两条尽管看似平行,但由于与原点距离偏差距离较大(超过3mm),并未赋予粉红色。因此除了图中显色粉色的线外,尚有必要手动检查下其他线。

image-20230314164454400

其余正常处理后的图形中,绿色线表示已消除重线。

image-20230314154722221

4.2 精度

对于标红需要手动处理的图形,其原因主要在于各处理环节中精度控制的大小。在目前所取的精度下,对于设计部方面所提供的两个案例cad图纸,图1共1385个图块,程序处理1381个,剩余4个需要手动处理,完成率达到99.71%;图2共1269个图块,程序处理1261个,剩余8个需要手动处理,完成率达到99.37%。

4.3 对比演示

5 代码

5.1 主程序

'''
2023/3/14 星期二 天气晴
本程序用于对CAD零件图进行处理
版本1.1
'''

import math
from pyautocad import Autocad, APoint, aDouble, ACAD
from mycad61 import Line, AllLine
A1 = Autocad(create_if_not_exists=True)



def get_layer(acad, la_name): # 顶级对象 图层名称
    '''
    获取所要设置图层是否存在
    '''
    ret = []
    la_all = acad.doc.layers  # 得到全部图层
    la_name_li = [obj.name for obj in la_all] # 获取所有图层名称
    ret.append(la_name in la_name_li)  # 返回该图层是否存在
    if la_name in la_name_li:  # 假如存在
        ret.append(la_all.item(la_name))  # 返回该图层对象
    return ret # 返回一序列,第一个值表征其是否存在,第二个为图层名称

def set_layer(acad, la_name, la_color1=255, la_color2=255, la_color3=255):
    '''
    设置图层
    '''
    ret = get_layer(acad, la_name)  # 获取当前图层是否存在
    if ret[0]:  # 假如存在所要设置图层
        acad.doc.Activelayer = ret[1]  # 设置当前图层
    else:
        ac_layer = acad.doc.layers.add(la_name)  # 添加所要图层
        acad.doc.Activelayer = ac_layer  # 设置当前图层
        version_str = acad.app.Version[0:4]        
        progid = version_str[:2]
        cc = acad.app.GetInterfaceObject('AutoCAD.AcCmColor.'+ progid)
        # 获取一个AcCmColor对象,这里progid代表版本号,需要根据CAD版本变化而调整
        cc.setrgb(la_color1, la_color2, la_color3)
        ac_layer.TrueColor = cc

round_no = 6
snap = A1.doc.GetVariable("OSMODE") # 清除所有捕捉
A1.doc.SetVariable("OSMODE", 0)

# 读取坐标
selSets = A1.doc.SelectionSets
for obj in selSets:
    print(obj)
    obj.delete()
gg = selSets.add("Wall")
gg.SelectOnScreen()
i = 0
L = 0
p1li = []
p2li = []
pli = []

ac_layer = A1.doc.Activelayer
cc = ac_layer.TrueColor  #这里得到了一个AcCmColor对象
cc.setrgb(0, 255, 255)
num = 0
lines = []
angleLi = []
dLi = []

# obj.ObjectName=="AcDbArc"
for obj in gg:
    #print(obj.ObjectName)
    if (obj.ObjectName == "AcDbLine" or obj.ObjectName == "AcDbArc") and\
          (obj.Layer == 'Part' or obj.Layer == '0'):
        line1 = Line(obj)
        lines.append(line1)
        num += 1
        #print(line1.angle,line1.p1,line1.p2)
        #L+=obj.Length
print('共'+str(num)+'条线\n')
gg.delete()


print('1----图形分块')
Al = AllLine(lines)


i = 0
for obj1 in Al.Lines:
    aa = []
    for obj2 in Al.Lines:
        if obj1 == obj2:
            pass
        else:
            if max(obj1.rec[0], obj2.rec[0]) <= min(obj1.rec[2], obj2.rec[2]) and\
                max(obj1.rec[1], obj2.rec[1]) <= min(obj1.rec[3], obj2.rec[3]):
                obj1.coinLines.append(obj2)
    #print(obj1.coinLines)
    i += 1



RecAll = []

L2 = Al.Lines
#L2[0].L.TrueColor=cc
i = 0
while len(L2) != 0:
    Lw0 = [L2[0]]+L2[0].coinLines
    LwSearch = L2[0].coinLines
    while len(LwSearch) != 0:
        #print('第一次')
        #print(len(LwSearch))
        aa = []
        for obj2 in LwSearch:
            #print(len(obj2.coinLines))
            for obj3 in obj2.coinLines:
                if obj3 in Lw0:
                    pass
                else:
                    if obj3 not in aa:
                        aa.append(obj3)
                        Lw0.append(obj3)
            #print(len(aa))
        LwSearch = aa

    RecAll.append(AllLine(Lw0))
    for obj4 in Lw0:
        L2.remove(obj4)

    i += 1
    #print('第'+str(i)+'个')
print('共'+str(i)+'个图形')
    #print(L2)
set_layer(A1, '外框图层', 0, 255, 255)
for obj1 in RecAll:
    minX = obj1.Lines[0].rec[0]
    minY = obj1.Lines[0].rec[1]
    maxX = obj1.Lines[0].rec[2]
    maxY = obj1.Lines[0].rec[3]
    for obj2 in obj1.Lines[1:]:
        #print(obj2.rec)
        minX = min(obj2.rec[0], minX)
        minY = min(obj2.rec[1], minY)
        maxX = max(obj2.rec[2], maxX)
        maxY = max(obj2.rec[3], maxY)

    dd = [minX, minY, 0]+[minX, maxY, 0]+[maxX, maxY, 0]+[maxX, minY, 0]+[minX, minY, 0]
    ee = aDouble(dd)
    L = A1.model.AddPolyline(ee)



print('图形分块完成\n')
print('2---处理重线')
## 这里有个问题是如果内部有环的话,但好像也没有问题,正好适合开洞



cc.setrgb(0, 255, 0)
set_layer(A1, '0')
ff = ac_layer.TrueColor  #这里得到了一个AcCmColor对象
ff.setrgb(255, 0, 255)
##去重
newRecAll = []
for AL  in  RecAll:
    flag = 1
    lines = AL.Ls
    while flag == 1:
        delL = []
        appL = []
        for i, line1 in enumerate(lines):
            #print(line1.angle)
            for line2 in lines[i+1:]:
                #print(line1.angle,line2.angle)
                if abs(line1.p1[1]-line1.p2[1]) <= 1e-6:# 水平线
                    d = abs(line1.p1[1]-line2.p1[1])
                elif abs(line1.p1[0]-line1.p2[0]) <= 1e-6:# 竖直线
                    d = abs(line1.p1[0]-line2.p1[0])
                else:
                    d = abs(line1.d0-line2.d0)
                x12 = max(abs(line1.p2[0]-line2.p1[0]), abs(line1.p1[0]-line2.p2[0]))
                y12 = max(abs(line1.p2[1]-line2.p1[1]), abs(line1.p1[1]-line2.p2[1]))
                if abs(line1.angle-line2.angle) < 1e-6: # 角度相同
                #print('i='+str(i))
                    #print('d',d)
                    if d < 1e-6:# 共线 (如果是多条共线?)

                        if (math.sqrt(x12*x12+y12*y12)-line1.len-line2.len) > 1e-3:
                            pass
                            # print('无重合段')
                        else:
                            if line1.L.ObjectName == "AcDbArc":
                                pc = APoint(line1.L.Center[0], line1.L.Center[1])
                                l1 = A1.model.AddArc(pc, line1.L.Radius, line1.L.StartAngle, line1.L.EndAngle)
                            else:
                                #print('aa')
                                aa = [line1.p1, line1.p2]
                                if line2.p1 not in aa:
                                    aa.append(line2.p1)
                                if line2.p2 not in aa:
                                    aa.append(line2.p2)
                                aa = sorted(aa, key=lambda x: (x[1], x[0]))
                                x1 = aa[0][0]
                                y1 = aa[0][1]
                                x2 = aa[-1][0]
                                y2 = aa[-1][1]

                                p1 = APoint(x1, y1)
                                p2 = APoint(x2, y2)
                                #print(aa)
                                l1 = A1.model.addLine(p1, p2)
                            l1.TrueColor = cc

                            #Al.mat[no][2].L.TrueColor =cc
                            #Al.mat[i][2].L.TrueColor =cc

                            delL += [line1, line2]
                            appL.append(Line(l1))
                            print('发现重叠线')
                            break # 找到一条就跳出
                            #print(len(appL))
                    elif d<0.1:
                        if (math.sqrt(x12*x12+y12*y12)-line1.len-line2.len) > 1e-3:
                            pass
                        else:
                            line1.L.TrueColor =ff
                            line2.L.TrueColor =ff
                elif abs(line1.angle-line2.angle) < 1e-3 or abs(abs(line1.angle-line2.angle)-math.pi)<1e-3 : #角度相差较小
                    if d<0.1:
                        if (math.sqrt(x12*x12+y12*y12)-line1.len-line2.len) > 1e-3:
                            pass
                        else:
                            line1.L.TrueColor =ff
                            line2.L.TrueColor =ff                  

        #print('gh',len(delL))
        if len(delL) != 0:
            for line in delL:
                if line in lines:
                    line.L.Delete()
                    lines.remove(line)
            #print(len(lines))

            for line in appL:
                lines.append(line)

        else:
            flag = 0
            newRecAll.append(AllLine(lines))

print('重线处理完成\n')
print('3---处理交线')
### 求交点,暂时仅考虑对直线相交的处理
newRecAll2 = []
for Al in newRecAll:
    new_Ls = []
    for line in Al.Ls:
        line.new_lines = [[line.p1[0], line.p1[1]], [line.p2[0], line.p2[1]]]
        #print(line.new_lines)

    for i, line1 in enumerate(Al.Ls):
        for line2 in Al.Ls[i+1:]:
            RetVal = line1.L.IntersectWith(line2.L, ACAD.acExtendNone)
            if len(RetVal) == 3:#有交点
                inP = [round(RetVal[0], round_no), round(RetVal[1], round_no)] # 相交点
                #print(inP)
                flag = 1
                for obj in line1.new_lines:
                    #print(abs(obj[0]-inP[0]),abs(obj[1]-inP[1]))
                    if abs(obj[0]-inP[0]) < 1e-4 and abs(obj[1]-inP[1]) < 1e-4:
                        flag = 0
                        break
                if flag != 0:
                    line1.new_lines.append(inP)
                    #print(line1.new_lines)

                flag = 1
                for obj in line2.new_lines:
                    if abs(obj[0]-inP[0]) < 1e-4 and abs(obj[1]-inP[1]) < 1e-4:
                        flag = 0
                        break
                if flag != 0:
                    line2.new_lines.append(inP)

    for line in Al.Ls:
        new_line = line.new_lines
        #print(new_line)
        #print('aa')

        if len(new_line) == 2:
            new_Ls.append(line)
        else:
            #print('aa')
            if line.angle<1e-6:
                new_line = sorted(new_line, key=lambda x: (x[0]))
            else:
                new_line = sorted(new_line, key=lambda x: (x[1], x[0]))
            #print(new_line)
            line.L.Delete()
            for j in range(len(new_line)-1):
                x1 = new_line[j][0]
                y1 = new_line[j][1]
                x2 = new_line[j+1][0]
                y2 = new_line[j+1][1]
                p1 = APoint(x1, y1)
                p2 = APoint(x2, y2)
                l1 = A1.model.addLine(p1, p2)
                new_Ls.append(Line(l1))

    Al2 = AllLine(new_Ls)
    newRecAll2.append(Al2)

print('交线处理完成\n')
print('4---寻找最外圈闭合框')




def drawRec(AL):

    minX = AL.Lines[0].rec[0]
    minY = AL.Lines[0].rec[1]
    maxX = AL.Lines[0].rec[2]
    maxY = AL.Lines[0].rec[3]
    for obj2 in AL.Lines[1:]:
        #print(obj2.rec)
        minX = min(obj2.rec[0], minX)
        minY = min(obj2.rec[1], minY)
        maxX = max(obj2.rec[2], maxX)
        maxY = max(obj2.rec[3], maxY)

    dd = [minX, minY, 0]+[minX, maxY, 0]+[maxX, maxY, 0]+[maxX, minY, 0]+[minX, minY, 0]
    ee = aDouble(dd)
    L = A1.model.AddPolyline(ee)




jj = 0
for Al in newRecAll2:
    #print(Al.pDL.p)
    pDL = Al.pDL
    Pos = Al.Pos
    ret = pDL.L0 # 得到
    #print(ret[1].p)
    OutLines = []
    #print('L0',pDL)
    #print(ret)
    #print(len(Al.Ls))
    if ret != 0:
        set_layer(A1, '外框图层')
        Point = APoint(pDL.p[0], pDL.p[1])
        A1.model.AddPoint(Point)
        set_layer(A1, '0')
    else:
        #print('跳出')
        jj += 1
    i = 1
    while ret != 0:
        OutLines.append(ret[0])
        #ret[0].L.TrueColor =ee
        pStart = ret[1]
        L0 = ret[0]
        ret = pStart.L1(L0)

        i += 1
        #print(ret)
        #print(ret[1].p[0]-pDL.p[0],ret[1].p[1]-pDL.p[1])
        #print(ret[1] == pDL)
        #print(ret)
        if ret != 0 and abs(ret[1].p[0] - pDL.p[0]) < 1e-6 and abs(ret[1].p[1] - pDL.p[1]) < 1e-6:
            #ret[0].L.TrueColor = ee
            OutLines.append(ret[0])
            #print('闭合')
            break

        if ret != 0 and i > 2*len(Al.Ls):
            ret = 0
            #print('陷入死循环')
            break

        if ret == 0:
            jj += 1


    if ret == 0:
        print('第'+str(jj)+'未闭合')
        set_layer(A1, '问题图形', 255, 0, 0)
        drawRec(Al)

        '''
        for obj in Al.Ls:
            obj.L.TrueColor = ee

        '''



    else:
        print('闭合')
        for obj in Al.Ls:
            if obj not in OutLines:
                obj.L.Delete()

print('最外圈闭合框寻找完成\n')

A1.doc.SetVariable("OSMODE", snap) # 设置捕捉

input('按 Enter 退出…')

5.2 自定义模块

import math
import numpy as np

class Point():
    def __init__(self,p):
        self.__p  = p
        self.Ls = []


    @property
    def p(self):
        return self.__p

    @property
    def L0(self):
        if len(self.Ls)==1:
            #print('cc')
            return 0
        else:
            angle = math.pi
            for obj in self.Ls:
                if obj.p1 == self.p:
                    if obj.angle <angle:
                        angle = obj.angle
                        ret = obj
                        p2 = obj.Po2

                else:
                    angle = math.pi
                    break

            if angle < round(math.pi/2,6):
                #print('angle',angle)
                return ret,p2
            else:
                return 0

    def L1(self,L0):
        if len(self.Ls)==1:            
            return 0
        else:
            if L0.p1 == self.p: #如果线段起点即为此点
                angle1 = L0.angle  
            else:
                angle1 = L0.angle +math.pi               
            dangle = 0
            #print('angle1')
            #print(angle1)
            #print(len(self.Ls))
            for obj in self.Ls:
                if obj == L0:
                    pass
                else:
                    if obj.p1 == self.p: #如果线段起点即为此点:
                        angle2 = obj.angle
                        p2p = obj.Po2
                    else:
                        angle2 = obj.angle+math.pi
                        p2p = obj.Po1


                    if angle2<=angle1:
                        dangle2 = angle1-angle2
                    else:
                        dangle2 = math.pi*2-(angle2-angle1)
                    #print('angle2')
                    #print(angle2)
                    #print(p2p.p)
                    #print(dangle2)
                    if dangle2 >= dangle:
                        dangle = dangle2
                        ret = obj
                        p2 = p2p
            #print(p2.p)
            return ret,p2





class Line():
    def __init__(self,L):
        self.__tol = 1 # 矩形搜索范围两侧1mm
        self.__L = L
        round_no= 6
        p1 = [round(L.StartPoint[0],round_no),round(L.StartPoint[1],round_no)] 
        p2 = [round(L.EndPoint[0],round_no),round(L.EndPoint[1],round_no)]
        #p1 = [L.StartPoint[0],L.StartPoint[1]] 
        #p2 = L.EndPoint[0],L.EndPoint[1]]       
        [x1,y1] = p1
        [x2,y2] = p2



        if abs(p1[1]-p2[1])<=1e-6:
            self.__d = p1[1]
            #print(self.__d)
        elif abs(p1[0]-p2[0])<=1e-6:
            self.__d = p1[0]
        else:
            a = 1/(x2-x1)
            b = -1/(y2-y1)
            c = -x1/(x2-x1)+y1/(y2-y1)
            if a>0:
                pass
            else:
                a= -a
                b =-b
                c= -c
            if c/b>=0:
                self.__d = abs(c/math.sqrt(a*a+b*b))
            else:
                self.__d = -abs(c/math.sqrt(a*a+b*b))
            self.__abc =[a,b,c]

        if L.ObjectName=="AcDbLine":
            self.__angle = L.angle
            self.__len = L.Length 
        else:
            self.__len=math.sqrt((y2-y1)*(y2-y1)+(x2-x1)*(x2-x1))
            if abs(x2-x1)<1e-6:
                self.__angle = math.pi/2
            else:
                self.__angle = math.atan((y2-y1)/(x2-x1))+math.pi


        '''
        if aa >= math.pi-1e-6 and aa<2*math.pi-1e-6:
            self.__angle = aa - math.pi
        elif aa>=2*math.pi -1e-6: 
            self.__angle = aa - 2*math.pi
        else:
            self.__angle = aa

        if self.__angle<0:
            self.__angle =-self.__angle
        self.__angle = round(self.__angle,6)
        '''



        if y1<y2: 
            self.__p1 = p1
            self.__p2 = p2
        elif y1>y2:
            self.__angle = self.__angle- math.pi
            self.__p1 = p2
            self.__p2 = p1
        else:
            if x1<x2:
                self.__p1 = p1
                self.__p2 = p2
            else:
                self.__angle = self.__angle - math.pi
                self.__p1 = p2
                self.__p2 = p1

        if abs(self.__angle - math.pi)< 1e-6:
            self.__angle = self.__angle -math.pi
            aa = self.__p1
            self.__p1 = self.__p2
            self.__p2 = aa
        if abs(self.__angle -2*math.pi)< 1e-6:
            self.__angle = self.__angle -2*math.pi

        self.__angle = round(self.__angle,round_no)





        self.__minX = min(x1,x2)
        self.__maxX = max(x1,x2)
        self.__minY = min(y1,y2)
        self.__maxY = max(y1,y2)

        self.coinLines = []

        #print(self.__p1)

        self.new_lines =[]
        self.Po1 =Point([0,0])
        self.Po2 =Point([0,0])


    @property
    def L(self):
        return self.__L

    @property
    def angle(self):
        return self.__angle

    @property
    def abc(self):
        return self.__abc 

    @property
    def d0(self):
        return self.__d

    @property
    def p1(self):
        return self.__p1

    @property
    def p2(self):
        return self.__p2

    @property
    def len(self):
        return self.__len

    @property
    def rec0(self):
        return [self.__minX,self.__minY,self.__maxX,self.__maxY]

    @property
    def rec(self):
        return [self.__minX-self.__tol,self.__minY-self.__tol,self.__maxX+self.__tol,self.__maxY+self.__tol]


class AllLine():
    def __init__(self,Ls):
        self.Ls = Ls

        self.__ps =[]
        for obj in self.Ls:
            if obj.p1 not in self.__ps:
                self.__ps.append(obj.p1)
            if obj.p2 not in self.__ps:
                self.__ps.append(obj.p2)


    @property
    def Lines(self):
        ret = []
        for obj in self.mat:
            ret.append(obj[2])
        return ret

    @property
    def mat(self):    
        ret =[]
        #print(self.Ls)
        for obj in self.Ls:
            ret.append([obj.angle,obj.d0,obj,obj.p1[1],obj.p1[0]])

        ret = sorted(ret,key=lambda x: (x[0], -x[1],x[4],x[3]))
        return ret


    # 所有点
    @property
    def ps(self):    
        return self.__ps

    @property
    def pDL(self):
        ret = self.Pos[0]
        for obj in self.Pos[1:]:
            if obj.p[1]<ret.p[1] and obj.L0!=0:
                ret = obj
            elif obj.p[1] == ret.p[1] and obj.L0!=0:
                if obj.p[0] < ret.p[0]:
                    ret = obj
        return ret


    @property
    def Pos(self):
        ps = self.__ps
        ret =[]
        for obj in ps:
            ret.append(Point(obj))

        for obj in self.Ls:
            if ps.count(obj.p1)==1:
                no = ps.index(obj.p1)
                ret[no].Ls.append(obj)
                obj.Po1 = ret[no]
            if ps.count(obj.p2)==1:
                no = ps.index(obj.p2)
                ret[no].Ls.append(obj)
                obj.Po2 = ret[no]

        return ret





class SepRecs():
    def __init__(self,lines):
        self.Lines = lines
        self.outLines0 = []
        self.inLines0 = []

5.3 lsp代码

;;;; 2023-3-7 星期二 天气晴
;;;; kiritanimirei.cn

(defun c:2265() ;;;自定义函数名        
    (startapp "qx.exe")
)

2023年3月7日 星期二 天气晴

精选博客

终于考完了

昨天终于把驾照给考出来了。说起来真正考试过程倒是并不复杂,每个科目都是一次过了,但整个过程还是略显艰辛。……

继 续 阅 读

APDL输入与输出

Mechanical APDL 在ANSYS帮助文件中是这样称呼我们习以为常的采用APDL来操作也就是那个黑乎乎的程序的。因此后续都用这个来称呼。工欲善其事必先利其器,关于APDL的编辑器,目前来看PSPAD还基本可以。ANSYS看起来并没有设置很好的接口,因为APDL看起来并不是一种很利于编程的语言,无论是数据格式还是流程都相当不友好。网上也有采用Python去做接口,但是仔细一看就是换了……

继 续 阅 读

组合杆件的长细比

本文主要讲一下对于由两种材料构成的组合杆件换算长细比的事。……

继 续 阅 读