BSplineSurface how to script correctly in a solid?

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

BSplineSurface how to script correctly in a solid?

Post by onekk »

Hello, I'm struggling with BsplineSurfaces, I'm trying to model a parametric thing:

I start with a Bspline, obtained approximating a cruve present in a FreeCAD file, so to use for scripting I have no other way to manually discretize and copy an paste the "point list" obtained from the code I manually input in the FreeCAD console:

Code: Select all

import FreeCAD
from FreeCAD import Base, Rotation, Vector
import Part
import Draft
import DraftVecUtils
 
from draftgeoutils.edges import findMidpoint
from draftgeoutils.intersections import findIntersection
from math import degrees

DOC_NAME = "test"
DOC = FreeCAD.activeDocument()

# CMPN_NAME = "Components"
# DOC.addObject("App::DocumentObjectGroup",CMPN_NAME)

def clear_doc():
    """
    Clear the active document deleting all the objects
    """
    for obj in DOC.Objects:
       DOC.removeObject(obj.Name)

if DOC is None:
    FreeCAD.newDocument(DOC_NAME)
    FreeCAD.setActiveDocument(DOC_NAME)
    DOC = FreeCAD.activeDocument()
    VIEW = FreeCAD.Gui.ActiveDocument.ActiveView
else:
    clear_doc()
    VIEW = FreeCAD.Gui.ActiveDocument.ActiveView

def setview():
    """Rearrange View"""
    DOC.recompute()
    VIEW.viewAxometric()
    VIEW.setAxisCross(True)
    VIEW.fitAll()

def bsp2arcs(spline, prec):
    edges = []
    arcs = spline.toBiArcs(prec)
    for i in arcs:
        edges.append(Part.Edge(i))
    
    return Part.Wire(edges)    


def bilinear_surface(pts):
    """Create a degree 1 bspline surface from array(2D) for points
        
        code made by chrisb on FreeCAD forum
    """
    ny = len(pts)
    nx = len(pts[0])
    umults = [1]*ny
    umults[0] = 2
    umults[-1] = 2
    vmults = [1]*nx
    vmults[0] = 2
    vmults[-1] = 2
    uknots = [v for v in range(ny)]
    vknots = [v for v in range(nx)]
    bs = Part.BSplineSurface()
    # 1 x 1 are the last numbers
    bs.buildFromPolesMultsKnots(array, umults, vmults, uknots, vknots, False, False, 1, 1)
    return bs

def find_intersection(wire, line):
    pts = []
    for edge in wire.Edges:
        f_pts = findIntersection(line, edge)
       
        if len(f_pts) > 1:
            pts.extend(f_pts)
        else:
            if len(f_pts) != 0:
                pts.append(f_pts[0])
    
    return pts
    
# some vars
ROT0 = Rotation(0,0,0)
EPS = 1e-5


cm_pts = (
    (-56.64199999999988, -108.52651511673002, 0.0),
    (5.045714702451431, -89.33237066431363, 0.0),
    (68.53266394469387, -77.37432303177049, 0.0),
    (132.97648568219708, -72.83764291497576, 0.0),
    (197.51261011152948, -75.78319379570682, 0.0),
    (260.7847439701756, -88.14580249881409, 0.0),
    (323.6040807225694, -79.3788987372448, 0.0), 
    (367.2823694583445, -33.3861228817963, 0.0),
    (372.79691119601375, 29.801837430421966, 0.0),
    (337.74602605414333, 82.66546820752089, 0.0),
    (277.3950206667093, 102.18170352239254, 0.0),
    (214.92488372007435, 86.599090089876, 0.0),
    (150.92898644445887, 78.65194260507964, 0.0),
    (86.93383045440443, 86.30428420472582, 0.0),
    (32.35737605886684, 119.93166381983781, 0.0), 
    (-29.46640748383976, 133.54417536624192, 0.0),
    (-93.13416360635159, 129.88433443137143, 0.0),
    (-153.07012316309192, 153.86273378994565, 0.0),
    (-216.6511630988794, 164.47375642110174, 0.0),
    (-280.4777089025564, 156.68787584496482, 0.0),
    (-336.6023312195406, 125.4766614340001, 0.0),
    (-376.3754965874899, 75.05634269866019, 0.0),
    (-393.6601152739239, 13.206886332309258, 0.0), 
    (-385.78913055704123, -50.52819728232701, 0.0),
    (-353.9770547168756, -106.31444138277075, 0.0),
    (-303.132563410529, -145.54390506584397, 0.0),
    (-241.10107460845313, -162.1633961111713, 0.0),
    (-177.15670396003594, -154.8026278244786, 0.0),
    (-116.09033910107355, -133.8670902129459, 0.0),
    (-56.641999999999854, -108.52651511673002, 0.0)
)

a_tol = 0.002

# interpolation points of the recostructed bspline (y dir)
dis_fac= 25

cus_incr = 40
incr = 18

f_incr = cus_incr * 0.25

off_dim = 1.0

c_off = 0

c_height = 41
bsf = (0.05, 0.22)
# this is holding the height as factor of c_height in the "central section"
# same progression is done for start and end section, but c_height is calculated
# using a formula that increase from start and decrease to end of surface
bsh = (0.45, 0.90, 1, 0.90, 0.45) 

# cut h must be more high than bsh[2] *c_height as the surface is extruded in negative z-height

cut_h = c_height * 1.25

# body height the heigh of the base, using base_spline
body_h = 1.5 * 25.4

newpoints = []

for point in cm_pts:
    newpoints.append(Vector(*point))

base_spline = Part.BSplineCurve()
base_spline.buildFromPoles(newpoints)


Part.show(base_spline.toShape(), "spline")

base_curve = bsp2arcs(base_spline, a_tol)

Part.show(base_curve, "curve")

DOC.recompute()

off_curve = base_curve.makeOffset2D(off_dim)

DOC.recompute()

# calculations

# select the offset spline

sp_dims = off_curve.BoundBox

xmax = sp_dims.XMax
xmin = sp_dims.XMin
ymin = sp_dims.YMin
ymax = sp_dims.YMax

lax = sp_dims.XLength * 1.1
lay = sp_dims.YLength * 1.1



# Create  lines to find intersection points:
lines = []

idx = 0
xpos = xmin
while xpos < (xmin + cus_incr):
    line = Part.LineSegment(Vector(xpos, ymin - incr), Vector(xpos, ymax + incr, 0))
    lines.append(line)
    xpos += f_incr
    idx +=1

idx1 = idx - 1

xpos = xmin + cus_incr + 0.05

while xpos < xmax - cus_incr:
    line = Part.LineSegment(Vector(xpos, ymin - incr), Vector(xpos, ymax + incr, 0))
    lines.append(line)
    last_xpos = xpos
    xpos += incr
    idx +=1

idx2 = idx
xpos = last_xpos + 0.05

while xpos < xmax:
    line = Part.LineSegment(Vector(xpos, ymin - incr), Vector(xpos, ymax + incr, 0))
    lines.append(line)
    xpos += f_incr
    idx +=1

idx_max = idx - 1

#print("number of lines", len(lines))

yint = []

idx = 0
for line in lines:
    pts = find_intersection(off_curve, line.toShape())
    yint.append((idx, pts))
    idx +=1

#print("yint = ", yint)


splines = []

idx = 0
for apts in yint:
    poles = []
    print(apts)
    if len(apts[1]) == 2:
        pt_s = apts[1][0]
        pt_e = apts[1][1]
        
        if pt_s.y > pt_e.y:
            pta = pt_s
            ptb = pt_e
        else:
            pta = pt_e
            ptb = pt_s
        poles.append(pta)
        pt1 = Vector.add(pta, (ptb.sub(pta)).multiply(bsf[0]))
        pt2 = Vector.add(pta, (ptb.sub(pta)).multiply(bsf[1]))
        pt3 = Vector.add(pta, (ptb.sub(pta)).multiply(1 - bsf[1]))
        pt4 = Vector.add(pta, (ptb.sub(pta)).multiply(1 - bsf[0]))
        if apts[0] < idx1:
            mul_f = c_height / idx1
            fact = [mul_f * idx * x for x in bsh]
        elif apts[0] >= idx1 and apts[0] <= idx2:
            fact = [c_height * x for x in bsh]
        else:
            mul_f = c_height / (idx_max - idx2)
            el_h = c_height - (idx - idx2) * mul_f
            fact = [el_h * x for x in bsh]
        poles.extend(
            [Vector(pt1.x, pt1.y, fact[0]),
             Vector(pt2.x, pt2.y, fact[1]),
             Vector(pt2.x, c_off, fact[2]),
             Vector(pt3.x, pt3.y, fact[3]),
             Vector(pt3.x, pt3.y, fact[4]),
             ptb
            ])
        skspline = Part.BSplineCurve()
        skspline.buildFromPoles(poles)
        
        skcurve = bsp2arcs(skspline, a_tol)
        vrtxs = skcurve.OrderedVertexes  
        stpt = Part.LineSegment(vrtxs[-1].Point, vrtxs[0].Point)
        edges = skcurve.OrderedEdges
        edges.append(stpt.toShape())
        new_wire = Part.Wire(edges)      
        profile = DOC.addObject('Part::Feature','Profile_{0:03d}'.format(idx))
        profile.Shape = new_wire
        
        splines.append(skcurve)   
        idx +=1

end_idx = idx

array = []

for spl in splines:
    pts = spl.discretize(dis_fac)
    array.append(pts)

surf = bilinear_surface(array)

int_shape = surf.toShape()

Part.show(int_shape, "surf")


path = DOC.addObject('Part::Feature','Length')
path.Shape = Part.Wire(Part.LineSegment(Vector(xmin, 0, 5), Vector(xmax, 0,5)).toShape())

DOC.recompute()

setview()

I have done some work, but there are some quirks.

I want to obtain a solid, but I can't find a way to correctly close the solid:

1) the faces of the surface seems to be created in the "wrong way" with the surface internal to the solid, the gray color is in the "bottom" side of the created surface.

2) I can't close the solid as the "base face" is not matching the "curve" (that is the bspline transfomed in biarcs)

3) i cant to model the edges, the two parts at the begging and at the end of the shape, that has to be a cruved surface between the portion of the "generating curve" and the first an last spline of the approximating "vertical" splines.

TIA and Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by onekk »

Chris_G wrote: Mon Mar 01, 2021 10:37 am ...
So, feel free to ask in the Python Scripting forum when you need help.
I see that your "Curves wb" will help me to solve my problem.

There is way to use scripting?

I could generate the pointlist:
cb_pointlist.png
cb_pointlist.png (6.83 KiB) Viewed 1970 times
and maybe use it to generates a gordon surface?
or something different?

Any hints?


TIA and Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by Chris_G »

onekk wrote: Tue Jul 20, 2021 4:13 pm I could generate the pointlist:
...
Hello,
If you have a 2D array of points, you can approximate it with a BSpline surface, like here :
https://github.com/tomate44/CurvesWB/bl ... te.py#L186
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by onekk »

Chris_G wrote: Wed Jul 21, 2021 12:12 pm
onekk wrote: Tue Jul 20, 2021 4:13 pm I could generate the pointlist:
...
Hello,
If you have a 2D array of points, you can approximate it with a BSpline surface, like here :
https://github.com/tomate44/CurvesWB/bl ... te.py#L186
Many thanks, for the reply, i have some problems, to make the "aproximate" code to work

EDIT: PROBLEM SOLVED
2D array of points, means a [[],[], []] so I have put the array directly as pts and the surface is generated.

But the surface is generated upside down, the "grey" surface is in the "internal" side of the "would be solid"
Last edited by onekk on Wed Jul 21, 2021 3:21 pm, edited 1 time in total.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by onekk »

See the images:

from top:
curved1.png
curved1.png (5.29 KiB) Viewed 1896 times
from bottom:
curved2.png
curved2.png (51.22 KiB) Viewed 1896 times
Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by Chris_G »

Maybe you can try reversing the point list :

Code: Select all

pts.reverse()
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by onekk »

Thanks, now the surface is correct:
curved3.png
curved3.png (43.68 KiB) Viewed 1861 times

But there are some ondulations in the intepolation, see the side view:
curved4.png
curved4.png (22.08 KiB) Viewed 1861 times

I have used these parameters:

Code: Select all

bs = Part.BSplineSurface()
bsdmi = 3 # DegMin
bsdma = 5 # DegMax
bslw = 0.01 # LengthWeight
bscw = 0.5 # CurvatureWeight
bstw = 0.01 # TorsionWeight
with C2 continuity

Any suggestion?

TIA and Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by onekk »

An image of the pointlist:
curved5.png
curved5.png (5.53 KiB) Viewed 1859 times
the continuos curve is the generating curve.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6222
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by onekk »

Code: Select all

import FreeCAD
from FreeCAD import Base, Rotation, Vector
import Part
import Draft
import DraftVecUtils
 
from draftgeoutils.edges import findMidpoint
from draftgeoutils.intersections import findIntersection
from math import degrees

DOC_NAME = "test"
DOC = FreeCAD.activeDocument()

# CMPN_NAME = "Components"
# DOC.addObject("App::DocumentObjectGroup",CMPN_NAME)

def clear_doc():
    """
    Clear the active document deleting all the objects
    """
    for obj in DOC.Objects:
       DOC.removeObject(obj.Name)

if DOC is None:
    FreeCAD.newDocument(DOC_NAME)
    FreeCAD.setActiveDocument(DOC_NAME)
    DOC = FreeCAD.activeDocument()
    VIEW = FreeCAD.Gui.ActiveDocument.ActiveView
else:
    clear_doc()
    VIEW = FreeCAD.Gui.ActiveDocument.ActiveView

def setview():
    """Rearrange View"""
    DOC.recompute()
    VIEW.viewAxometric()
    VIEW.setAxisCross(True)
    VIEW.fitAll()

def bsp2arcs(spline, prec):
    edges = []
    arcs = spline.toBiArcs(prec)
    for i in arcs:
        edges.append(Part.Edge(i))
    
    return Part.Wire(edges)    

def find_intersection(wire, line):
    pts = []
    for edge in wire.Edges:
        f_pts = findIntersection(line, edge)
       
        if len(f_pts) > 1:
            pts.extend(f_pts)
        else:
            if len(f_pts) != 0:
                pts.append(f_pts[0])
    
    return pts
 
def get_wire(edges, e1, e2):
    _tmp = edges + [e1]
    se = Part.sortEdges(_tmp)
    if len(se) == 1:  # e1 is indeed touching previous edges
        w1 = Part.Wire(se[0])
        return w1, e2
    else:  # e1 is not touching previous edges, let's try with e2
        se = Part.sortEdges(edges + [e2])
        if len(se) == 1:  # all edges connected
            w1 = Part.Wire(se[0])
            return w1, e1
    return None, None

def split_wire(wire, line, tol=1e-7):
    wires = []
    edges = []
    for i, oe in enumerate(wire.OrderedEdges):
        d, pts, info = oe.distToShape(line)
        if d > tol:
            edges.append(oe)
        else:
            for data in info:
                if data[0] == "Edge":
                    e1, e2 = oe.split(data[2]).Edges
                    w, e = get_wire(edges, e1, e2)
                    if w:
                        wires.append(w)
                        edges = [e]
                elif data[0] == "Vertex":
                    print("Wire is cut exactly on a vertex")
                    print("We should do something also, but I'm getting lazy ;-)")
    if edges:
        wires.append(Part.Wire(Part.sortEdges(edges)[0]))
    if wire.isClosed():
        join = Part.Wire(Part.sortEdges(wires[0].Edges + wires[-1].Edges)[0])
        wires = [join] + wires[1:-1]
    return wires       

   
# some vars
ROT0 = Rotation(0,0,0)
EPS = 1e-5


cm_pts = (
    (-56.64199999999988, -108.52651511673002, 0.0),
    (5.045714702451431, -89.33237066431363, 0.0),
    (68.53266394469387, -77.37432303177049, 0.0),
    (132.97648568219708, -72.83764291497576, 0.0),
    (197.51261011152948, -75.78319379570682, 0.0),
    (260.7847439701756, -88.14580249881409, 0.0),
    (323.6040807225694, -79.3788987372448, 0.0), 
    (367.2823694583445, -33.3861228817963, 0.0),
    (372.79691119601375, 29.801837430421966, 0.0),
    (337.74602605414333, 82.66546820752089, 0.0),
    (277.3950206667093, 102.18170352239254, 0.0),
    (214.92488372007435, 86.599090089876, 0.0),
    (150.92898644445887, 78.65194260507964, 0.0),
    (86.93383045440443, 86.30428420472582, 0.0),
    (32.35737605886684, 119.93166381983781, 0.0), 
    (-29.46640748383976, 133.54417536624192, 0.0),
    (-93.13416360635159, 129.88433443137143, 0.0),
    (-153.07012316309192, 153.86273378994565, 0.0),
    (-216.6511630988794, 164.47375642110174, 0.0),
    (-280.4777089025564, 156.68787584496482, 0.0),
    (-336.6023312195406, 125.4766614340001, 0.0),
    (-376.3754965874899, 75.05634269866019, 0.0),
    (-393.6601152739239, 13.206886332309258, 0.0), 
    (-385.78913055704123, -50.52819728232701, 0.0),
    (-353.9770547168756, -106.31444138277075, 0.0),
    (-303.132563410529, -145.54390506584397, 0.0),
    (-241.10107460845313, -162.1633961111713, 0.0),
    (-177.15670396003594, -154.8026278244786, 0.0),
    (-116.09033910107355, -133.8670902129459, 0.0),
    (-56.641999999999854, -108.52651511673002, 0.0)
)

a_tol = 0.002

# interpolation points of the recostructed bspline (y dir)
dis_fac= 15

# inital and final length for increasing and decreasing 
sta_len = 40
end_len = 40
lim_mod = 3
# increment used after final and before initial length
incr = 80
# increment used in starting and final length
sta_inc = sta_len / lim_mod
end_inc = end_len / lim_mod
# offset dimension of the interpolated curve 
# intended to compensate for difference of curvature between generating curve
# and final interpolated surface  
off_dim = 1.0
# center offset
c_off = 0
# maximun height at center 
c_height = 41
# positions of the points, from start to center,  
bsf = (0.05, 0.22)
# height as factor of c_height in the "central section"
# same progression is done for start and end section, but c_height is calculated
# using a formula that increase from start and decrease to end of surface
# bsf is used to position the point along the lenght of "base line"
bsh = (0.45, 0.90, 1, 0.90, 0.45) 

# cut h must be more high than bsh[1] * c_height as the surface is extruded in negative z-height

cut_h = c_height * 1.25

# body height the heigh of the base
# using base_spline () this is to be changed as the base spline
# is recalculated based on calulcated final surface
body_h = 1.5 * 25.4

debug_line = False
debug_ips = False
debug_rib = False
debug_pts = False

newpoints = []

for point in cm_pts:
    newpoints.append(Vector(*point))

base_spline = Part.BSplineCurve()
base_spline.buildFromPoles(newpoints)

#Part.show(base_spline.toShape(), "spline")

base_curve = bsp2arcs(base_spline, a_tol)

#Part.show(base_curve, "curve")

DOC.recompute()

off_curve = base_curve.makeOffset2D(off_dim)

#gen_curve = base_spline.toShape() # strange result
#gen_curve = base_curve # creation error
gen_curve = off_curve

Part.show(gen_curve, "curve")

DOC.recompute()

# calculations

sp_dims = gen_curve.BoundBox

xmax = sp_dims.XMax
xmin = sp_dims.XMin
ymin = sp_dims.YMin
ymax = sp_dims.YMax

lax = sp_dims.XLength * 1.1
lay = sp_dims.YLength * 1.1

c_f = lay / 11.0

# Create  lines to find intersection points:
lines = []

idx = 0
xpos = xmin
while xpos < (xmin + sta_len):
    line = Part.LineSegment(Vector(xpos, ymin - c_f), Vector(xpos, ymax + c_f, 0))
    lines.append(line)
    if debug_line == True:
        Part.show(line.toShape(), "sta_line_{0:04d}".format(idx))
    xpos += sta_inc
    idx +=1

idx1 = idx - 1

xpos = xmin + sta_len + 0.05

while xpos < (xmax - end_len):
    line = Part.LineSegment(Vector(xpos, ymin - c_f), Vector(xpos, ymax + c_f, 0))
    lines.append(line)
    if debug_line == True:
        Part.show(line.toShape(), "int_line_{0:04d}".format(idx))
    xpos += incr
    idx +=1

idx2 = idx
xpos = xmax - end_len + 0.05

while xpos < xmax:
    line = Part.LineSegment(Vector(xpos, ymin - c_f), Vector(xpos, ymax + c_f, 0))
    lines.append(line)
    if debug_line == True:
        Part.show(line.toShape(), "end_line_{0:04d}".format(idx))
    xpos += end_inc
    idx +=1

idx_max = idx # - 1

#print("number of lines", len(lines))

yint = []

idx = 0
for line in lines:
    pts = find_intersection(gen_curve, line.toShape())
    yint.append((idx, pts))
    idx +=1

#print("yint = ", yint)

splines = []

idx = 0
for apts in yint:
    poles = []
    #print(apts)
    if len(apts[1]) == 2:
        pt_s = apts[1][0]
        pt_e = apts[1][1]
        if debug_ips == True:
            Part.show(Part.Vertex(pt_s), "isp_ln_{0:04d}".format(apts[0]))
            Part.show(Part.Vertex(pt_e), "iep_ln_{0:04d}".format(apts[0]))
        if pt_s.y > pt_e.y:
            pta = pt_s
            ptb = pt_e
        else:
            pta = pt_e
            ptb = pt_s
        poles.append(pta)
        pt1 = Vector.add(pta, (ptb.sub(pta)).multiply(bsf[0]))
        pt2 = Vector.add(pta, (ptb.sub(pta)).multiply(bsf[1]))
        pt3 = Vector.add(pta, (ptb.sub(pta)).multiply(1 - bsf[1]))
        pt4 = Vector.add(pta, (ptb.sub(pta)).multiply(1 - bsf[0]))
        if apts[0] < idx1:
            mul_f = c_height / idx1
            fact = [mul_f * idx * x for x in bsh]
        elif apts[0] >= idx1 and apts[0] <= idx2:
            fact = [c_height * x for x in bsh]
        else:
            mul_f = c_height / (idx_max - idx2)
            el_h = c_height - (idx - idx2) * mul_f
            fact = [el_h * x for x in bsh]
        poles.extend(
            [Vector(pt1.x, pt1.y, fact[0]),
             Vector(pt2.x, pt2.y, fact[1]),
             Vector(pt2.x, c_off, fact[2]),
             Vector(pt3.x, pt3.y, fact[3]),
             Vector(pt3.x, pt3.y, fact[4]),
             ptb
            ])
        skspline = Part.BSplineCurve()
        skspline.buildFromPoles(poles)

        skcurve = bsp2arcs(skspline, a_tol)

        if debug_rib == True:
            Part.show(skcurve, "rib_{0:04d}".format(idx))
        
        splines.append(skcurve)
        idx +=1

end_idx = idx

# add the final segment to make the figure closed:

cut_line = lines[-1].toShape()

Part.show(cut_line, "cut_line")

result = split_wire(gen_curve, cut_line)

array = []

for spl in splines:
    pts = spl.discretize(dis_fac)
    array.append(pts)

# add the last segment as discretization of the gen_curve section

end_wire = result[1]
Part.show(end_wire, "end wire")

array.append(end_wire.discretize(dis_fac))

if debug_pts == True:
    l_idx = 0
    p_idx = 0
    for line in array:
        for point in line:
            vtx = Part.Vertex(point)
            Part.show(vtx, "Point_{0:04d}_{1:04d}".format(l_idx, p_idx))
            p_idx += 1
        l_idx += 1

bs = Part.BSplineSurface()
bsdmi = 1 # DegMin
bsdma = 5 # DegMax
bslw = 0.01 # LengthWeight
bscw = 0.5 # CurvatureWeight
bstw = 0.01 # TorsionWeight

array.reverse()
# Continuity: 0 : C0, 1 : C1, 2 : C2
bs.approximate(Points=array, DegMin=bsdmi, DegMax=bsdma, Tolerance=a_tol, Continuity=2,
    LengthWeight=bslw, CurvatureWeight=bscw, TorsionWeight=bstw)

Part.show(bs.toShape(), "surface")

DOC.recompute()

setview()
Now I have two problems:

First: how to avoid the ondulations: maybe setting a tangent on top , but how:
curved-curv.png
curved-curv.png (33.08 KiB) Viewed 1779 times

Adding the last portion of the base curve discretized as last points on the "BSpline surface generation pointlist" it seems that there is a curvature inward at the bottom.
curved-curv2.png
curved-curv2.png (37.75 KiB) Viewed 1779 times
I want to obtain something similar to the image in:

https://forum.freecadweb.org/viewtopic. ... 78#p203978
Chris_G wrote: Tue Dec 12, 2017 2:43 pm - Finally, I can build a surface with the boundary curves and the profile sketches as "unbound edge" constraint curves.
This surface is fully parametric and can be stretched and modified by editing the sketches.
but using scripting.

The technique used in the post did not adapt to my model as the base curve is not symmetric so how to smooth the curve.

Maybe the rough assumption I've made to inteporlate the shape using a succession of bsplinesurface generated in a parametric way is not correct.

Any Hints?

TIA and Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
Chris_G
Veteran
Posts: 2601
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: BSplineSurface how to script correctly in a solid?

Post by Chris_G »

The problem of you surface project is that a BSpline surface is mathematically a 4-sided surface.
But your boundary curve is a smooth ellipse-like curve.
So there are different options :
1 - cut the boundary curve into 2 halves, and use them as the 2 rails of a Sweep on 2 rails. This means that the profiles degenerate to a point at each end.
2 - cut the boundary curve into 4 pieces, and use them as the 2 rails and the 2 end profiles of a Sweep on 2 rails.
3 - Create a BSpline surface that extends beyond the boundary curve, and passes through the boundary curve and the profiles, then trim it.

1 and 2 will most probably lead to problems (degenerated curves (1), or rails and profiles that are tangent at their contact point (2))
3 is the solution used in the Surface WB Filling tool. And it is mathematically more robust, but more complex to work with.

The Surface WB Filling tool has no python binding.
But there is the Part.GeomPlate that does the same (Part.GeomPlate is a geometry tool, that works on curves and surfaces, while Surface.Filling is a topology tool that works on edges and faces)

I have not worked much with Part.GeomPlate, but here is a rough guide :
- create some curve constraints (either boundary, or inner curves) :

Code: Select all

cc = Part.GeomPlate.CurveConstraint(my_curve)
- create some point constraints if you need :

Code: Select all

pc = Part.GeomPlate.PointConstraint(my_point)
- create a PlateSurface builder :

Code: Select all

builder = Part.GeomPlate.BuildPlateSurface()
- add your constraints to the builder :

Code: Select all

builder.add(cc)
builder.add(pc)
- compute and get the PlateSurface object :

Code: Select all

builder.perform()
ps = builder.surface()
- you now have a PlateSurface object, that you need to approximate to a BSpline surface :

Code: Select all

bspline = ps.makeApprox()
You may need to have a look at the source code for in-depth documentation :

https://github.com/FreeCAD/FreeCAD/blob ... mp.cpp#L49
https://github.com/FreeCAD/FreeCAD/blob ... mp.cpp#L77
https://github.com/FreeCAD/FreeCAD/blob ... mp.cpp#L58
https://github.com/FreeCAD/FreeCAD/blob ... p.cpp#L140
Post Reply