(2024-10-11, 8:37)Jaco van der Molen Wrote: Oops, my bad. I work on more PC's / Laptops and noticed I used 1.6d
In 1.7b1 works fine and got some nice results too.
To enhance this more, perhaps we can take a look at parts that need to rotate 90 degrees like you would knolling in real life with physical bricks, like wheels and tyres.
I tested with a model of a car ;-)
And what I would love is a sort by part and color!
Now it is "just" the order in which the parts are hierarchical in the LDR file.
I must say it is very nice to see this fun "feature" is picked up so fast!
New version, now with sorting by size, then part, then color.
Still needs to make the algorithm rotate the parts so it's lowest profile. So lay down antennas and so on. And also pack it more densly. But it's getting better. Also have some overlaps still
Code:
-- Setting to determine layout direction ('width' or 'depth')
local layoutDirection = 'depth' -- Change this to 'width' to layout by width
-- Function to get the X, Y (min), and Z dimensions of a subfile using its bounding box
function getSubfileDimensions(subfile)
local minVec = subfile:getBoundingBoxMin()
local maxVec = subfile:getBoundingBoxMax()
return maxVec:getX() - minVec:getX(), minVec:getY(), maxVec:getZ() - minVec:getZ() -- Return width, minY, and depth
end
-- Function to sort subfiles by their combined XZ size,
-- then by name, and finally by color
function sortSubfiles(a, b)
-- Get dimensions and calculate size
local widthA, _, depthA = getSubfileDimensions(a:getSubfile())
local widthB, _, depthB = getSubfileDimensions(b:getSubfile())
local sizeA = widthA * depthA
local sizeB = widthB * depthB
-- Compare by size first
if sizeA ~= sizeB then
return sizeA < sizeB
end
-- Compare by name if sizes are equal
local nameA = a:getSubfile():getFileName()
local nameB = b:getSubfile():getFileName()
if nameA ~= nameB then
return nameA < nameB
end
-- Compare by color if names are equal
local colorA = a:getColor()
local colorB = b:getColor()
return colorA < colorB
end
-- Function to reset the rotation of a part to identity
function resetPartRotation(part)
local identityMatrix = ldc.matrix()
identityMatrix:setIdentity()
part:setOri(identityMatrix)
end
-- Function to check if a part is a submodel and print its name if it is
function checkAndPrintSubmodel(part)
local subfile = part:getSubfile()
if subfile:isModel() and subfile:getRefCount() > 0 then
-- print("Submodel name: " .. subfile:getFileName())
-- Do stuff here to inline the submodels
end
end
-- Function to layout parts densely based on the layout direction
function knollingLayout()
local ses = ldc.session()
if not ses:isLinked() then
ldc.dialog.runMessage('No active model.')
return
end
local sel = ses:getSelection()
local cnt = sel:getRefCount()
if cnt < 2 then
ldc.dialog.runMessage('At least two items should be selected.')
return
end
local subfiles = {}
-- Collect all subfiles (Parts in the selection)
for i = 1, cnt do
local part = sel:getRef(i)
table.insert(subfiles, part)
checkAndPrintSubmodel(part) -- Check and print submodel name if applicable
end
-- Sort subfiles by size, name, and color
table.sort(subfiles, sortSubfiles)
-- Determine spacing and maximum row/cell size
local spacing = 20
local maxRowSize = 2000 -- Maximum size for a row or column
-- Keep track of the current row's size and maximum dimension height
local currentRowSize = 0
local currentRowMaxDim = 0
local mainOffset = 0
local secondaryOffset = 0
for _, part in ipairs(subfiles) do
local subfile = part:getSubfile()
local width, minY, depth = getSubfileDimensions(subfile)
local primarySize, secondarySize
if layoutDirection == 'width' then
primarySize = width
secondarySize = depth
else
primarySize = depth
secondarySize = width
end
-- Check if adding this part exceeds the current row's size
if currentRowSize + primarySize + spacing > maxRowSize then
-- Move to the next row or column
mainOffset = 0
secondaryOffset = secondaryOffset + currentRowMaxDim + spacing
currentRowSize = 0
currentRowMaxDim = 0
end
-- Reset the part's rotation
resetPartRotation(part)
-- Set the part's position
local pos
if layoutDirection == 'width' then
pos = ldc.vector(mainOffset, minY, secondaryOffset)
else
pos = ldc.vector(secondaryOffset, minY, mainOffset)
end
part:setPos(pos)
-- Update offsets for the next item in the current row or column
mainOffset = mainOffset + primarySize + spacing
currentRowSize = currentRowSize + primarySize + spacing
currentRowMaxDim = math.max(currentRowMaxDim, secondarySize + spacing)
end
-- Pack layout more densely
packLayout(subfiles)
end
-- Function to pack the layout more densely
function packLayout(subfiles)
local spacing = 20
local maxRowSize = 2000
local currentRowSize = 0
local currentRowMaxDim = 0
local mainOffset = 0
local secondaryOffset = 0
for _, part in ipairs(subfiles) do
local subfile = part:getSubfile()
local width, minY, depth = getSubfileDimensions(subfile)
local primarySize, secondarySize
if layoutDirection == 'width' then
primarySize = width
secondarySize = depth
else
primarySize = depth
secondarySize = width
end
-- Check if adding this part exceeds the current row's size
if currentRowSize + primarySize + spacing > maxRowSize then
-- Move to the next row or column
mainOffset = 0
secondaryOffset = secondaryOffset + currentRowMaxDim + spacing
currentRowSize = 0
currentRowMaxDim = 0
end
-- Set the part's new packed position
local pos
if layoutDirection == 'width' then
pos = ldc.vector(mainOffset, minY, secondaryOffset)
else
pos = ldc.vector(secondaryOffset, minY, mainOffset)
end
part:setPos(pos)
-- Update offsets for the next item in the current row or column
mainOffset = mainOffset + primarySize + spacing
currentRowSize = currentRowSize + primarySize + spacing
currentRowMaxDim = math.max(currentRowMaxDim, secondarySize + spacing)
end
end
-- Register the macro for the knolling layout
function register()
local macro_lo = ldc.macro('Perform knolling layout')
macro_lo:setHint('Arrange elements densely with even spacing in the XY plane with Z = 0.')
macro_lo:setEvent('run', 'knollingLayout')
end
register()