Hello, I have started a new journey to learn more about how CFrames, Vectors and all the script-align methods work.
I tried making tree part presets that will be randomly placed corresponding to the previous part, as well as have a random amount of them. Problem is I have so low knowledge about this part of scripting that I only just placed attachments at the ends of each tree where I want them to align. How would I go about this?

Here are the preset models (Ignore that it looks like Lumber Tycoon 2, Iâ€™m using this for reference and gonna change the model later):

While making the topic I have though of making it with ball joint using randomized angles and then anchor the whole thing, but how can I find all these separate parts and anchor them?

Thatâ€™s only really useful if you want the parts to move. Itâ€™s a better idea to just use attachments like you suggested.

Aligning attachments

Since youâ€™re probably going to align parts according to attachments a lot, itâ€™s good to have a reusable function that you can use across all your scripts. This one does what we need:

--Given two attachments, move the Parent of the 1st attachment
-- so that the 1st attachment has the same WorldCFrame as the second attachment.
function place_att_on_att(to_place, place_on)
to_place.Parent.CFrame = place_on.WorldCFrame * to_place.CFrame:inverse()
end

Long and rambly explanation

Attachments have two kinds of CFrames (and Positions), attachment.CFrame and attachment.WorldCFrame. Confusingly, the WorldCFrame is more like the CFrame we know from Parts and such. It represents the position and orientation of something in 3D space. The attachment.CFrame is relative to the Parent Part. So if an attachment has a Position of (0, 0, 0) then its WorldPosition is just the same as the Parent Partâ€™s Position.

The function has two parts of an expression for calculating the CFrame to place the Part at. The first part is this:

to_place.Parent.CFrame = place_on.WorldCFrame

If we just used this, the Part would be placed with its center at the given attachment. What we actual want is for the Part to be placed with its attachment placed at the other attachment. Itâ€™s the same thing, except itâ€™s moved over byâ€¦ well, what the CFrame of the to_place attachment is relative to the Part thatâ€™s being movedâ€¦ but like the opposite of that, which is why we transform the first part of the CFrame by the inverse of the CFrame of the attachment, so we end up with

If you pay close attention to the â€śbeforeâ€ť image, you can see that if you were to move the blue part so that the two arrows its Attachment lines up with the arrows on the red part, weâ€™ll end up with the â€śafterâ€ť imageâ€¦ so it works!

Rotated alignment

You could use this to generate some cool plants by rotating the attachments randomly, but thatâ€™s also a thing that youâ€™ll want to do a lot so hereâ€™s a function that does that for you:

--Given two attachments, move the Parent of the 1st attachment
-- so that the 1st attachment has the same WorldCFrame as the second attachment,
-- but rotated by angles. Temporarily changes the CFrame of the to_place attachment.
function place_att_on_att_rotated(to_place, place_on, angles)
to_place.CFrame *= angles
place_att_on_att(to_place, place_on)
to_place.CFrame *= angles:Inverse()
end

It temporarily rotates the to_place attachment so that the Part ends up with a different orientation. It reverses the rotation afterwards by rotating again, but with the inverse of the CFrame (â€śanglesâ€ť) that was used to rotate it in the first place. So you can get joints like this:

Using this we can generate a neat palm tree from scratch :

--Given a Part, scale the Positions of all child Attachments by factor.
function scale_attachmets(part, factor)
for _, c in pairs(part:GetChildren()) do
if c:IsA("Attachment") then
c.Position *= factor
end
end
end
--Generate trunk
local n_trunks = 5
local last_trunk = game.Workspace.Palm.Trunk
for n = 1, n_trunks do
local next_part = last_trunk:Clone()
next_part.Size *= 0.9
scale_attachmets(next_part, 0.9)
place_att_on_att_rotated(next_part.BottomAtt, last_trunk.TopAtt, CFrame.Angles(0.1, 0.1, .1))
next_part.Parent = last_trunk.Parent
last_trunk = next_part
end
--Generate leaves
local n_leaves = 6
local n_leaf_parts = 3
local angle_step = math.rad(360)/n_leaves
for angle = 0, math.rad(360) - angle_step, angle_step do
local last_leaf_part = game.Workspace.Leaf:Clone()
place_att_on_att_rotated(last_leaf_part.BaseAtt, last_trunk.TopAtt, CFrame.Angles(0, angle, 0))
last_leaf_part.Parent = last_trunk.Parent
for n = 1, n_leaf_parts - 1 do
local next_leaf_part = last_leaf_part:Clone()
next_leaf_part.Size *= 0.8
scale_attachmets(next_leaf_part, 0.8)
place_att_on_att_rotated(next_leaf_part.BaseAtt, last_leaf_part.TipAtt, CFrame.Angles(0, 0, .2))
next_leaf_part.Parent = last_leaf_part.Parent
last_leaf_part = next_leaf_part
end
end

And the place file so you can see how everything is set up: Palm Gen.rbxl (23.9 KB)

Wrap up

Itâ€™s not exactly what you asked, and actually randomizing the angles on an existing tree is a bit harder than generating one from scratch. At least something like a palm tree, because the bottom part gets generated first, then the next, then the next. If you randomize one joint in an existing tree, all the other parts that may be attached would also have to be moved accordingly, using something called forward kinematics. Let me know if you really want to know about that.

Hope this was helpful! Ask away if you have any questions

EDIT: I got carried away and made it generate 441 different palm trees in less than a second!

Thanks a lot for the Informative reply. The reason why I made this the solution so lately is because I finally think I understood what happens there. Probably gonna make a resource about this soon!

So I started messing with your place, but I stumbled across a problem. When there is a function call and you use CFrame.Angles I tried to put math.random(-0.1, 0.1) instead of having just 0.1, but it broke and decided to make a straight trunk. When I tried that with the leaves on line 43 n_leaves = math.random(3, 8) it goes either for 3 or for 8.

I have knowledge only with Vector3.new so an explanation on how that works would be appreciated!