How can I find the 3d location of the tip of a pyramid which has a 3 sided base arbitrarily angled relative to the xy plane and remaining three faces with identical anglation relative to the z-axis.
The known variables are:
The three xyz vertices for the base face
The angle relative to the z-axis for the other three faces
(this would be acceptible if fixed at pi/4, but much better if it could be determined on a case-by-case basis)
Why?
I am automatically generating supports for printing of a 3d modelled structure. In 3d printing, faces which "overhang" (ie are inverted and within approx 45 degrees of horizontal) often fail to print correctly without support.
The intention is to develop a more robust support structure for these faces which spreads force more evenly and thus requires less point adhesion to the printed object which therefore reduces surface damage when removed.
The option being currently tested is a series of inverted triangular pyramids. The pyramid faces will have an identical angle relative to horizontal (typically 45 deg, but it would be ideal if this angle could be arbitrarily set between 0 and PI/2). To minimise waste and optomise quality this angle should be identical for each of the 3 non-base faces irrespective of the orientation of the base.
Although many models to have faces with more than 3 sides, for practical purposes any faces with more than 3 sides will have previously been split into multiple 3-sided "base" faces which are each processed individually.
I have coded the following with outputs as per linked graphics which is probably "good enough". However although I don't believe it is robust (as evidenced when recursively looped), I haven't managed to get closer to a better solution.
triangular pyramidal supports underneath hand
triangular pyramidal supports underneath icosohedron
def support_interface(faceVertices, maxAngle):
vertices = []
for vert in faceVertices:
vertices.append((vert[X], vert[Y], vert[Z]))
# find a point which is maxAngle (currently locked at 45 degrees) below and between each pair
# vertices loops anticlockwise around the face
# then average this to get an approximate intersection point
#potentially repeat the process to successively approximate the solution
curV = 0 # which vertex pair is currently being evaluated?
nextApproxVert = [] # temp list holding the most recent full iteration around all the vertex pairs
closeEnough = False # not really needed - but set up for future successive approximation
loopCnt = 1 # the maximum number of loops to iterate over
#maxAngle = -3.14159/4 # locked at 45 degrees for testing
PI_2 = -3.14159/2 # 90 degrees
while not closeEnough:
# load the next vertex pair in the loop around the edge of the face
x0 = vertices[curV][0]
y0 = vertices[curV][1]
z0 = vertices[curV][2]
if curV < vertexCnt -1:
x1 = vertices[curV+1][0]
y1 = vertices[curV+1][1]
z1 = vertices[curV+1][2]
else:
x1 = vertices[0][0]
y1 = vertices[0][1]
z1 = vertices[0][2]
# get the xyz axial distances of the triangle
dx = x0 - x1
dy = y0 - y1
dz = z0 - z1
#print("dxyz=",dx,dy,dz)
# get the 2D distance on the z-plane between vertices
xyLen = math.sqrt(dx*dx + dy*dy)
#print("xyLen",xyLen)
# get the 3D distance between vertices
h0 = math.sqrt(dx*dx + dy*dy + dz*dz)
#print("h0",h0)
# get the angle of the hypotenuse relative to horizontal
a0 = abs(math.asin(dz/h0))
#print("a0",a0)
#get the angle of the additional triangle needed to keep angle under max value
a1 = PI_2 - a0 - maxAngle
#print("a1",a1)
#get the length of the opp and adj sides of the "safe" triangle
opp = math.sin(a1) * h0 # the z drop from the lower vertex to the "safe" vertex
adj = math.cos(a1) * h0
#print("opAd",opp, adj)
#now get the other side lengths for the triangle from the higher vertex to the safe vertex
# at 90deg to horizontal
zHigh = math.asin(PI_2 - maxAngle) * adj # adj for the previous triangle is hyp of the new one
zAdj = math.acos(PI_2 - maxAngle) * adj
#print("zHigh/zAdj",zHigh,zAdj)
xyProp = zAdj / xyLen
#print("xyProp",xyProp)
dx1 = dx * xyProp
dy1 = dy * xyProp
#print("dxy1",dx1,dy1)
nextApproxVert.append((x0 - dx1, y0 - dy1, z0 + zHigh))
curV +=1
if curV >= vertexCnt:
curV = 0
loopCnt -= 1
#print(nextApproxVert)
vertices = nextApproxVert
nextApproxVert = []
if loopCnt <= 0:
closeEnough = True
# after it meets criteria for closeEnough then take the average of vertices
(x,y,z)=(0,0,0)
for v in vertices:
x += v[0]
y += v[1]
z += v[2]
x=x/vertexCnt
y=y/vertexCnt
z=z/vertexCnt
return (x,y,z)