• 【脚本工具】SVG路径中的A指令转DXF的圆弧和椭圆弧 & C++代码实现



    一、SVG路径的A指令的语法说明

    目前Svg的Arc的参数字符串如下:其中,A (绝对) a (相对)

    A  rx  ry  x-axis-rotation  large-arc-flag  sweep-flag  x  y 
    
    • 1
    a  rx  ry  x-axis-rotation  large-arc-flag  sweep-flag  x  y 
    
    • 1

    除了A(a)表示标识为圆弧之外,其余参数说明如下:

    参数说明符号
    rx椭圆半长轴 a a a
    ry椭圆半短轴 b b b
    x-axis-rotation椭圆相对于坐标系的旋转角度,角度数而非弧度数 α \alpha α
    large-arc-flag是否优(大)弧:0否,1是 f l f_l fl
    sweep-flag绘制方向:0逆时针,1顺时针 f s f_s fs
    x圆弧终点的x坐标 x e x_{e} xe
    y圆弧终点的y坐标 y e y_{e} ye

    实际上,我们也可以得到圆弧起点的坐标(即上一段图像的终点):

    说明符号
    圆弧起点的x坐标 x s x_{s} xs
    圆弧起点的y坐标 y s y_{s} ys

    二、DXF中的圆弧和椭圆弧对象

    2.1 圆弧对象

    class DXFArc
    {
    public:
    	double cx; // 圆心X坐标
    	double cy; // 圆心Y坐标
    	double radius; // 圆弧半径
    	double bangle; // 起点角度
    	double eangle; // 终点角度
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 椭圆弧对象

    class DXFEllipse
    {
    public:
    	double cx; // 椭圆心X坐标
    	double cy; // 椭圆心Y坐标
    	double mx; // 长轴端点相对于中点的X坐标
    	double my; // 长轴端点相对于中点的Y坐标
    	double ratio; // 短半轴长度➗长半轴长度
    	double angle1; // 起始弧度
    	double angle2; // 终止弧度
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    三、转DXF圆弧

    3.1 数学公式

    圆弧可以看作一个长半轴等于短半轴(即满足 a = b = r a=b=r a=b=r)的特殊椭圆弧,因此,下面统一用长半轴 a a a 代替半径 r r r

    另外,圆弧的 α = 0 \alpha = 0 α=0

    第一步:计算圆弧中心 ( c x , c y ) (c_x,c_y) (cx,cy)

    x 1 ′ = ( x s − x e ) / 2 y 1 ′ = ( y s − y e ) / 2 m = ± ∣ a 4 − a 2 y 1 ′ 2 − a 2 x 1 ′ 2 ∣ / ( a 2 ( y 1 ′ 2 + x 1 ′ 2 ) ) ( 仅当 l f = l s 时取 − , 其余时候取 + ) ⇓ c x ′ = m y 1 ′ c y ′ = − m x 1 ′ ⇓ c x = c x ′ + ( x s + x e ) / 2 c y = c y ′ + ( y s + y e ) / 2 x_1'=(x_s-x_e)/2\\ y_1'= (y_s-y_e)/2\\ m=±\sqrt{|a^4-a^2y_1'^2-a^2x_1'^2|/(a^2(y_1'^2+x_1'^2))} \quad (仅当l_f=l_s时取-,其余时候取+)\\ \Downarrow\\ c_x'=m y_1'\\ c_y'=-mx_1'\\ \Downarrow\\ c_x=c_x'+(x_s+x_e)/2\\ c_y=c_y'+(y_s+y_e)/2\\ x1=(xsxe)/2y1=(ysye)/2m=±a4a2y1′2a2x1′2∣/(a2(y1′2+x1′2)) (仅当lf=ls时取,其余时候取+)cx=my1cy=mx1cx=cx+(xs+xe)/2cy=cy+(ys+ye)/2

    第二步:计算起始和终止角度 ( b a n g l e , e a n g l e ) (bangle,eangle) (bangle,eangle)

    根据圆的参数方程,可以通过代入点 ( x , y ) (x,y) (x,y),从而反推出该点的角度 t t t

    { x = a ⋅ c o s ( t ) + c x y = a ⋅ s i n ( t ) + c y ⇓ t = ± a r c c o s ( ( x − c x ) / a ) {x=a·cos(t)+cxy=a·sin(t)+cy

    \\ \Downarrow\\ t=±arccos((x-c_x)/a) {x=acos(t)+cxy=asin(t)+cyt=±arccos((xcx)/a)

    其中, t t t 的符号和 a r c s i n ( ( y − c y ) / a ) arcsin((y-c_y)/a) arcsin((ycy)/a) 的相同。

    基于此,我们可以通过将圆弧的起点 ( x s , y s ) (x_s,y_s) (xs,ys) 和终点 ( x e , y e ) (x_e,y_e) (xe,ye) 分别代入圆的参数方程,反推出圆弧的起始和终止角度 ( b a n g l e , e a n g l e ) (bangle,eangle) (bangle,eangle)

    3.2 代码实现

    // 圆周率
    #define PI acos(-1)
    // 浮点型数据精度
    #define ERROR 0.00000001
    
    // 根据圆的参数方程和圆上一个点的坐标,计算该点的角度
    // r:圆弧半径
    // (cx,cy):圆弧的中心点
    // (x,y):圆弧上某点的坐标
    double calcArcPointAngle(double r, double cx, double cy, double x, double y){
    	double res = acos((x - cx)/r);
    	if (asin((y - cy)/r) < 0){
    		res = -res;
    	}
    	return res / PI * 180;
    }
    
    // 传入SVG的A指令的相关参数,返回对应的DXF中的圆弧对象
    // a:圆弧半径
    // lf:是否优(大)弧:0否,1是
    // sf:绘制方向:0逆时针,1顺时针
    // (startX,startY):圆弧的起点
    // (endX,endY):圆弧的终点
    DXFArc getDXFArcBySvg(double a,int lf,int sf,double startX,double startY,double endX,double endY){
    	DXFArc arc = DXFArc();
    	// 圆弧半径
    	arc.radius = a;
    	// 计算中点(cx,cy)
    	double x1Pie = 0.5 * (startX - endX);
    	double y1Pie = 0.5 * (startY - endY);
    	double m = sqrt(abs(a*a*a*a - a*a*y1Pie*y1Pie - b*b*x1Pie*x1Pie) / (a*a* (y1Pie*y1Pie+x1Pie*x1Pie)));
    	if (lf == sf){
    		m = -m;
    	}
    	double cxPie = m * y1Pie;
    	double cyPie = m * -x1Pie;
    	arc.cx = cxPie + 0.5 * (startX + endX);
    	arc.cy = cyPie + 0.5 * (startY + endY);
    	// 计算角度(bangle,eangle)
    	arc.bangle = calcArcPointAngle(a, arc.cx, arc.cy,startX,startY);
    	arc.eangle = calcArcPointAngle(a, arc.cx, arc.cy, endX, endY);
    	// 修正角度
    	while (arc.eangle < arc.bangle){
    		arc.eangle += 360.0;
    	}
    	if ((lf == 1 && arc.eangle - arc.bangle < 180.0) || (lf == 0 && arc.eangle - arc.bangle > 180.0)){
    		double temp = arc.bangle;
    		arc.bangle = arc.eangle;
    		arc.eangle = temp;
    	}
    	// 返回转化好的圆弧
    	return arc;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    3.3 转换效果展示

    SVG显示效果如下

    在这里插入图片描述

    转化为DXF后的显示效果如下

    在这里插入图片描述


    四、转DXF椭圆弧

    4.1 数学公式

    第一步:计算椭圆弧中心 ( c x , c y ) (c_x,c_y) (cx,cy)

    x 1 ′ = c o s ( α ) ⋅ ( x s − x e ) / 2 + s i n ( α ) ⋅ ( y s − y e ) / 2 y 1 ′ = − s i n ( α ) ⋅ ( x s − x e ) / 2 + c o s ( α ) ⋅ ( y s − y e ) / 2 m = ± ∣ a 2 b 2 − a 2 y 1 ′ 2 − b 2 x 1 ′ 2 ∣ / ( a 2 y 1 ′ 2 + b 2 x 1 ′ 2 ) ( 仅当 l f = l s 时取 − , 其余时候取 + ) ⇓ c x ′ = m ( a y 1 ′ ) / b c y ′ = − m ( b x 1 ′ ) / a ⇓ c x = c o s ( α ) ⋅ c x ′ − s i n ( α ) ⋅ c y ′ + ( x s + x e ) / 2 c y = s i n ( α ) ⋅ c x ′ + c o s ( α ) ⋅ c y ′ + ( y s + y e ) / 2 x_1'=cos(\alpha)·(x_s-x_e)/2+sin(\alpha)·(y_s-y_e)/2\\ y_1'= -sin(\alpha)·(x_s-x_e)/2+cos(\alpha)·(y_s-y_e)/2\\ m=±\sqrt{|a^2b^2-a^2y_1'^2-b^2x_1'^2|/(a^2y_1'^2+b^2x_1'^2)} \quad (仅当l_f=l_s时取-,其余时候取+)\\ \Downarrow\\ c_x'=m(ay_1')/b\\ c_y'=-m(bx_1')/a\\ \Downarrow\\ c_x=cos(\alpha)·c_x'-sin(\alpha)·c_y'+(x_s+x_e)/2\\ c_y=sin(\alpha)·c_x'+cos(\alpha)·c_y'+(y_s+y_e)/2\\ x1=cos(α)(xsxe)/2+sin(α)(ysye)/2y1=sin(α)(xsxe)/2+cos(α)(ysye)/2m=±a2b2a2y1′2b2x1′2∣/(a2y1′2+b2x1′2) (仅当lf=ls时取,其余时候取+)cx=m(ay1)/bcy=m(bx1)/acx=cos(α)cxsin(α)cy+(xs+xe)/2cy=sin(α)cx+cos(α)cy+(ys+ye)/2

    第二步:计算长轴端点相对中点的坐标 ( m x , m y ) (m_x,m_y) (mx,my)

    m x = a ⋅ c o s ( α ) m y = ± a 2 − m x 2 ( 仅当 α < 0 时取 − ,其余时候取 + ) m_x=a·cos(\alpha)\\ m_y=±\sqrt{a^2-m_x^2} \quad (仅当\alpha<0时取-,其余时候取+)\\ mx=acos(α)my=±a2mx2 (仅当α<0时取,其余时候取+)

    第三步:计算椭圆弧的起始和终止角度 ( a n g l e 1 , a n g l e 2 ) (angle1,angle2) (angle1,angle2)

    根据椭圆的参数方程,可以通过代入点 ( x , y ) (x,y) (x,y),从而反推出该点的角度 t t t

    { x = a ⋅ c o s ( t ) ⋅ c o s ( α ) − b ⋅ s i n ( t ) ⋅ s i n ( α ) + c x y = a ⋅ c o s ( t ) ⋅ s i n ( α ) + b ⋅ s i n ( t ) ⋅ c o s ( α ) + c y ⇓ { c o s ( t ) = ( ( x − c x ) ⋅ c o s ( α ) + ( y − c y ) ⋅ s i n ( α ) ) / a s i n ( t ) = ( − ( x − c x ) ⋅ s i n ( α ) + ( y − c y ) ⋅ c o s ( α ) ) / b ⇓ t = ± a r c c o s ( ( ( x − c x ) ⋅ c o s ( α ) + ( y − c y ) ⋅ s i n ( α ) ) / a ) {x=a·cos(t)·cos(α)b·sin(t)·sin(α)+cxy=a·cos(t)·sin(α)+b·sin(t)·cos(α)+cy

    \\ \Downarrow\\ {cos(t)=((xcx)·cos(α)+(ycy)·sin(α))/asin(t)=((xcx)·sin(α)+(ycy)·cos(α))/b
    \\ \Downarrow\\ t=±arccos(((x-c_x)·cos(\alpha)+(y-c_y)·sin(\alpha))/a) {x=acos(t)cos(α)bsin(t)sin(α)+cxy=acos(t)sin(α)+bsin(t)cos(α)+cy{cos(t)=((xcx)cos(α)+(ycy)sin(α))/asin(t)=((xcx)sin(α)+(ycy)cos(α))/bt=±arccos(((xcx)cos(α)+(ycy)sin(α))/a)

    其中, t t t 的符号和 a r c s i n ( ( − ( x − c x ) ⋅ s i n ( α ) + ( y − c y ) ⋅ c o s ( α ) ) / b ) arcsin((-(x-c_x)·sin(\alpha)+(y-c_y)·cos(\alpha))/b) arcsin(((xcx)sin(α)+(ycy)cos(α))/b) 的相同。

    基于此,我们可以通过将圆弧的起点 ( x s , y s ) (x_s,y_s) (xs,ys) 和终点 ( x e , y e ) (x_e,y_e) (xe,ye) 分别代入椭圆的参数方程,反推出椭圆弧的起始和终止角度 ( a n g l e 1 , a n g l e 2 ) (angle1,angle2) (angle1,angle2)

    4.2 代码实现

    // 圆周率
    #define PI acos(-1)
    // 浮点型数据精度
    #define ERROR 0.00000001
    
    // 根据椭圆的参数方程和椭圆上一个点的坐标,计算该点的角度
    // a:长轴半径
    // b:短轴半径
    // alpha:椭圆弧长半轴与x正半轴的夹角(弧度)
    // (cx,cy):椭圆弧的中心点
    // (x,y):椭圆弧上某点的坐标
    double calcEllipsePointAngle(double a,double b,double alpha,double cx,double cy,double x,double y){
    	double c = ((x - cx)*cos(alpha) + (y - cy)*sin(alpha)) / a;
    	// 防止出现 acos(1.00000001)=-1.#IND的情况
    	if (abs(c - 1) <= ERROR){
    		c = 1;
    	}
    	// 防止出现 acos(-1.00000001)=-1.#IND的情况
    	if (abs(c + 1) <= ERROR){
    		c = -1;
    	}
    	double res = acos(c);
    	c = (-(x - cx)*sin(alpha) + (y - cy)*cos(alpha)) / b;
    	// 防止出现 asin(1.00000001)=-1.#IND的情况
    	if (abs(c - 1) <= ERROR){
    		c = 1;
    	}
    	// 防止出现 asin(-1.00000001)=-1.#IND的情况
    	if (abs(c + 1) <= ERROR){
    		c = -1;
    	}
    	if (asin(c) < 0){
    		res = -res;
    	}
    	return res / PI * 180;
    }
    
    // 传入SVG的A指令的相关参数,返回对应的DXF中的椭圆弧对象
    // a:长轴半径
    // b:短轴半径
    // lf:是否优(大)弧:0否,1是
    // sf:绘制方向:0逆时针,1顺时针
    // alpha:椭圆弧长半轴与x正半轴的夹角(弧度)
    // (startX,startY):椭圆弧的起点
    // (endX,endY):椭圆弧的终点
    DXFEllipse getDXFEllipseBySvg(double a,double b,int lf,int sf,double alpha,double startX,double startY,double endX,double endY){
    	DXFEllipse ellipse = DXFEllipse();
    	// 短半轴长度➗长半轴长度
    	ellipse.ratio = b / a;
    	// 计算中点(cx,cy)
    	double x1Pie = cos(alpha) * 0.5 * (startX - endX) + sin(alpha) * 0.5 * (startY - endY);
    	double y1Pie = -sin(alpha) * 0.5 * (startX - endX) + cos(alpha) * 0.5 * (startY - endY);
    	double m = sqrt(abs(a*a*b*b - a*a*y1Pie*y1Pie - b*b*x1Pie*x1Pie) / (a*a*y1Pie*y1Pie + b*b*x1Pie*x1Pie));
    	if (lf == sf){
    		m = -m;
    	}
    	double cxPie = m * ((a * y1Pie) / b);
    	double cyPie = -m * ((b * x1Pie) / a);
    	ellipse.cx = cos(alpha) * cxPie - sin(alpha) * cyPie + 0.5 * (startX + endX);
    	ellipse.cy = sin(alpha) * cxPie + cos(alpha) * cyPie + 0.5 * (startY + endY);
    	// 计算长轴端点相对中点的坐标(mx,my)
    	ellipse.mx = a * cos(alpha);
    	ellipse.my = sqrt(a*a - ellipse.mx*ellipse.mx);
    	if (alpha < 0){
    		ellipse.my = -ellipse.my;
    	}
    	// 计算角度(angle1,angle2)
    	ellipse.angle1 = calcEllipsePointAngle(a, b, alpha, ellipse.cx, ellipse.cy, startX, startY) / 180.0 * PI;
    	ellipse.angle2 = calcEllipsePointAngle(a, b, alpha, ellipse.cx, ellipse.cy, endX, endY) / 180.0 * PI;
    	// 修正角度
    	while (ellipse.angle2 < ellipse.angle1){
    		ellipse.angle2 += (2 * PI);
    	}
    	if ((lf == 1 && ellipse.angle2 - ellipse.angle1 < PI) || (lf == 0 && ellipse.angle2 - ellipse.angle1 > PI)){
    		double temp = ellipse.angle1;
    		ellipse.angle1 = ellipse.angle2;
    		ellipse.angle2 = temp;
    	}
    	// 返回转化好的椭圆弧
    	return ellipse;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    4.3 转换效果展示

    SVG显示效果如下

    在这里插入图片描述

    转化为DXF后的显示效果如下

    在这里插入图片描述


    本博客的参考链接如下,第一章内容两篇都有借鉴。计算圆弧和椭圆弧中点的公式借鉴了第2个参考博客,公式与他的有所不同,增加了一个绝对值:

    1. SVG路径(path)中的圆弧(A)指令的语法说明及计算逻辑
    2. 根据SVG Arc求出其开始角、摆动角和椭圆圆心
  • 相关阅读:
    图神经网络简介
    【中阳期货】如何判断入场点
    人工智能知识全面讲解:你真的了解数据吗?
    关于开源和闭源
    【华为OD机试真题 python】 绘图机器【2022 Q4 | 100分】
    三、Mediasoup进程通信实现的原理
    oracle-buffer cache
    使用mysql的cmd窗口,运行项目中的mapper层xml里的sql语句,查看运行结果
    在架构组工作是种什么体验?今天大鸡腿带你体验下~
    自动化运维监控展示三剑客:Telegraf+Influxdb 2.4+ Grafana
  • 原文地址:https://blog.csdn.net/weixin_51545953/article/details/130897905