Closed zeffii closed 4 years ago
Wow. How come I did not find this when I searched for nurbs-python?...
It seems it has a lot of functionality, parts of which we currently have implemented either with pure python or via Geomdl. So, to integrate this, we will have to think about two big questions
i'm not sure, but I was thinking of writing a small wrapper to allow keyword args for rhino3dm constructors, the docs don't make it clear what's happening if we pass a few arguments to Arc()
. there are i think 4 or more variants of Arc.
if going the wrapper route, that would make it possible to smoothly switch backends, either globally or local.
it's an interesting approach, the Arc "class" can be instantiated differently based on the types of parameters received by the constructor.
1. rhino3dm._rhino3dm.Arc(circle: BND_Circle, angleRadians: float)
2. rhino3dm._rhino3dm.Arc(center: rhino3dm._rhino3dm.Point3d, radius: float, angleRadians: float)
3. rhino3dm._rhino3dm.Arc(startPoint: rhino3dm._rhino3dm.Point3d, pointOnInterior: rhino3dm._rhino3dm.Point3d, endPoint: rhino3dm._rhino3dm.Point3d)
4. rhino3dm._rhino3dm.Arc(pointA: rhino3dm._rhino3dm.Point3d, tangentA: rhino3dm._rhino3dm.Vector3d, pointB: rhino3dm._rhino3dm.Point3d)
C++-style :)
Just wanted to point the FreeCad api offers two kind of arcs (center, start, end) and 3pt and also NURBS... in case you want to explore...
@vicdoval when you push freecad nodes to master, i think we can help explore the rest of the freecad API.
rhino api makes brep so that's interesting, i bet there is a way to make them work interchangably to some degree
It seems we are fastly moving to state of Grasshopper, where there are a lot of "node sets" (plugins), which are partially intersecting. Look at http://grasshopperdocs.com/ , there are very similar nodes in different sections. I assume they ended up in this state the same way we are: they had not some functions in core, then someone implemented it in plugin, then someone else implemented it in another plugin "the better way", then they implemented it in core... Probably having several implementations of the same functionality is not that bad (different implementations can be better in different corner cases, for example); but this brings the question of managing this amount of nodes — we may have "Solids (Rhino)" category near "Solids (FreeCAD)", and the same with several other categories... Maybe we should have some kind of super-categories?
Technically, what I think we should do is
curve.IsCircle
, I think FreeCAD does not have it? or maybe there is a way to implement the same, maybe just checking isinstance
). This will be necesssary only if we want to expose this functionality in a new node. Such node will raise an exception if passed with an object which does not support required method.The question of how the user will select the required implementation (and how will he decide which implementation to use) is still open. Maybe we will add a popup menu into each node. Or maybe we will have several copies of each node, as Grasshopper does. Or?...
I prefer we focus on getting it working nicely using freeCAD first, and put some requests on blender.org devs to add methods to the Nurbs object type to make it possible to pass it all the information it needs to represent the Surface (without using ops).
NurbData = bpy.data.curves.new(name, "NURBSURFACE<or whatever>", packed_data_tuple)
NurbsObject = bpy.data.objects.new(name, NurbData)
NurbsObject.data.update_from_pydata(packed_data_tuple)
NurbsObject.data.update()
NurbsObject.data.validate()
NurbsObject.data.update_from_pydata(packed_data_tuple, validate=True, update=True)
multiple implementations is always possible, but now we are almost spoiled for choice, it would be useful to get better backend support for displaying this data (and let Blender do that heavy lifting for a change...it's practically implemented already... we are just missing one or two functions to use it)
Creating Blender's nurbs objects having control points and weights is the easiest part. Mathematics / algorithms required to
yes, the longer we can keep the objects being shuttled around our nodetree as 'symbolic' entities, the better.
Hmm. Rhino3dm's Curve doesn't seem to provide much differential geometry information:
So, such things we still will have to calculate numerically... Note: Geomdl does provide derivatives.
As far as i have tested FreeCad supplies NURBS (and many things I don't understand)
Also the Curve Object (at least the solid edges) can give Tangents, curvature, derivatives 2nd and 3rd and intersections i think i saw binormals somewhere too... and other things i dont understand)
rhino3dm's Nurbs curve:
"""
in control_points_in v
in weights_in s
in degree_in s
out curve_out C
"""
import numpy as np
from sverchok.data_structure import zip_long_repeat
from sverchok.utils.curve import SvCurve
from rhino3dm._rhino3dm import NurbsCurve, Point3d
class RhinoNurbsCurve(SvCurve):
def __init__(self, degree, points, weights=None, is_cyclic=False):
pts = [Point3d(*t) for t in points]
self.curve = NurbsCurve.Create(is_cyclic, degree, pts)
if weights is not None:
for pt, weight in zip_long_repeat(self.curve.Points, weights):
pt.W = weight
def is_closed(self, eps=None):
return self.curve.IsPeriodic
def evaluate(self, t):
pt = self.curve.PointAt(t)
return np.array([pt.X, pt.Y, pt.Z])
def evaluate_array(self, ts):
return np.vectorize(self.evaluate)(ts)
def get_u_bounds(self):
return (0, 1)
curve_out = []
for control_points, weights, degree in zip_long_repeat(control_points_in, weights_in, degree_in):
if isinstance(degree, (list, tuple)):
degree = degree[0]
curve = RhinoNurbsCurve(degree, control_points, weights)
curve_out.append(curve)
So, such things we still will have to calculate numerically... Note: Geomdl does provide derivatives.
tragic. for rhino3dm :)
This would be it? Not sure what "Mults" is...
Mults probably are weights ("multipliers") ?
Mults probably are weights ("multipliers") ?
I think those refer to knot multiplicities
@portnov does curve.PointAt(t)
accept multiple inputs or is that function called for every t
, the numpy
kind of makes it difficult to be sure :) , the docs suggest a single input. oh i see, you are (truely) vectorizing that.
would passing an array not be a better / faster implementation for that API, than calling that function repeatedly ?
rhino3dm's PointAt() method must be called for each t
, as far as I see...
| PointAt(...)
| PointAt(self: rhino3dm._rhino3dm.Curve, t: float) -> rhino3dm._rhino3dm.Point3d
Off course if the API allows to pass an array, such feature must be used (and called from evaluate_array()
).
https://github.com/mcneel/rhino3dm/issues
i wonder if they accept pullrequests for features like that.
PointAt(t)
PointsAt([t0, t1, t2, t3. ....tn])
.def("PointAt", &BND_BezierCurve::PointAt, py::arg("t"))
interesting to see pybind11
in the wild.
Well, obviously C++-part should support such features first. If it uses Eigen3 or boost::ublas, it should be relatively easy. Otherwise...
C++ part of rhino3dm is OpenNurbs https://developer.rhino3d.com/guides/opennurbs/what-is-opennurbs/ . Which is, as far as I understand, a small opensourced part of Rhino itself.
It does not seem to be vectorized: https://github.com/mcneel/opennurbs/blob/7.x/opennurbs_bezier.cpp#L173 .
On the other hand, my pure-python implementation of Bezier curves is vectorized with numpy: https://github.com/nortikin/sverchok/blob/master/utils/curve.py#L1640 . So, I would not be very surprised if it appeared that python implementation is actually faster than rhino3dm's, at least for some applications.
I'm already thinking about implementing nurbs evaluation in python/numpy similarly; it will be more complex than Bezier, but not so that very much. But at the moment I'm too lazy :)
So, I would not be very surprised if it appeared that python implementation is actually faster than rhino3dm's, at least for some applications.
i thought it might be.
But at the moment I'm too lazy :)
sure, until you need it ;)
FreeCAD's nurbs implementation is not, in general, vectorized either; For one single operation - evaluation - there is a function that can return several points at one time, and in some particular cases it can be useful:
| discretize(...)
| Discretizes the curve and returns a list of points.
| The function accepts keywords as argument:
| discretize(Number=n) => gives a list of 'n' equidistant points
| discretize(QuasiNumber=n) => gives a list of 'n' quasi equidistant points (is faster than the method above)
| discretize(Distance=d) => gives a list of equidistant points with distance 'd'
| discretize(Deflection=d) => gives a list of points with a maximum deflection 'd' to the curve
| discretize(QuasiDeflection=d) => gives a list of points with a maximum deflection 'd' to the curve (faster)
| discretize(Angular=a,Curvature=c,[Minimum=m]) => gives a list of points with an angular deflection of 'a'
| and a curvature deflection of 'c'. Optionally a minimum number of points
| can be set which by default is set to 2.
|
| Optionally you can set the keywords 'First' and 'Last' to define a sub-range of the parameter range
| of the curve.
|
| If no keyword is given then it depends on whether the argument is an int or float.
| If it's an int then the behaviour is as if using the keyword 'Number', if it's float
| then the behaviour is as if using the keyword 'Distance'.
|
| Example:
|
| import Part
| c=Part.Circle()
| c.Radius=5
| p=c.discretize(Number=50,First=3.14)
| s=Part.Compound([Part.Vertex(i) for i in p])
| Part.show(s)
|
|
| p=c.discretize(Angular=0.09,Curvature=0.01,Last=3.14,Minimum=100)
| s=Part.Compound([Part.Vertex(i) for i in p])
| Part.show(s)
totally this.
discretize(Number=n) => gives a list of 'n' equidistant points
FreeCAD's Nurbs curve
"""
in control_points_in v
in weights_in s
in degree_in s
out curve_out C
"""
import numpy as np
from sverchok.data_structure import zip_long_repeat, repeat_last_for_length
from sverchok.utils.curve import SvCurve
from FreeCAD import Base
import Part
class FreeCadNurbsCurve(SvCurve):
def __init__(self, degree, points, weights=None):
pts = [Base.Vector(*t) for t in points]
print(pts)
pts_count = len(points)
knots_count = pts_count + degree
order = degree+1
self.curve = Part.BSplineCurve()
knots = np.linspace(0, 1, num=pts_count-2).tolist()
mults = [degree+1] + [1]*(len(knots)-degree-1) + [degree+1]
print(knots)
print(mults)
self.curve.buildFromPolesMultsKnots(pts, mults, knots, False, degree, weights)
def is_closed(self, eps=None):
return self.curve.isClosed()
def evaluate(self, t):
pt = self.curve.value(t)
r = np.array([pt.x, pt.y, pt.z])
#print(r)
return r
def evaluate_array(self, ts):
return np.vectorize(self.evaluate, signature='()->(3)')(ts)
def tangent(self, t):
v = self.curve.tangent(t)
return np.array([v.x, v.y, v.z])
def tangent_array(self, ts):
return np.vectorize(self.tangent, signature='()->(3)')(ts)
def get_u_bounds(self):
return (0, 1)
curve_out = []
for control_points, weights, degree in zip_long_repeat(control_points_in, weights_in, degree_in):
if isinstance(degree, (list, tuple)):
degree = degree[0]
weights = repeat_last_for_length(weights, len(control_points))
curve = FreeCadNurbsCurve(degree, control_points, weights)
curve_out.append(curve)
clearly FreeCAD is looking like it will get preferential treatment :)
anything we can ransack from rhino3dm is just bonus :) (i thought the export functions look particularly useful )
i'll dig around in the API tomorrow. related content: https://www.rhino3d.com/download/rhino/6/essentialmathematics