next.js 用于产品草图绘制的JS库

k10s72fa  于 2023-08-04  发布在  其他
关注(0)|答案(1)|浏览(98)

我需要帮助找到一个好的JS库,可以创建这样的产品草图:Partition sketch。我使用Next.js,并且由于Next.js使用SSR并且大多数图形库运行客户端,因此大多数库都有问题。我不需要显示草图(neccessarrily)给用户,它应该只是一个可下载的SVG文件,是基于用户从网页输入。
理想情况下,我希望它是一个PDF格式,可以直接发送给客户,与所有需要的信息和草图。这可能吗?
我试过使用Rhino3dm.js和P5.js,但这两个都很难用Next.js实现。

s1ag04yj

s1ag04yj1#

有一个p5.svg renderer,你可以尝试在p5中绘制相同的测量/线条,但直接渲染到SVG而不是画布(完全绕过.3dm格式)。这个库最近似乎没有更新,但是你可以从一个旧的版本/例子开始,比如this one,然后复制它/修改它来原型化你需要的东西。
如果.3dm更好,你根本不需要p5.js:rhino 3dmjs库应该处理绘制直线/曲线/文本(如果你有来自用户的输入)并保存到.3dm。
您可以查看SampleSketch2d的实时示例。(source
x1c 0d1x的数据
你也可以运行下面的例子)

const downloadButton = document.getElementById("downloadButton")
downloadButton.onclick = download

// global variables
let _model = {
  // saved nurbs curves
  curves: [],
  // new nurbs curve
  points: null,
  // viewport for canvas
  viewport: null,
}

// wait for the rhino3dm web assembly to load asynchronously
let rhino
rhino3dm().then(async m => {
  console.log('Loaded rhino3dm.')
  rhino = m // global
  run()
})
/**/

// initialize canvas and model
function run() {
  let canvas = getCanvas()
  canvas.addEventListener('mousedown', onMouseDown)
  canvas.addEventListener('mousemove', onMouseMove)
  window.addEventListener('keyup', onKeyUp)
  _model.points = new rhino.Point3dList()
  _model.viewport = new rhino.ViewportInfo()
  _model.viewport.screenPort = [0, 0, canvas.clientWidth, canvas.clientHeight]
  _model.viewport.setFrustum(-30,30,-30,30,1,1000)
  draw()
}

function download() {
  if(_model.curves.length<1){
    console.log('no curves')
    return
  }

  let doc = new rhino.File3dm()
  for(let i=0; i<_model.curves.length;i++) {
    doc.objects().add(_model.curves[i], null)
  }

  let options = new rhino.File3dmWriteOptions()
  options.version = 7
  let buffer = doc.toByteArray(options)
  saveByteArray("sketch2d"+ options.version +".3dm", buffer)
  doc.delete()
}

function saveByteArray(fileName, byte) {
  let blob = new Blob([byte], {type: "application/octect-stream"})
  let link = document.createElement('a')
  link.href = window.URL.createObjectURL(blob)
  link.download = fileName
  link.click()
}

/* * * * * * * * * * * * * * * *  interaction   * * * * * * * * * * * * * * * */

// handles mouse down events
// adds a new control point at the location of the mouse
function onMouseDown(event) {
  // get the location of the mouse on the canvas
  let [x,y] = getXY(event)

  // if this is a brand new curve, add the first control point
  if (_model.points.count === 0) {
    _model.points.add(x, y, 0)
  }

  // add a new control point that will be saved on the next mouse click
  // (the location of the previous control point is now frozen)
  _model.points.add(x, y, 0)
  draw()
}

// handles mouse move events
// the last control point in the list follows the mouse
function onMouseMove(event) {
  let index = _model.points.count - 1
  if (index >= 0) {
    let [x,y] = getXY(event)
    _model.points.set(index, [x, y, 0])
    draw()
  }
}

// handles key up events
function onKeyUp( event ) {
  switch ( event.key ) {
    // when the enter key is pressed, save the new nurbs curve
    case "Enter":
      if (_model.points.count < 4) { // 3 pts (min.) + next pt
        console.error('Not enough points!')
      } else {
        // remove the last point in the list (a.k.a. next)
        let index = _model.points.count - 1
        _model.points.removeAt(index)

        // construct a curve from the points list
        let degree = _model.points.count - 1
        if (degree > 3)
          degree = 3

        // construct a nurbs curve
        // (first arg == true to create a closed periodic uniform curve)
        _model.curves.push(rhino.NurbsCurve.create(true, degree, _model.points))
      }

      // clear points list
      _model.points.clear()
      
      // enable download button
      downloadButton.disabled = false
      break
  }
  draw()
}

/* * * * * * * * * * * * * * * * *  helpers   * * * * * * * * * * * * * * * * */

// gets the canvas
function getCanvas() {
  return document.getElementById('canvas')
}

// gets the [x, y] location of the mouse in world coordinates
function getXY(evt) {
  let canvas = getCanvas()
  let rect = canvas.getBoundingClientRect()
  let x = evt.clientX - rect.left
  let y = evt.clientY - rect.top
  let s2w = _model.viewport.getXform(rhino.CoordinateSystem.Screen, rhino.CoordinateSystem.World)
  let world_point = rhino.Point3d.transform([x,y,0], s2w)
  s2w.delete()
  return [world_point[0],world_point[1]]
}

/* * * * * * * * * * * * * * * * *  drawing   * * * * * * * * * * * * * * * * */

// clears the canvas and draws the model
// for some reason removing semicolons causes an error in this method
function draw() {
  // get canvas' 2d context
  let canvas = getCanvas();
  let ctx = canvas.getContext('2d');
  let w2s = _model.viewport.getXform(rhino.CoordinateSystem.World, rhino.CoordinateSystem.Screen);

  // clear and draw a grid
  ctx.beginPath();
  ctx.lineWidth = 0.5;
  ctx.strokeStyle = 'rgb(130,130,130)';
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for(let i=0; i<50; i+=1){
    let [x,y] = rhino.Point3d.transform([i,-50,0], w2s);
    let [x1,y1] = rhino.Point3d.transform([i,50,0], w2s);
    ctx.moveTo(x,y);
    ctx.lineTo(x1,y1);
    [x,y] = rhino.Point3d.transform([-i,-50,0], w2s);
    [x1,y1] = rhino.Point3d.transform([-i,50,0], w2s);
    ctx.moveTo(x,y);
    ctx.lineTo(x1,y1);

    [x,y] = rhino.Point3d.transform([-50, i, 0], w2s);
    [x1,y1] = rhino.Point3d.transform([50, i, 0], w2s);
    ctx.moveTo(x,y);
    ctx.lineTo(x1,y1);
    [x,y] = rhino.Point3d.transform([-50, -i, 0], w2s);
    [x1,y1] = rhino.Point3d.transform([50, -i, 0], w2s);
    ctx.moveTo(x,y);
    ctx.lineTo(x1,y1);
  }
  ctx.stroke();

  ctx.lineWidth = 2;
  ctx.strokeStyle = 'rgb(150,75,75)';
  let [x,y] = rhino.Point3d.transform([0,0,0], w2s);
  let [x1,y1] = rhino.Point3d.transform([50,0,0], w2s);
  ctx.beginPath();
  ctx.moveTo(x,y);
  ctx.lineTo(x1,y1);
  ctx.stroke();
  ctx.beginPath();
  ctx.strokeStyle = 'rgb(75,150,75)';
  [x1,y1] = rhino.Point3d.transform([0,50,0], w2s);
  ctx.moveTo(x,y);
  ctx.lineTo(x1,y1);
  ctx.stroke();

  // draw saved nurbs curves
  for (let i=0; i<_model.curves.length; i++)
    drawNurbsCurve(ctx, _model.curves[i], w2s);

  // create a temporary curve from the points and draw it
  if (_model.points !== null && _model.points.count > 0) {
    let degree = _model.points.count - 1;
    if (degree > 3)
      degree = 3;
    let curve = rhino.NurbsCurve.create(true, degree, _model.points);
    drawNurbsCurve(ctx, curve, w2s);

    // draw control polygon from the temp curve's control points
    //drawControlPolygon(ctx, curve.points());
    drawControlPolygon(ctx, _model.points);

    // delete the temp curve when we're done using it
    // (webassembly memory management isn't great)
    curve.delete();
  }

  w2s.delete();
}

// draws a nurbs curve
function drawNurbsCurve(ctx, curve, w2s) {
  ctx.lineWidth = 1
  ctx.strokeStyle = 'black'

  const divisions = 200 // TODO: dynamic
  ctx.beginPath()

  let [t0,t1] = curve.domain
  let world_point = curve.pointAt(t0)
  let screen_point = rhino.Point3d.transform(world_point, w2s)
  ctx.moveTo(screen_point[0],screen_point[1])
  for (let j=1; j<=divisions; j++) {
    let t = t0 + j / divisions * (t1-t0)
    world_point = curve.pointAt(t)
    let screen_point = rhino.Point3d.transform(world_point, w2s)
    ctx.lineTo(screen_point[0],screen_point[1])
  }
  ctx.stroke()
}

// draws a control polygon
function drawControlPolygon(ctx, points) {
  // draw dashed lines between control points
  ctx.strokestyle = 'darkgray'
  ctx.setLineDash([4,4])
  ctx.beginPath()

  let w2s = _model.viewport.getXform(rhino.CoordinateSystem.World, rhino.CoordinateSystem.Screen)
  for (let i=0; i<points.count; i++) {
    let world_point = points.get(i)
    let screen_point = rhino.Point3d.transform(world_point, w2s)
    if (0 === i)
      ctx.moveTo(screen_point[0], screen_point[1])
    else
      ctx.lineTo(screen_point[0], screen_point[1])
  }
  if( points.count > 2 ){
    let world_point = points.get(0)
    let screen_point = rhino.Point3d.transform(world_point, w2s)
    ctx.lineTo(screen_point[0], screen_point[1])
  }

  ctx.stroke()

  // draw control points
  ctx.setLineDash([])
  ctx.fillStyle = 'white'
  ctx.strokeStyle = 'black'
  for (let i=0; i<points.count; i++) {
    let world_point = points.get(i)
    let screen_point = rhino.Point3d.transform(world_point, w2s)
    let [x,y,z] = screen_point
    ctx.fillRect(x-1,y-1, 3, 3)
    ctx.strokeRect(x-2, y-2, 5, 5)
  }
  w2s.delete()
}

个字符
在上面的示例中,下载按钮将被sanboxed,但在本地web服务器上运行应该没问题。
(One最后要注意的是,如果出于某种原因需要使用p5rhino 3dm(尽管性能可能会有所不同),您可以访问p5的drawingContext来获取草图的底层Canvas渲染(当不使用SVG渲染器时),以在草图的画布中绘制与上述相同的内容)。

相关问题