Trouble creating a flat net of a polyhedron

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
wangnick
Posts: 19
Joined: Thu Nov 03, 2022 9:27 am

Trouble creating a flat net of a polyhedron

Post by wangnick »

Dear all,

as part of my quest to use FreeCAD in a design project of mine I now need to create a flat net of a Goldberg polyhedron. In order to do so I labelled all the faces of the polyhedron in a reasonable manner, and then devised a scheme for the remaining face connectivity, and a recursive function to map all those faces onto the XY plane.

But I'm having trouble with this mapping. See the following screenshot:
goldbergnet.png
goldbergnet.png (268.38 KiB) Viewed 859 times
The idea is that the (planar) Face133 is mapped onto the XY plane such that the selected edge towards Face136 gets located on the Y axis with the vertex v1 closest to the "700" text ending up at (0,0,0).

So the first thing I do is translate all points of Face133 by the vector -v1. So far so good, and you can see the result in the screenshot slightly off the YZ plane.

In a next step I intended to rotate the face around an axis by an angle such that the second vertex v2 of the selected edge gets located on the Y axis.

Code: Select all

def toxy(pp1,pp2,pxy1,pxy2,p1,p2):
    # The previous edge pp1->pp2 was mapped onto pxy1->pxy2 (with pxy1.z and pxy2.z being 0). 
    # Map the edge p1->p2 accordingly by translation and rotation.
    # 1. Translate p1 and p2 by the vector that positions pp1 onto pxy1
    xy1 = p1+(pxy1-pp1)
    xy2 = p2+(pxy1-pp1)
    # 2. Rotate p1 and p2 around the axis through pp1 and perpendicular to the plane pp1->pp2 and pxy1->pxy2
    # by the angle between pp1->pp2 and pxy1->pxy2, i.e. perform the rotation that keeps pp1 at pxy1 and maps pp2 onto pxy2
    rot = App.Rotation(pp2-pp1,pxy2-pxy1)
    #angle = (pp2-pp1).getAngle(pxy2-pxy1)
    #axis = (pp2-pp1).cross(pxy2-pxy1)
    #rot = App.Rotation(axis,Radian=angle)
    xy1 = rot.multVec(xy1)
    xy2 = rot.multVec(xy2)
    # Now xy1.z and xy2.z should be 0. But alas, they are not!
    return (xy1,xy2)
But my spherical math is insufficient to allow me to see around which axis to rotate the translated face such that all its edges get located flat onto the XY plane. And the one I'm using in the code above results in the face being rotated, and v2 ending at the right position, but with the face not at all flat on the XY plane!

Any help is appreciated.

Kind regards,
Sebastian
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Trouble creating a flat net of a polyhedron

Post by edwilliams16 »

Without digging in, I see you using

Code: Select all

rot = App.Rotation(pp2-pp1,pxy2-pxy1)
This maps the pp2 - pp1 axis into the pxy2-pxy1 axis, but gives you no control over the axial rotation about the he pxy2-pxy1 axis.

My guess is that you will need to use the more powerful
App.Rotation(e1, e2, e3, 'XYZ') constructor. See https://wiki.freecadweb.org/Sandbox:Edw ... #Rotations

The inverse of this will allow you to rotate a object with known orientation axes back into the global XYZ triad.
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Trouble creating a flat net of a polyhedron

Post by edwilliams16 »

Here's the idea.

Code: Select all

doc = App.getDocument("Unnamed4")
obj = doc.getObject("Sketch")
p1 = obj.getSubObject("Vertex1").Point
p2 = obj.getSubObject("Vertex2").Point
p6 = obj.getSubObject("Vertex6").Point
dir1 = p2 - p1
dir2 = p6 - p1
#we want to map p1 to the origin, dir1 to the x-axis and dir2 into the XY-Plane
pl = App.Placement(p1, App.Rotation(dir1, dir2, App.Vector(0, 0, 0), 'XYZ')).inverse()
newSk = obj.Shape.copy()
newSk.Placement = pl * newSk.Placement
Part.show(newSk)
Attachments
invertplacement.FCStd
(5.26 KiB) Downloaded 10 times
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Trouble creating a flat net of a polyhedron

Post by edwilliams16 »

https://netlib.org/polyhedra/ has nets for many polyhedra. See also https://forum.freecadweb.org/viewtopic. ... 24#p517624

Ed

Edit: BTW I never was able to get the man page in readable form. http://www.netlib.no/netlib/polyhedra/index.html
wangnick
Posts: 19
Joined: Thu Nov 03, 2022 9:27 am

Re: Trouble creating a flat net of a polyhedron

Post by wangnick »

edwilliams16 wrote: Thu Nov 24, 2022 10:58 pm My guess is that you will need to use the more powerful
App.Rotation(e1, e2, e3, 'XYZ') constructor. See https://wiki.freecadweb.org/Sandbox:Edw ... #Rotations. The inverse of this will allow you to rotate a object with known orientation axes back into the global XYZ triad.
Thanks, Ed. However I followed a different path and reconstructed the faces on the XY axis from their edge lengths and angles. Here my code and results fwiw:

Code: Select all

doc = App.ActiveDocument
obj = doc.getObject("Geodesic_sphere")
shp = obj.Shape

import Draft
import math

def sgn(v): return 1 if v>0 else -1 if v<0 else 0

def sph(vec): 
    rad = math.hypot(*vec)
    inc = math.acos(vec.z/rad)
    azi = sgn(vec.y)*math.acos(vec.x/math.hypot(vec.x,vec.y))
    return (rad,inc,azi)

class W:
    def __init__(self,shp,i=None): 
        self.shp = shp
        self.i = i
    def __eq__(self,other): return self.shp.isSame(other.shp)
    def __hash__(self): return self.shp.hashCode()
    def __str__(self): return "%s%d"%(self.shp.ShapeType,self.i or 0)
    def __repr__(self): return "%s(%s)"%(self.__class__.__name__,str(self))

fd = dict()
for f in shp.Faces:
    wf = W(f,len(fd)+1)
    fd[wf] = wf

def F(f): return fd[W(f)]

def id(wf):
    inc,azi = map(math.degrees,sph(wf.com)[1:])
    lvl = int(inc/12+0.5)
    idx = int((azi%360)/7.2+0.5) if abs(inc-90)<89.9 else 0
    return lvl*100+idx

for wf in fd:
    wf.com = wf.shp.CenterOfMass
    wf.id = id(wf)
    #print("%s %s"%(wf,wf.id))
    wf.el = wf.shp.Edges # Assume ordered list of edges
    vlt = list(tuple(v.Point for v in e.Vertexes) for e in wf.el)
    wf.vl = list(vlt[i][0] if vlt[i][1] in vlt[(i+1)%len(vlt)] else vlt[i][1] for i in range(len(vlt))) # Order vertices accordingly
    wf.vl.append(wf.vl[0]) # This eases edge processing late or
    xp = (wf.vl[0]-wf.com).cross(wf.vl[1]-wf.com).normalize() # Check if the cross product point outwards
    if (wf.com+xp).Length<wf.com.Length: # Inwards-pointing. Reverse order of points and edges
        #print("Reversing F%s"%wf.id)
        wf.vl.reverse()
        wf.el.reverse()
    wf.fl = list(F(tuple(f for f in shp.ancestorsOfType(e,Part.Face) if not f.isSame(wf.shp))[0]) for e in wf.el)
fid = dict((wf.id,wf) for wf in fd)   
  
cs = set()
for i in range(0,50,10):
    for j in range(0,10,2):
        cs.add((700+i+j,801+i+j))
        if 702+i+j<750: cs.add((801+i+j,702+i+j))
        cs.add((801+i+j,900+i+j))
        cs.add((700+i+j,601+i+j))
        cs.add((601+i+j,500+i+j))
        if j in (0,2,6,8):
            j2 = 5 if j==6 else j
            cs.add((500+i+j,400+i+j2))
            j = j2
            if j in (0,2,8):
                j2 = 3 if j==2 else 7 if j==8 else j
                cs.add((400+i+j,300+i+j2))
                j = j2
                if j in (0,3):
                    j2 = 5 if j==3 else j
                    cs.add((300+i+j,200+i+j2))
                    if j==0:
                        cs.add((200+i,100+i))
cs.add((100,0))

txtl = []
objl = []
def flatten(pwf,xy1,xy2,wf):
    #print("Flattening F%s, previous face F%s edge %s %s"%(wf.id,pwf.id,xy1,xy2))
    for i in range(len(wf.el)): 
        if wf.fl[i]==pwf: break # Which of our edges connects to the previous face?
    p1,p2 = wf.vl[i:i+2]
    elo = [p1,p2]
    elxy = [xy1,xy2]
    nd = p2-p1
    nxyd = (xy2-xy1).normalize()
    for j in range(len(wf.el)-1):
        od = nd
        oxyd = nxyd
        p1 = p2
        xy1 = xy2
        i1 = (i+j+1)%len(wf.el)
        p2 = wf.vl[i1+1]
        nd = p2-p1
        angle = nd.getAngle(od)
        rot = App.Rotation(App.Vector(0,0,1),Radian=angle) # This assumes a certain winding rule
        nxyd = rot.multVec(oxyd)
        xy2 = xy1+nxyd*nd.Length
        elo.append(p2)
        elxy.append(xy2)
        owf = wf.fl[i1]
        if (wf.id,owf.id) in cs:
            cs.remove((wf.id,owf.id))
            flatten(wf,xy2,xy1,owf) # We swap the xy points because the winding rule seems to swaps from face to face
    py = Part.makePolygon(elxy)
    shp = Part.Face(py)
    obj = Part.show(shp,"F%s elxy"%wf.id)
    objl.append(obj)
    pl = App.Placement()
    pl.Base = shp.CenterOfMass
    pl.Rotation = App.Rotation(App.Vector(0,0,1),shp.normalAt(0,0))
    txt = Draft.make_text("F%s"%wf.id,placement=pl)
    txt.ViewObject.FontSize = 3
    txt.ViewObject.Justification = "Center"
    txtl.append(txt)

wf = fid[700]
pwf = fid[849]
for i in range(len(pwf.el)):
    if pwf.fl[i]==wf: break
#print(f"i:{i} vl:{pwf.vl}")
p1,p2 = pwf.vl[i:i+2]
#print(f"p1:{p1} p2:{p2}")
xy1 = App.Vector(0,0,0)
rot = App.Rotation(App.Vector(0,0,1),Degree=58.88)
xy2 = xy1+rot.multVec(App.Vector(0,-(p2-p1).Length,0))

flatten(pwf,xy1,xy2,wf)

cmpd = doc.addObject("Part::Compound","FlattenedFaces")
cmpd.Links = objl

cmpd = doc.addObject("Part::Compound","FlattenedFaceLabels")
cmpd.Links = txtl
for txt in txtl:
    txt.ViewObject.Visibility = True

doc.recompute()
goldbergnet02.png
goldbergnet02.png (244.1 KiB) Viewed 680 times
Kind regards,
Sebastian

PS: Folks, Draft.make_text() is very slow because it creates a few lines of output into the Report view. Actually, any more significant Python output into the Report view slows down macros a hell of a lot. I'm using vanilla 0.20.1 on Windows. Is that normal?
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Trouble creating a flat net of a polyhedron

Post by edwilliams16 »

Your script fails for me, acting on your goldbergtest03.FCStd file

First error is:

Code: Select all

pwf = fid[849]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
KeyError: 849
generating downstream errors.

For me

Code: Select all

>>> fid
{135: W(Face1), 335: W(Face2), 730: W(Face3), 830: W(Face4), 1430: W(Face5), 1230: W(Face6), 1127: W(Face7), 1133: W(Face8), 1440: W(Face9), 1137: W(Face10), 1240: W(Face11), 1143: W(Face12), 1400: W(Face13), 1147: W(Face14), 1200: W(Face15), 1103: W(Face16), 1410: W(Face17), 1107: W(Face18), 1210: W(Face19), 1113: W(Face20), 1420: W(Face21), 1123: W(Face22), 1117: W(Face23), 1220: W(Face24), 933: W(Face25), 927: W(Face26), 835: W(Face27), 632: W(Face28), 735: W(Face29), 638: W(Face30), 740: W(Face31), 943: W(Face32), 937: W(Face33), 840: W(Face34), 845: W(Face35), 642: W(Face36), 745: W(Face37), 648: W(Face38), 700: W(Face39), 903: W(Face40), 947: W(Face41), 800: W(Face42), 805: W(Face43), 602: W(Face44), 705: W(Face45), 608: W(Face46), 710: W(Face47), 913: W(Face48), 907: W(Face49), 810: W(Face50), 815: W(Face51), 612: W(Face52), 715: W(Face53), 618: W(Face54), 720: W(Face55), 923: W(Face56), 917: W(Face57), 820: W(Face58), 825: W(Face59), 628: W(Face60), 622: W(Face61), 725: W(Face62), 432: W(Face63), 438: W(Face64), 145: W(Face65), 442: W(Face66), 448: W(Face67), 345: W(Face68), 105: W(Face69), 402: W(Face70), 408: W(Face71), 305: W(Face72), 115: W(Face73), 412: W(Face74), 418: W(Face75), 315: W(Face76), 125: W(Face77), 422: W(Face78), 428: W(Face79), 325: W(Face80)}
>>> 
What algorithm did you use to generate the net? It would take me a week to penetrate your coding! I was under the impression from Wikipedia there was no general algorithm, or even if a net existed for a general convex polyhedron. https://en.wikipedia.org/wiki/Net_(poly ... olyhedron.
wangnick
Posts: 19
Joined: Thu Nov 03, 2022 9:27 am

Re: Trouble creating a flat net of a polyhedron

Post by wangnick »

edwilliams16 wrote: Sat Nov 26, 2022 10:26 pm Your script fails for me, acting on your goldbergtest03.FCStd file
Attached the file I used.

First thing I do is to wrap all faces (class W implementing __hash__ and __eq__, and allowing me to add additional information to each face easily) and collect them into a dictionary. Then I produce for every face an ordered list of edges running counterclockwise when viewed from the outside of the sphere, and the vertices and the faces they border to accordingly.

I also establish (function id()) and then refer to a certain naming of the faces, where each face gets an index consisting of its integer longitude within the interval [0..50[, plus 100 times its latitude within the interval [0..15]. This way all the 14 "levels" of the Goldberg face rings from top to botton plus the two polar cap faces can be easily referred to, and similarly the longitudinal sequence of faces within the rings is easily visible and referable.

The algorithm is very specific for exactly the Goldberg polyhedron that is dual to a geodesic sphere with the underlying icosahedron triangle edges divided by 5, and for a very specific net which me and my partner devised on paper before and which aims to minimise the bounding box area and to ease construction of the design object that we're ultimately aiming at.

HTH.

Kind regards,
Sebastian
Attachments
goldberglabeltest.FCStd
(719.7 KiB) Downloaded 9 times
edwilliams16
Veteran
Posts: 3106
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Trouble creating a flat net of a polyhedron

Post by edwilliams16 »

Thanks for the explanation. I had noticed that your net didn't seem to be of the entire polyhedron. With your labelling, I see that it only includes latitudes 1-9. I assume that is all you actually need.

I generalized some earlier work to allow construction of any of the polyhedra in the netlib database (which doesn't appear to include the Goldberg polyhedra, unfortunately). Curiously, there are some polyhedra for which they provide the net, but not the 3D vertices! This is the opposite of what I would have expected.
https://forum.freecadweb.org/viewtopic. ... 22&t=73937

It's an interesting construction for geodesic dome like structures.
Post Reply