将 SVG 路径弧转换为 DXF

问题描述 投票:0回答:1

我正在创建一个从 svg 到 dxf 的自定义转换器,需要一个数学向导来帮助我找出我的逻辑有缺陷的地方。

这是我当前的代码:

function svgArcToLWPolyline(rx, ry, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2) {
    const scaleX = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) / (2 * rx);
    const scaleY = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) / (2 * ry);

    const arcInfo = svgArcToOvalArc(rx * scaleX, ry * scaleY, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2);
    const numPoints = calculateNumberOfPoints(rx * scaleX, ry * scaleY, arcInfo.startAngle, arcInfo.endAngle);

    // Calculate bulge factors
    const bulgeFactors = [];
    const angleIncrement = (arcInfo.endAngle - arcInfo.startAngle) / (numPoints - 1);
    let currentAngle = arcInfo.startAngle;
    for (let i = 0; i < numPoints - 1; i++) {
        const nextAngle = currentAngle + angleIncrement;
        const bulge = Math.tan((nextAngle - currentAngle) * Math.PI / 360);
        bulgeFactors.push(bulge);
        currentAngle = nextAngle;
    }
    bulgeFactors.push(0); // Last point has zero bulge

    // Construct LWPOLYLINE points
    const lwpolylinePoints = [];
    for (let i = 0; i < numPoints; i++) {
        const angle = arcInfo.startAngle + i * angleIncrement;
        const x = arcInfo.cx + rx * scaleX * Math.cos(angle * Math.PI / 180);
        const y = arcInfo.cy + ry * scaleY * Math.sin(angle * Math.PI / 180);
        lwpolylinePoints.push([x, y, bulgeFactors[i]]);
    }

    return lwpolylinePoints;
}

function svgArcToOvalArc(rx, ry, rotation, largeArcFlag, sweepFlag, x1, y1, x2, y2) {
    // Convert rotation angle to radians
    const angle = rotation * Math.PI / 180;

    // Calculate intermediate values
    const dx = (x1 - x2) / 2;
    const dy = (y1 - y2) / 2;
    const x1p = Math.cos(angle) * dx + Math.sin(angle) * dy;
    const y1p = -Math.sin(angle) * dx + Math.cos(angle) * dy;
    const rxSq = rx * rx;
    const rySq = ry * ry;
    const x1pSq = x1p * x1p;
    const y1pSq = y1p * y1p;
    let radicand = (rxSq * rySq - rxSq * y1pSq - rySq * x1pSq) / (rxSq * y1pSq + rySq * x1pSq);

    // Ensure non-negative radicand
    if (radicand < 0) {
        radicand = 0;
    }

    // Calculate root
    let root = Math.sqrt(radicand);
    if (largeArcFlag === sweepFlag) {
        root = -root;
    }
    const cxp = root * rx * y1p / ry;
    const cyp = -root * ry * x1p / rx;

    // Calculate center
    const cx = Math.cos(angle) * cxp - Math.sin(angle) * cyp + (x1 + x2) / 2;
    const cy = Math.sin(angle) * cxp + Math.cos(angle) * cyp + (y1 + y2) / 2;

    // Calculate start and end angles
    let startAngle = Math.atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
    let endAngle = Math.atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);

    // Convert angles to degrees
    startAngle *= 180 / Math.PI;
    endAngle *= 180 / Math.PI;

    // Adjust angles to be in the range [0, 360]
    if (startAngle < 0) {
        startAngle += 360;
    }
    if (endAngle < 0) {
        endAngle += 360;
    }

    return { cx: cx, cy: cy, startAngle: startAngle, endAngle: endAngle };
}

function calculateNumberOfPoints(rx, ry, startAngle, endAngle) {
    // Calculate arc length
    let arcLength;
    if (startAngle <= endAngle) {
        arcLength = (endAngle - startAngle) * Math.PI / 180;
    } else {
        arcLength = (360 - startAngle + endAngle) * Math.PI / 180;
    }
    arcLength *= (rx + ry) / 2;

    // Choose a fixed length for each segment
    const segmentLength = 1.0;  // Adjust as needed

    // Calculate the number of points
    const numPoints = Math.max(Math.floor(arcLength / segmentLength), 2);  // Minimum of 2 points

    return numPoints;
}

这是使用 d="M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z" 的 svg 的样子 SVG: svg 路径为 M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z DXF: 路径M10,10L20,25A1,3,0,0,0,50,50L90,90V80Z的dxf转换

如您所见,没有考虑 ry 和 rx。当我更改路径以添加 xRotationalAxis 时,我的 dxf 中断更多: d="M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z": SVG: svg 路径为 M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z DXF: M10,10L20,25A1,3,45,0,0,50,50L90,90V80Z的dxf转换

我花了 12 个小时尝试调整这个并从数学上弄清楚如何让它工作(在 ChatGPT 的帮助下)所以我能得到的任何帮助都会非常好!

javascript svg data-conversion dxf
1个回答
0
投票

恐怕计算助手中存在多个错误。
最重要的是,您需要通过参数化计算实际的

rx
ry
值。

换句话说

A 1,3, 45,0,0,50,50
不包含 rx/ry 的绝对值 – rx=1 和 ry=3 是相对值。
这是一个基于cuixiping的回答“计算SVG圆弧中心”的示例

// arc command values
let p0 = {
  x: 20,
  y: 25
}
let values = [1, 3, 45, 0, 0, 50, 50]
let vertices = 12;


let polylinePts = arcToPolyline(p0, values, vertices)
//render polyline
polyline.setAttribute('points', polylinePts.map(pt => {
  return `${pt.x} ${pt.y}`
}))


function arcToPolyline(p0, values, vertices = 12) {
  let [rxC, ryC, xAxisRotation, largeArc, sweep, x, y] = values;

  // parametrize arc command to get actual rx,ry and center
  let param = svgArcToCenterParam(p0.x, p0.y, rxC, ryC, xAxisRotation, largeArc, sweep, x, y);
  let {
    cx,
    cy,
    rx,
    ry,
    startAngle,
    deltaAngle,
    endAngle
  } = param;
  let splitAngle = deltaAngle / vertices;

  let pts = []
  for (let i = 0; i <= vertices; i++) {
    let angle = startAngle - splitAngle * i;
    let xAxisRotation_radian = xAxisRotation * Math.PI / 180;
    let pt = getEllipsePointForAngle(cx, cy, rx, ry, xAxisRotation_radian, deltaAngle + angle)
    pts.push(pt)
  }
  return pts;
}


/**
 * based on @cuixiping;
 * https://stackoverflow.com/questions/9017100/calculate-center-of-svg-arc/12329083#12329083
 */
function svgArcToCenterParam(p0x, p0y, rx, ry, angle, largeArc, sweep, px, py) {
  const radian = (ux, uy, vx, vy) => {
    let dot = ux * vx + uy * vy;
    let mod = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
    let rad = Math.acos(dot / mod);
    if (ux * vy - uy * vx < 0) {
      rad = -rad;
    }
    return rad;
  };
  // degree to radian – if rx equals ry the x-axis rotation has no effect
  let phi = rx === ry ? 0 : (angle * Math.PI) / 180;
  let cx, cy, startAngle, deltaAngle, endAngle;
  let PI = Math.PI;
  //let PIpx = PI * 2;

  // invalid arguments
  if (rx == 0 || ry == 0) {
    throw Error("rx and ry can not be 0");
  }
  if (rx < 0) {
    rx = -rx;
  }
  if (ry < 0) {
    ry = -ry;
  }
  let s_phi = Math.sin(phi);
  let c_phi = Math.cos(phi);
  let hd_x = (p0x - px) / 2; // half diff of x
  let hd_y = (p0y - py) / 2; // half diff of y
  let hs_x = (p0x + px) / 2; // half sum of x
  let hs_y = (p0y + py) / 2; // half sum of y
  // F6.5.1
  let p0x_ = c_phi * hd_x + s_phi * hd_y;
  let p0y_ = c_phi * hd_y - s_phi * hd_x;
  // F.6.6 Correction of out-of-range radii
  //   Step 3: Ensure radii are large enough
  let lambda = (p0x_ * p0x_) / (rx * rx) + (p0y_ * p0y_) / (ry * ry);
  if (lambda > 1) {
    rx = rx * Math.sqrt(lambda);
    ry = ry * Math.sqrt(lambda);
  }
  let rxry = rx * ry;
  let rxp0y_ = rx * p0y_;
  let ryp0x_ = ry * p0x_;
  let sum_of_sq = rxp0y_ * rxp0y_ + ryp0x_ * ryp0x_; // sum of square
  if (!sum_of_sq) {
    console.log("start point can not be same as end point");
  }
  let coe = Math.sqrt(Math.abs((rxry * rxry - sum_of_sq) / sum_of_sq));
  if (largeArc == sweep) {
    coe = -coe;
  }
  // F6.5.2
  let cx_ = (coe * rxp0y_) / ry;
  let cy_ = (-coe * ryp0x_) / rx;
  // F6.5.3
  cx = c_phi * cx_ - s_phi * cy_ + hs_x;
  cy = s_phi * cx_ + c_phi * cy_ + hs_y;
  let xcr1 = (p0x_ - cx_) / rx;
  let xcr2 = (p0x_ + cx_) / rx;
  let ycr1 = (p0y_ - cy_) / ry;
  let ycr2 = (p0y_ + cy_) / ry;
  // F6.5.5
  startAngle = radian(1, 0, xcr1, ycr1);
  // F6.5.6
  deltaAngle = radian(xcr1, ycr1, -xcr2, -ycr2);
  if (deltaAngle > PI * 2) {
    deltaAngle -= PI * 2;
  } else if (deltaAngle < 0) {
    deltaAngle += PI * 2;
  }
  if (sweep == false || sweep == 0) {
    deltaAngle -= PI * 2;
  }
  endAngle = startAngle + deltaAngle;
  if (endAngle > PI * 2) {
    endAngle -= PI * 2;
  } else if (endAngle < 0) {
    endAngle += PI * 2;
  }
  let outputObj = {
    cx: cx,
    cy: cy,
    rx: rx,
    ry: ry,
    startAngle: startAngle,
    deltaAngle: deltaAngle,
    endAngle: endAngle,
    sweep: sweep
  };
  return outputObj;
}



/*
 * based on
 * https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/modules/canvaskit/htmlcanvas/path2d.js
 * and https://observablehq.com/@toja/ellipse-and-elliptical-arc-conversion
 */

function getEllipsePointForAngle(cx, cy, rx, ry, rotation_angle, angle) {
  let M = Math.abs(rx) * Math.cos(angle),
    N = Math.abs(ry) * Math.sin(angle);
  return {
    x: cx + Math.cos(rotation_angle) * M - Math.sin(rotation_angle) * N,
    y: cy + Math.sin(rotation_angle) * M + Math.cos(rotation_angle) * N
  };
}
svg {
  overflow: visible;
  border: 1px solid #ccc;
}
<svg id="svg" viewBox="-9 0 100 100">
  <path id="path" d="M 10 10
          L 20 25
          A 1 3 45 0 0 50 50
          L 90 90
          V 80 
          Z" fill="none" stroke="#000" />
  <polyline id="polyline" fill="none" stroke="red"></polyline>
</svg>

如何运作

  1. svgArcToCenterParam(p0x, p0y, rx, ry, angle, largeArc, sweep, px, py)
    返回参数化椭圆参数
  2. 现在我们可以通过
    getEllipsePointForAngle(cx, cy, rx, ry, rotation_angle, angle)
  3. 计算椭圆上的多个点

您也可以从我的多边形路径助手(或简单示例)中获得灵感。它基于自定义路径数据解析器并计算每个路径段的长度。这样我们就可以根据路径点命令计算多边形顶点。

© www.soinside.com 2019 - 2024. All rights reserved.