User Tools

Site Tools


start

Blog

From a Pointcloud slice to a sketcher spline

Point cloud slices are planar point clouds which can be used to create 2D curves.

These slices can be generated simply from images with edge detection algorithms. An other way is to slice 3D Point clouds.

A point cloud holds typically thousends of points. And the points are not located on exact positions. Scanner and Cameras generate some rounding errors and noise.

It's interesting to have a way from such a point cloud back to an near exact CAD model.

This is an introduction how to achieve this with FreeCAD.

It's for the special case where the curve is closed and has it's center of mass inside on a position where for any point its connecting line to the center is completly inside the “figure”.

These are the steps to proceed.

  1. Generate some randomized test data or get a point cloud from a use case
  2. Create a first set of approximations as Polygons
  3. Create Draft Bsplines for this first approximations
  4. Convert the Draft Bsplines to Sketcher Bsplines

All methods are stored in the nurbs workbench but can be used standalone.

Random Data Set

The dataset of 1000 points are points around the origin with mean distance rm and To have more irregularitiy we overlay all with two sine waves.

count=1000
ri=100
rm=400

# random ankle
kaps=np.random.random(count)*2*np.pi

#random radius
rads=np.random.random(count)*ri*np.cos(kaps*5)*np.cos(kaps*1.3) + rm

y= np.cos(kaps) * rads
x=np.sin(kaps) * rads
z=np.zeros(count)

pps=np.array([x,y,z]).swapaxes(0,1)
goods=[FreeCAD.Vector(tuple(p)) for p in pps]

Points.show(Points.Points(goods))

To run the script we import the nurbswb and the script and call its run function.

import nurbswb;
import nurbswb.gen_random_dat;
nurbswb.gen_random_dat.run()
First Approximation

There are different ways to get an approximation of a curve. The simplest is to create a circle. The center is the center of mass of the point cloud.

Then there are at least 3 main approches:

  • The outer circle is the “round version” of the BoundBox. All but the noise points should lie inside.
  • The inner circle is the maximum circle around the center without any point in it.

If we have the point cloid as a volume inner and outer circle can be used to make something like a tube or a worm.

  • A 3rd simple way to get a circle is the medium circle with the mean radius of the cloud.

More complex is to order the points in a useful way and to connect them to a polygon. Because there can be a local noise of points some local average processing is needed.

For a outer and inner polygon the extrema of the distances to the center can be used. The strategy is: Jump from one top to the next.

First the center of the cloud and the distances of all points from this center is calculated.

npts=np.array(pts).swapaxes(0,1)
mp=(npts[0].mean(),npts[1].mean(),npts[2].mean())

# center
vm=FreeCAD.Vector(mp)
# distances of all points
lengths=np.array([(FreeCAD.Vector(p)-vm).Length for p in pts])

Later the directions of each point viewd from center is used to order the points.Note that this is essential. The calcualted curve cannot change its direction forward and backward as when writing a S-curve. For such cases an other method is required.

for v in pts:
	vn=v-vm
	aps[np.arctan2(vn.x,vn.y)]=vn
	rads[np.arctan2(vn.x,vn.y)]=vn.Length

kaps=aps.keys()
kaps.sort()

#the poits ordered and its distances
ptss=[aps[k] for k in kaps]
radss=[rads[k] for k in kaps]

kaps is a list of all directions. If there are gaps in this list an extra interpolation step which fills these gaps makes sence.

If the median approximation is calculated the radia have to be smoothed. This is done with the scipy.signal.medfilt method. The size of the window is configurable.

l=np.array(radss)
f=medianfilterwindow
path=np.concatenate([[l[0]] * f,l,[l[-1]]*f])
l2 = sp.signal.medfilt(path,f)
mmaa=l2[f:-f]

For the outer curves a jumping sequence form one top to the next is needed. This is done with the scipy.signal.argrelextrema method.

if inner:
   z1=argrelextrema(radss, np.less)
else:
   z1=argrelextrema(radss, np.greater)

And at last the gaps are filled with numpy.interp.

So the method generates 3 circles and 3 wires: inner green, outer red and intermediate blue.

How to use:

import nurbswb;
import nurbswb.orderpoints;
nurbswb.orderpoints.run()
From Wire to Bspline

In the next step a Bspline is approximated. The Part.BSpline approximate method is the first step. But its result has too many poles for later editing by hand.

There is an extra reduce process: The curvature of the first approximated curve is computed and then only points where the curvature has extrem values are used as places for poles

bc=Part.BSplineCurve()
bc.approximate(pts,DegMax=3,Tolerance=1)
bc.setPeriodic()

Once again the scipy.signal.argrelextrema method is a good friend.

vps=np.array([bc.value(1.0/ct*i)+off for i in x])
y=np.array([sc.curvatureAt(1.0/ct*i) for i in x])

z=argrelextrema(y, np.greater)
z2=argrelextrema(y, np.less)

zc=np.concatenate([z,z2,[0,ct]])
zc.sort()
exps=vps[zc]

ps=[tuple(p) for p in exps]
Draft.makeBSpline(ps,closed=True,face=False)

There is a parameter how fine the starting curve is inspected.

How to use:

import nurbswb;
import nurbswb.simplecurve;
nurbswb.simplecurve.run()
Draft BSpline to Sketcher BSpline

From the Draft BSpline to the Sketcher BSpline its not a big step. Take the poles over and create the helper stuff.

sk.addGeometry(Part.BSplineCurve(l,True),False)

conList = []

for i,p in enumerate(pts):
   conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint',i,3,k,i))

sk.addConstraint(conList)

How to use:

import nurbswb;
import nurbswb.createsketchspline;
nurbswb.createsketchspline.run()

Configruation of the methods
2017/03/12 11:54
Mesh-Cutter

Möchte man sich nur Teile eines Mesh anschauen, kann man sich auf diese Weise sein Modell zusammenschneiden.

  1. Mesh importieren z. B aus einer obj- oder stl-Datei
  2. Quader erzeugen und platzieren, sodass der gewünschte Mesh-Teil im Innern des Quaders liegt
  3. Erst den Mesh und dann als zweites den Quader selektieren
  4. Makro ausführen

Es entsteht ein Teilmesh im Innern des Quaders

https://github.com/microelly2/freeCAD_macro/blob/master/misc/commonmesh.py

2017/02/20 18:03
Schuh-Design

Wie erstellt man eine Schuhsohle?

Den Fuß näherungsweise als Streckenzug zeichnen, aus den Punkten eine schöne Kurve gestalten, die anschließend verkleinert unter den Fuß und vergrößert außen am Fuß nach oben gezogen wird.

Die drei Hilfskurven werden mit Part::Offset2D erzeugt und als BSpline zu einer schönen abgerundeten Form zusammengefasst.

2017/02/14 15:31
Wie kommen die Löcher in den Käse

Für große Datenmengen kan man zunächst eine Punktwolke erzeugen und daraus dann die Hüllflächen für einen Mesh generieren.

Im Beispiel werden in einem 1 m großen Würfel die Hüllen von 5000 Kugeln erzeugt. Bei einer Näherung auf 1 mmm Genauigkeit sind das 40 Millionen Punkte. Die Idee besteht darin, schichtenweise mittels der Bildbearbeitungsmethoden von OpenCV Schnitte zu erzeugen und diese Schichten dann im Raum anzuordnen. Die Bildbearbeitung kann schnell Kreise zeichnen und Hüllkurven finden.

Die beiden grundlegenden Nethoden sind

cv2.circle(img,(x,y),r, 255, -1)
edges = cv2.Canny(img,100,200)

Die Punkte liegen genau auf dem 1 mm Raster und können deshalb einfach zu Flächenteilen umgerechnet werden. Die für QuadMesh oder Nurbs notwendige Struktur ist schon vorbereitet.

Der Skript zum Erzeugen der Blasen

2017/02/06 18:17
start.txt · Last modified: 2018/09/04 14:21 by admin