Idea for LUA script to create a knolling layout


RE: Idea for LUA script to create a knolling layout
#9
(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  Confused

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()


Attached Files Thumbnail(s)
   
Reply
« Next Oldest | Next Newest »



Messages In This Thread
RE: Idea for LUA script to create a knolling layout - by Fredrik Hareide - 2024-10-11, 9:07

Forum Jump:


Users browsing this thread: 8 Guest(s)