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

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

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

2.1 圆弧对象

class DXFArc
{
public:
	double cx; // 圆心X坐标
	double cy; // 圆心Y坐标
	double radius; // 圆弧半径
	double bangle; // 起点角度
	double eangle; // 终点角度
};

2.2 椭圆弧对象

class DXFEllipse
{
public:
	double cx; // 椭圆心X坐标
	double cy; // 椭圆心Y坐标
	double mx; // 长轴端点相对于中点的X坐标
	double my; // 长轴端点相对于中点的Y坐标
	double ratio; // 短半轴长度➗长半轴长度
	double angle1; // 起始弧度
	double angle2; // 终止弧度
};

三、转DXF圆弧

3.1 数学公式

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

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

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

根据圆的参数方程,可以通过代入点 ( 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 ) \begin{cases} x=a · cos(t)+c_x \\ y=a · sin(t) + c_y \end{cases}\\ \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;
}

3.3 转换效果展示

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

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


四、转DXF椭圆弧

4.1 数学公式

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 = 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时取,其余时候取+)

根据椭圆的参数方程,可以通过代入点 ( 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 ) \begin{cases} x = a·cos(t)·cos(\alpha)-b·sin(t)·sin(\alpha)+c_x\\ y = a·cos(t)·sin(\alpha)+b·sin(t)·cos(\alpha)+c_y \end{cases}\\ \Downarrow\\ \begin{cases} cos(t) = ((x-c_x)·cos(\alpha)+(y-c_y)·sin(\alpha))/a\\ sin(t) = (-(x-c_x)·sin(\alpha)+(y-c_y)·cos(\alpha))/b\\ \end{cases}\\ \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;
}

4.3 转换效果展示

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

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


05-28 03:22