A developable surface is one that can be flattened without any stretching or compression. It is one that be created by suitably bending a flat sheet. The two common examples are the curved surfaces of cones and cylinders. The most general developable surface is a union of portions of generalized cylinders and generalized cones.
A generalized cylinder is the extrusion of a space curve in a fixed direction. For a standard cylinder, the curve is a circle (or circular arc).
Similarly, a generalized cone is a space curve lofted to a point with a ruled surface.
All developable surfaces are ruled surfaces - but not all ruled surfaces are developable. The simplest counter-example is the ruled surface loft between two non-parallel line segments.
In viewtopic.php?p=178312#p178312 @Chris_G showed how to flatten a generalized cylinder.
His idea was to re-parameterize the surface with u distance around the cross-section and v distance along axial direction. Mapping the edges onto a plane unrolls the surface.
For a generalized cone, I re-parameterized the surface in polar coordinates. u being angle and v radius. Then the flattened surface is plotted on the XY-plane by converting Polar to Cartesian. I think my code can be simplified - but for now, here it is:
Code: Select all
#start with edge and vertex
#create loft to point from edge to vertex
#intersect it with sphere from vertex
#reparameterize with length, then angle as u coordinate
#create an expanded loft to point, overlapping edge
#reparametrize v to length
#find u, v coords of edge on the surface and draw flattened triangle in 2d.
from math import sin, cos
debug = False
doc = App.ActiveDocument()
#input is an edge an a vertex
def flattenGeneralizedCone(edge, vertex):
#edge = doc.getObject('Sketch').Shape.Edge1
#vertex = doc.getObject('Vertex').Shape.Vertex1
if edge.Curve.TypeId == 'Part::GeomBSplineCurve':
skspline = edge.Curve
else:
skspline = edge.Curve.toBSpline(*edge.ParameterRange)
vskspline = skspline.copy()
for i, pole in enumerate(vskspline.getPoles()):
vskspline.setPole(i+1, vertex.Point) #note 1-origin for pole list
#create regular ruled surface from skspline to vertex
sksurf = Part.BSplineSurface()
sksurf.buildFromNSections([vskspline, skspline])
if debug: Part.show(sksurf.toShape(), 'sksurf')
#we are going to make a new surface that includes the sketch
radii = [(v - vertex.Point).Length for v in skspline.discretize(20)]
minrad = min(radii)
maxrad = max(radii)
radius = minrad/10
center = vertex.Point
sph = Part.makeSphere(radius, center)
sphsurf = sph.Face1.Surface
sphcurve = sphsurf.intersect(sksurf)[0]# this curve is constant radius from vertex on sksurf
#create a version of sphcurve parameterized by length along the curve
npts = 20
dis = sphcurve.length()/npts
pts = sphcurve.discretize(Distance = dis)
params = [i * dis for i in range(len(pts))]
sphcurvei = Part.BSplineCurve()
sphcurvei.interpolate(Points = pts, Parameters = params)
#now we create a surface coincident with the loft with polar coordinates u =r v=theta
vsphcurvei = sphcurvei.copy()
for i, pole in enumerate(sphcurvei.getPoles()):
vsphcurvei.setPole(i+1, vertex.Point)
osphcurvei = sphcurvei.copy()
for i, pole in enumerate(sphcurvei.getPoles()):
v = vertex.Point + (pole - vertex.Point) * (maxrad/minrad) *20
osphcurvei.setPole(i+1, v)
newsurf = Part.BSplineSurface()
newsurf.buildFromNSections([vsphcurvei, osphcurvei])
newsurfshp = newsurf.toShape()
newsurf.scaleKnotsToBounds(0, newsurfshp.Edge4.Length/newsurfshp.Edge1.Length, 0, newsurfshp.Edge1.Length) #polar coords u is theta v is r
newsurfshp = newsurf.toShape()
if debug:
newsurfdoc = Part.show(newsurfshp, 'newsurf')
newsurfdoc.ViewObject.Visibility = False
#map skspline to the u,v plane
skpts = skspline.discretize(Distance = 0.1)
skpcoords = [newsurf.parameter(p) for p in skpts]
skccoords = [App.Vector(uv[1]*cos(uv[0]), uv[1] * sin(uv[0]), 0.) for uv in skpcoords]
flatbs = Part.BSplineCurve()
flatbs.interpolate(skccoords)
#close up in the uv plane
l1 = Part.LineSegment(App.Vector(), flatbs.StartPoint).toShape()
l2 = Part.LineSegment(flatbs.EndPoint, App.Vector()).toShape()
wire = Part.Wire([l1, flatbs.toShape(), l2])
Part.show(Part.Face(wire), "Flattened Face")
#fit a 2d spline to the uv figure
bsonuv = Part.Geom2d.BSplineCurve2d()
bsonuv.interpolate([App.Base.Vector2d(uv[0], uv[1]) for uv in skpcoords])
edgeonnewsurf = bsonuv.toShape(newsurf, bsonuv.FirstParameter, bsonuv.LastParameter)
if debug: Part.show(edgeonnewsurf, "edgeonnewsurf")
l3 = Part.Geom2d.Line2dSegment(App.Base.Vector2d(skpcoords[0][0],0), App.Base.Vector2d(*skpcoords[0]))
l4 = Part.Geom2d.Line2dSegment(App.Base.Vector2d(*skpcoords[-1]), App.Base.Vector2d(0,0))
l3s = l3.toShape(newsurf, l3.FirstParameter, l3.LastParameter)
l4s = l4.toShape(newsurf, l4.FirstParameter, l4.LastParameter)
wireonface = Part.Wire(Part.__sortEdges__([l3s, l4s,edgeonnewsurf]))
if debug: Part.show(wireonface, 'WireOnFace')
#map back onto newsurf
#newsurf is coincident with the original loft, but has polar coordinate parameters.
ltp = Part.Face(newsurf, wireonface)
ltp.validate()
Part.show(ltp, "LoftToPoint")
edgecount = 0
vertexcount = 0
for sel in Gui.Selection.getSelectionEx('',0):
for path in sel.SubElementNames if sel.SubElementNames else ['']:
obj = sel.Object.getSubObject(path)
if obj.ShapeType == 'Edge':
edge = obj
edgecount += 1
elif obj.ShapeType == 'Vertex':
vertex = obj
vertexcount += 1
if edgecount != 1 or vertexcount != 1 or len(edge.Vertexes) != 2:
App.Console.PrintMessage("select one open edge and one vertex\n")
else:
flattenGeneralizedCone(edge, vertex)
With this one has a necessary ingredient to triangulate the loft between two arbitrary space -curves, making a developable surface.
However, the flat pieces would need to be glued together, and some automated way of choosing the triangulation would be needed. I have some ideas...
To use this in order to add capability to Curves|Flatten Face we need a simple way of identifying surfaces as generalized cones or cylinders. Sampling the Gaussian Curvature is one way, but I think given a ruled surface, a 1d sampling along an edge may suffice. The intersection of two lines a principal curvature direction could find the vertex.