2
votes

I'm struggling to write a script in 3ds Max to create a tube with weight changing along it.

As input I have points, which this tube should pass, also orientation and width at those points. This tube is supposed to be a visualization of something, and for me is important to have it as one object in order to map a texture on it later (like, color will be another visualized parameter).

I'm relatively new to the maxscript, so I might miss an obvious approach, but so far I found only extrusion applicable for the task.

Since I need tapering at each step I thought first to make a bunch of initial circles and then extrude them into tubes. But in order to arrive to next point, I need to rotate the circle according to direction of the tube. After some searching I found a way to rotate a single face, but it seems I'm messing up with the vector to angle transformation, because tubes do not look like they are supposed to.

Could you please tell me where I'm mistaken and, even more important, if there is a better way to draw tubes with variable width?

Here is the code:

--first, matrix with all the points of 5 tubes. for each point the first three numbers are position, next 3 - direction, then width, but I don't use width in this code, since I encountered problems even befor approaching tapering.
tubeMatr = #(#(#(-0.252, -0.282, 0.02, 0.025, 0.953, 0.302, 181.2), #(-0.252, -0.282, 0.074, 0.045, 0.957, -0.183, 42.16), #(-0.252, -0.282, 0.128, 0.045, 0.927, -0.341, 25.83), #(-0.252, -0.282, 0.182, 0.013, 0.903, -0.407, 23.73), #(-0.252, -0.191, 0.02, 0.062, 0.955, 0.282, 317.7)), #(#(-0.252, -0.272, 0.023, 0.03, 0.952, 0.274, 192.8), #(-0.251, -0.272, 0.072, 0.06, 0.945, -0.195, 51.41), #(-0.251, -0.273, 0.124, 0.056, 0.916, -0.361, 27.39), #(-0.252, -0.273, 0.178, 0.026, 0.892, -0.427, 24.9), #(-0.251, -0.181, 0.023, 0.069, 0.955, 0.25, 309.6)), #(#(-0.251, -0.263, 0.026, 0.036, 0.95, 0.245, 203.5), #(-0.251, -0.263, 0.07, 0.073, 0.934, -0.204, 62.15), #(-0.251, -0.263, 0.121, 0.068, 0.906, -0.381, 29.05), #(-0.251, -0.264, 0.173, 0.039, 0.88, -0.447, 26.13), #(-0.25, -0.172, 0.025, 0.074, 0.958, 0.226, 304.1)), #(#(-0.251, -0.253, 0.028, 0.044, 0.947, 0.216, 213.4), #(-0.25, -0.253, 0.068, 0.084, 0.924, -0.21, 74.34), #(-0.25, -0.254, 0.117, 0.08, 0.895, -0.401, 30.81), #(-0.251, -0.255, 0.169, 0.051, 0.87, -0.467, 27.44), #(-0.25, -0.162, 0.028, 0.077, 0.961, 0.21, 300.6)), #(#(-0.25, -0.244, 0.03, 0.051, 0.943, 0.188, 223.0), #(-0.249, -0.244, 0.066, 0.094, 0.915, -0.214, 87.96), #(-0.249, -0.245, 0.113, 0.092, 0.885, -0.423, 32.69), #(-0.25, -0.246, 0.164, 0.063, 0.859, -0.486, 28.84), #(-0.249, -0.153, 0.03, 0.079, 0.965, 0.199, 298.8)), #(#(-0.25, -0.234, 0.032, 0.06, 0.939, 0.16, 232.4), #(-0.248, -0.235, 0.064, 0.103, 0.907, -0.215, 102.9), #(-0.248, -0.236, 0.109, 0.106, 0.875, -0.445, 34.7), #(-0.25, -0.238, 0.159, 0.075, 0.849, -0.505, 30.33), #(-0.248, -0.143, 0.032, 0.08, 0.971, 0.193, 298.3)), #(#(-0.249, -0.225, 0.034, 0.068, 0.934, 0.133, 241.8), #(-0.247, -0.226, 0.062, 0.11, 0.9, -0.214, 119.2), #(-0.247, -0.228, 0.104, 0.12, 0.865, -0.468, 36.87), #(-0.249, -0.229, 0.154, 0.087, 0.84, -0.525, 31.93), #(-0.247, -0.133, 0.034, 0.079, 0.973, 0.19, 301.0)), #(#(-0.249, -0.216, 0.035, 0.073, 0.933, 0.115, 246.9), #(-0.246, -0.217, 0.06, 0.111, 0.9, -0.192, 138.0), #(-0.246, -0.219, 0.1, 0.132, 0.857, -0.486, 40.28), #(-0.248, -0.221, 0.149, 0.098, 0.83, -0.544, 33.64), #(-0.247, -0.124, 0.035, 0.077, 0.973, 0.19, 305.7)), #(#(-0.248, -0.206, 0.036, 0.075, 0.937, 0.11, 247.8), #(-0.245, -0.208, 0.058, 0.101, 0.912, -0.136, 158.1), #(-0.245, -0.211, 0.095, 0.126, 0.866, -0.443, 54.04), #(-0.247, -0.213, 0.144, 0.101, 0.819, -0.558, 35.67), #(-0.246, -0.114, 0.037, 0.075, 0.974, 0.19, 311.5)), #(#(-0.247, -0.197, 0.037, 0.076, 0.941, 0.108, 249.5), #(-0.244, -0.199, 0.056, 0.094, 0.923, -0.09, 175.0), #(-0.244, -0.202, 0.09, 0.119, 0.878, -0.389, 70.1), #(-0.246, -0.204, 0.138, 0.102, 0.809, -0.566, 38.7), #(-0.245, -0.104, 0.039, 0.073, 0.974, 0.191, 318.5)), #(#(-0.246, -0.188, 0.039, 0.076, 0.947, 0.108, 251.9), #(-0.243, -0.19, 0.055, 0.088, 0.933, -0.051, 189.6), #(-0.242, -0.193, 0.086, 0.112, 0.895, -0.326, 88.15), #(-0.245, -0.196, 0.132, 0.102, 0.806, -0.555, 45.22), #(-0.244, -0.094, 0.041, 0.071, 0.974, 0.194, 326.7)), #(#(-0.246, -0.178, 0.04, 0.076, 0.952, 0.111, 254.9), #(-0.242, -0.18, 0.055, 0.083, 0.943, -0.017, 202.4), #(-0.241, -0.184, 0.083, 0.104, 0.914, -0.256, 107.8), #(-0.244, -0.188, 0.127, 0.101, 0.808, -0.531, 54.32), #(-0.244, -0.085, 0.043, 0.069, 0.974, 0.197, 336.0)), #(#(-0.245, -0.169, 0.041, 0.076, 0.959, 0.116, 258.3), #(-0.241, -0.171, 0.055, 0.079, 0.954, 0.015, 214.0), #(-0.24, -0.175, 0.081, 0.096, 0.935, -0.18, 128.4), #(-0.243, -0.18, 0.122, 0.098, 0.814, -0.496, 65.77), #(-0.243, -0.075, 0.045, 0.067, 0.973, 0.202, 346.5)), #(#(-0.244, -0.159, 0.042, 0.075, 0.966, 0.123, 262.3), #(-0.241, -0.161, 0.055, 0.075, 0.964, 0.044, 224.6), #(-0.239, -0.166, 0.079, 0.087, 0.952, -0.107, 150.9), #(-0.242, -0.172, 0.117, 0.094, 0.824, -0.452, 79.27), #(-0.242, -0.065, 0.047, 0.065, 0.973, 0.208, 358.3)), #(#(-0.243, -0.149, 0.043, 0.073, 0.974, 0.133, 266.8), #(-0.24, -0.152, 0.055, 0.071, 0.975, 0.073, 234.7), #(-0.238, -0.156, 0.078, 0.078, 0.968, -0.041, 171.8), #(-0.241, -0.164, 0.112, 0.09, 0.836, -0.4, 94.45), #(-0.242, -0.055, 0.049, 0.063, 0.972, 0.213, 366.9)), #(#(-0.243, -0.14, 0.044, 0.072, 0.982, 0.146, 271.9), #(-0.239, -0.142, 0.056, 0.068, 0.986, 0.103, 244.5), #(-0.238, -0.146, 0.077, 0.071, 0.984, 0.022, 190.9), #(-0.24, -0.155, 0.108, 0.085, 0.851, -0.342, 110.9), #(-0.241, -0.046, 0.051, 0.063, 0.972, 0.216, 371.1)), #(#(-0.242, -0.13, 0.046, 0.07, 0.982, 0.149, 280.5), #(-0.238, -0.132, 0.057, 0.066, 0.987, 0.115, 256.8), #(-0.237, -0.137, 0.078, 0.066, 0.994, 0.067, 209.9), #(-0.24, -0.147, 0.105, 0.079, 0.867, -0.28, 128.2), #(-0.24, -0.036, 0.053, 0.063, 0.971, 0.219, 375.5)), #(#(-0.241, -0.12, 0.047, 0.068, 0.981, 0.153, 290.0), #(-0.238, -0.122, 0.058, 0.064, 0.987, 0.124, 270.2), #(-0.236, -0.127, 0.078, 0.064, 0.992, 0.083, 230.4), #(-0.239, -0.138, 0.102, 0.074, 0.881, -0.223, 146.1), #(-0.24, -0.026, 0.056, 0.063, 0.97, 0.223, 380.0)), #(#(-0.241, -0.11, 0.049, 0.066, 0.981, 0.158, 300.3), #(-0.237, -0.112, 0.059, 0.062, 0.986, 0.132, 284.2), #(-0.236, -0.117, 0.079, 0.061, 0.99, 0.098, 251.1), #(-0.238, -0.129, 0.1, 0.069, 0.886, -0.188, 165.3), #(-0.239, -0.017, 0.058, 0.063, 0.97, 0.227, 384.7)), #(#(-0.24, -0.1, 0.051, 0.064, 0.98, 0.164, 311.4), #(-0.237, -0.102, 0.061, 0.06, 0.985, 0.142, 298.8), #(-0.235, -0.107, 0.08, 0.059, 0.988, 0.113, 272.0), #(-0.237, -0.121, 0.098, 0.064, 0.89, -0.156, 184.9), #(-0.238, -0.007, 0.06, 0.063, 0.969, 0.232, 389.4)))

fn SetObjectRotation obj rx ry rz =
(
-- Reset the object's transformation matrix so that
-- it only includes position and scale information.
-- Doing this clears out any previous object rotation.
local translateMat = transMatrix obj.transform.pos
local scaleMat = scaleMatrix obj.transform.scale
obj.transform = scaleMat * translateMat

-- Perform each axis rotation individually
rotate obj (angleaxis rx [1,0,0])
rotate obj (angleaxis ry [0,1,0])
rotate obj (angleaxis rz [0,0,1])
)
--make empty array for tubes
extrudeMatr = #()
for i = 1 to ntubes do extrudeMatr[i] = 0

--make init circles
for j = 1 to 5 do--ntubes do 
(
    b=Circle radius:10 pos:[(tubeMatr[1][j][1] )*1000, (tubeMatr[1][j][2] )*1000,(tubeMatr[1][j][3] )*1000] isSelected:on
    convertTo b Editable_Poly
    SetObjectRotation b 0 (acos (tubeMatr[1][j][6] as float))  (atan2 (tubeMatr[1][j][5] as float)  (tubeMatr[1][j][4] as float))
    extrudeMatr[j]=b
)

--length of step
stepVec=#(tubeMatr[2][1][1]- tubeMatr[1][1][1] , tubeMatr[2][1][2] - tubeMatr[1][1][2], tubeMatr[2][1][3] - tubeMatr[1][1][3])
stepLength= sqrt (stepVec[1]^2+stepVec[2]^2+stepVec[2]^2)


--make tubes

diffAng=#()
for i = 1 to nsteps-1 do 
(
    for j = 1 to 5 do--ntubes do 
    (

        extrudeMatr[j].EditablePoly.SetSelection #Face #{1}
        extrudeMatr[j].extrudeFaces 5
        update extrudeMatr[j]
     -- I change the rotation of the faces based on the difference between angles of two vectors. This is the point I'm less confident. 
        diffAng[i]=#((atan2 tubeMatr[i+1][j][5]  tubeMatr[i+1][j][4]) - (atan2 (tubeMatr[i][j][5])  (tubeMatr[i][j][4] )) ,  (acos (tubeMatr[i+1][j][6] ))-(acos (tubeMatr[i][j][6] )))  

        extrudeMatr[j].EditablePoly.SetSelection #Face #{1}
--next chunk performs the rotation of the face. I found it on one of the cg forums and it seems legit. Though, I might use it wrong
        (
        angle =diffAng[i][1]
        selected_faces = (polyOp.getFaceSelection extrudeMatr[j]) as array
        center_face = polyOp.getFaceCenter extrudeMatr[j] selected_faces[1]
        transform_mat = matrixFromNormal (polyOp.getFaceNormal extrudeMatr[j] selected_faces[1])
        transform_mat.row4 = center_face --move TM to center of polygon

        face_vertices = (polyOp.getVertsUsingFace extrudeMatr[j] #selection) as array

        for v in face_vertices do
        (
        in coordsys transform_mat --get vertex in Normal's TM coordinates
        vertex_position = (polyOp.getVert extrudeMatr[j] v)
        -- create a Rotation TM, transform in the Normal's TM:
        RotateZ = (rotateZMatrix angle) * transform_mat
        -- finally, set the vertex to the position transformed by the rotation matrix
        polyOp.setVert extrudeMatr[j] v (vertex_position * RotateZ)
        )
        angle = diffAng[i][2]
        for v in face_vertices do
        (
        in coordsys transform_mat --get vertex in Normal's TM coordinates
        vertex_position = (polyOp.getVert extrudeMatr[j] v)
        -- create a Rotation TM, transform in the Normal's TM:
        RotateZ = (rotateYMatrix angle) * transform_mat
        -- finally, set the vertex to the position transformed by the rotation matrix
        polyOp.setVert extrudeMatr[j] v (vertex_position * RotateZ)
        )

        )

    )
)

Also I tried to do that with splines, but there after rotation of the face all went to hell (since extrusion was relative to the circle orientation), so I dropped that method. However, splines here show, where the tube is supposed to go (care about shape, not length):

    --first, same matrix
    tubeMatr = #(#(#(-0.252, -0.282, 0.02, 0.025, 0.953, 0.302, 181.2), #(-0.252, -0.282, 0.074, 0.045, 0.957, -0.183, 42.16), #(-0.252, -0.282, 0.128, 0.045, 0.927, -0.341, 25.83), #(-0.252, -0.282, 0.182, 0.013, 0.903, -0.407, 23.73), #(-0.252, -0.191, 0.02, 0.062, 0.955, 0.282, 317.7)), #(#(-0.252, -0.272, 0.023, 0.03, 0.952, 0.274, 192.8), #(-0.251, -0.272, 0.072, 0.06, 0.945, -0.195, 51.41), #(-0.251, -0.273, 0.124, 0.056, 0.916, -0.361, 27.39), #(-0.252, -0.273, 0.178, 0.026, 0.892, -0.427, 24.9), #(-0.251, -0.181, 0.023, 0.069, 0.955, 0.25, 309.6)), #(#(-0.251, -0.263, 0.026, 0.036, 0.95, 0.245, 203.5), #(-0.251, -0.263, 0.07, 0.073, 0.934, -0.204, 62.15), #(-0.251, -0.263, 0.121, 0.068, 0.906, -0.381, 29.05), #(-0.251, -0.264, 0.173, 0.039, 0.88, -0.447, 26.13), #(-0.25, -0.172, 0.025, 0.074, 0.958, 0.226, 304.1)), #(#(-0.251, -0.253, 0.028, 0.044, 0.947, 0.216, 213.4), #(-0.25, -0.253, 0.068, 0.084, 0.924, -0.21, 74.34), #(-0.25, -0.254, 0.117, 0.08, 0.895, -0.401, 30.81), #(-0.251, -0.255, 0.169, 0.051, 0.87, -0.467, 27.44), #(-0.25, -0.162, 0.028, 0.077, 0.961, 0.21, 300.6)), #(#(-0.25, -0.244, 0.03, 0.051, 0.943, 0.188, 223.0), #(-0.249, -0.244, 0.066, 0.094, 0.915, -0.214, 87.96), #(-0.249, -0.245, 0.113, 0.092, 0.885, -0.423, 32.69), #(-0.25, -0.246, 0.164, 0.063, 0.859, -0.486, 28.84), #(-0.249, -0.153, 0.03, 0.079, 0.965, 0.199, 298.8)), #(#(-0.25, -0.234, 0.032, 0.06, 0.939, 0.16, 232.4), #(-0.248, -0.235, 0.064, 0.103, 0.907, -0.215, 102.9), #(-0.248, -0.236, 0.109, 0.106, 0.875, -0.445, 34.7), #(-0.25, -0.238, 0.159, 0.075, 0.849, -0.505, 30.33), #(-0.248, -0.143, 0.032, 0.08, 0.971, 0.193, 298.3)), #(#(-0.249, -0.225, 0.034, 0.068, 0.934, 0.133, 241.8), #(-0.247, -0.226, 0.062, 0.11, 0.9, -0.214, 119.2), #(-0.247, -0.228, 0.104, 0.12, 0.865, -0.468, 36.87), #(-0.249, -0.229, 0.154, 0.087, 0.84, -0.525, 31.93), #(-0.247, -0.133, 0.034, 0.079, 0.973, 0.19, 301.0)), #(#(-0.249, -0.216, 0.035, 0.073, 0.933, 0.115, 246.9), #(-0.246, -0.217, 0.06, 0.111, 0.9, -0.192, 138.0), #(-0.246, -0.219, 0.1, 0.132, 0.857, -0.486, 40.28), #(-0.248, -0.221, 0.149, 0.098, 0.83, -0.544, 33.64), #(-0.247, -0.124, 0.035, 0.077, 0.973, 0.19, 305.7)), #(#(-0.248, -0.206, 0.036, 0.075, 0.937, 0.11, 247.8), #(-0.245, -0.208, 0.058, 0.101, 0.912, -0.136, 158.1), #(-0.245, -0.211, 0.095, 0.126, 0.866, -0.443, 54.04), #(-0.247, -0.213, 0.144, 0.101, 0.819, -0.558, 35.67), #(-0.246, -0.114, 0.037, 0.075, 0.974, 0.19, 311.5)), #(#(-0.247, -0.197, 0.037, 0.076, 0.941, 0.108, 249.5), #(-0.244, -0.199, 0.056, 0.094, 0.923, -0.09, 175.0), #(-0.244, -0.202, 0.09, 0.119, 0.878, -0.389, 70.1), #(-0.246, -0.204, 0.138, 0.102, 0.809, -0.566, 38.7), #(-0.245, -0.104, 0.039, 0.073, 0.974, 0.191, 318.5)), #(#(-0.246, -0.188, 0.039, 0.076, 0.947, 0.108, 251.9), #(-0.243, -0.19, 0.055, 0.088, 0.933, -0.051, 189.6), #(-0.242, -0.193, 0.086, 0.112, 0.895, -0.326, 88.15), #(-0.245, -0.196, 0.132, 0.102, 0.806, -0.555, 45.22), #(-0.244, -0.094, 0.041, 0.071, 0.974, 0.194, 326.7)), #(#(-0.246, -0.178, 0.04, 0.076, 0.952, 0.111, 254.9), #(-0.242, -0.18, 0.055, 0.083, 0.943, -0.017, 202.4), #(-0.241, -0.184, 0.083, 0.104, 0.914, -0.256, 107.8), #(-0.244, -0.188, 0.127, 0.101, 0.808, -0.531, 54.32), #(-0.244, -0.085, 0.043, 0.069, 0.974, 0.197, 336.0)), #(#(-0.245, -0.169, 0.041, 0.076, 0.959, 0.116, 258.3), #(-0.241, -0.171, 0.055, 0.079, 0.954, 0.015, 214.0), #(-0.24, -0.175, 0.081, 0.096, 0.935, -0.18, 128.4), #(-0.243, -0.18, 0.122, 0.098, 0.814, -0.496, 65.77), #(-0.243, -0.075, 0.045, 0.067, 0.973, 0.202, 346.5)), #(#(-0.244, -0.159, 0.042, 0.075, 0.966, 0.123, 262.3), #(-0.241, -0.161, 0.055, 0.075, 0.964, 0.044, 224.6), #(-0.239, -0.166, 0.079, 0.087, 0.952, -0.107, 150.9), #(-0.242, -0.172, 0.117, 0.094, 0.824, -0.452, 79.27), #(-0.242, -0.065, 0.047, 0.065, 0.973, 0.208, 358.3)), #(#(-0.243, -0.149, 0.043, 0.073, 0.974, 0.133, 266.8), #(-0.24, -0.152, 0.055, 0.071, 0.975, 0.073, 234.7), #(-0.238, -0.156, 0.078, 0.078, 0.968, -0.041, 171.8), #(-0.241, -0.164, 0.112, 0.09, 0.836, -0.4, 94.45), #(-0.242, -0.055, 0.049, 0.063, 0.972, 0.213, 366.9)), #(#(-0.243, -0.14, 0.044, 0.072, 0.982, 0.146, 271.9), #(-0.239, -0.142, 0.056, 0.068, 0.986, 0.103, 244.5), #(-0.238, -0.146, 0.077, 0.071, 0.984, 0.022, 190.9), #(-0.24, -0.155, 0.108, 0.085, 0.851, -0.342, 110.9), #(-0.241, -0.046, 0.051, 0.063, 0.972, 0.216, 371.1)), #(#(-0.242, -0.13, 0.046, 0.07, 0.982, 0.149, 280.5), #(-0.238, -0.132, 0.057, 0.066, 0.987, 0.115, 256.8), #(-0.237, -0.137, 0.078, 0.066, 0.994, 0.067, 209.9), #(-0.24, -0.147, 0.105, 0.079, 0.867, -0.28, 128.2), #(-0.24, -0.036, 0.053, 0.063, 0.971, 0.219, 375.5)), #(#(-0.241, -0.12, 0.047, 0.068, 0.981, 0.153, 290.0), #(-0.238, -0.122, 0.058, 0.064, 0.987, 0.124, 270.2), #(-0.236, -0.127, 0.078, 0.064, 0.992, 0.083, 230.4), #(-0.239, -0.138, 0.102, 0.074, 0.881, -0.223, 146.1), #(-0.24, -0.026, 0.056, 0.063, 0.97, 0.223, 380.0)), #(#(-0.241, -0.11, 0.049, 0.066, 0.981, 0.158, 300.3), #(-0.237, -0.112, 0.059, 0.062, 0.986, 0.132, 284.2), #(-0.236, -0.117, 0.079, 0.061, 0.99, 0.098, 251.1), #(-0.238, -0.129, 0.1, 0.069, 0.886, -0.188, 165.3), #(-0.239, -0.017, 0.058, 0.063, 0.97, 0.227, 384.7)), #(#(-0.24, -0.1, 0.051, 0.064, 0.98, 0.164, 311.4), #(-0.237, -0.102, 0.061, 0.06, 0.985, 0.142, 298.8), #(-0.235, -0.107, 0.08, 0.059, 0.988, 0.113, 272.0), #(-0.237, -0.121, 0.098, 0.064, 0.89, -0.156, 184.9), #(-0.238, -0.007, 0.06, 0.063, 0.969, 0.232, 389.4)))
--make empty array for tubes
extrudeMatr = #()
for i = 1 to ntubes do extrudeMatr[i] = 0

--make splines
for i = 1 to nsteps-1 do 
(
    for j = 1 to 5 do--ntubes do 
    (

        PointA=[(tubeMatr[i][j][1] as float)*1000, (tubeMatr[i][j][2] as float)*1000,(tubeMatr[i][j][3] as float)*1000]
        PointB=[(tubeMatr[i+1][j][1] as float)*1000, (tubeMatr[i+1][j][2] as float)*1000,(tubeMatr[i+1][j][3] as float)*1000]
        ss = SplineShape pos:PointA
        addNewSpline ss
        addKnot ss 1 #corner #line PointA
        addKnot ss 1 #corner #line PointB
        updateShape ss

    )
)

Sorry for such a long question, just I'm really stuck here.

1

1 Answers

0
votes

Just curious, what's the axis order here? Looks kinda wonky when I assume its XYZ:

struct tubePoint
(
    data,
    pos = 1000 * [data[1], data[2], data[3]],
    dir = [data[4], data[5], data[6]],
    radius = data[7] / 2d0
)

fn updateTM tm rot =
(
    tm.row1 *= rot
    tm.row2 *= rot
    tm.row3 *= rot
)

fn getRotQuat vec1 vec2 =
    quat (acos (dot vec1 vec2)) (normalize (cross vec1 vec2))

fn getTMAlongPath pt prevTM =
(
    updateTM prevTM (getRotQuat pt.dir prevTM.row3)
    prevTM * transMatrix pt.pos
)

fn addQuad pt1 pt2 pt3 pt4 &faces =
(
    append faces [pt1, pt4, pt3]
    append faces [pt3, pt2, pt1]
)

fn addNGonPoints n radius ngonTM &vertList clockwise:false =
(
    local angle = 360d0 / n * (if clockwise then 1 else -1)
    local vertCount = vertList.count

    for i = 1 to n collect
    (
        append vertList ([radius * sin (i * angle), radius * cos (i * angle), 0] * ngonTM)
        vertCount + i
    )
)

fn makeQuadStrip pts1 pts2 count &faceList closed:false =
(
    if closed do count -= 1
    for offset = 1 to count do
        addQuad pts1[offset] pts1[offset + 1] pts2[offset + 1] pts2[offset] &faceList

    if closed do addQuad pts1[count + 1] pts1[1] pts2[1] pts2[count + 1] &faceList
)

fn constructTube pts sides vertList:#() faceList:#() =
(
    local firstPoint = pts[1]
    local prevTM = arbAxis firstPoint.dir
    local prevRow = addNGonPoints sides firstPoint.radius (prevTM * transMatrix firstPoint.pos) &vertList clockwise:true

    for i = 2 to pts.count do
    (
        local nextPoint = pts[i]
        local nextRow = addNGonPoints sides nextPoint.radius (getTMAlongPath nextPoint prevTM) &vertList clockwise:true

        makeQuadStrip prevRow nextRow sides &faceList closed:true

        prevRow = nextRow
    )

    local tubeMesh = Mesh vertices:vertList faces:faceList
    for face in tubeMesh.faces do setEdgeVis tubeMesh face.index 3 false
    for face = 1 to faceList.count do setFaceSmoothGroup tubeMesh face (1 + int(mod ((face - 1) / (2 * sides)) 2))
)

for tubePts in tubeMatr do constructTube (for pt in tubePts collect tubePoint data:pt) 16

In order to get a rearranged list of data (i.e. #(#(1, 1, 1), #(2, 2, 2), #(3, 3, 3), #(4, 4, 4), #(5, 5, 5)) instead of #(#(1, 2, 3, 4, 5), #(1, 2, 3, 4, 5), #(1, 2, 3, 4, 5)), replace the last line with these two:

transposedTubeMatr = for i = 1 to tubeMatr[1].count collect for j = 1 to tubeMatr.count collect tubeMatr[j][i]
for tubePts in transposedTubeMatr do constructTube (for pt in tubePts collect tubePoint data:pt) 16