Idea for LUA script to create a knolling layout


RE: Idea for LUA script to create a knolling layout
#22
I would be great to have a function in the api to create a new file in a new session. Until that I think I will just keep the overwriting logic. Here is another version. Getting better and better. Now with some filters to rotate some parts. 

Code:
function getSubfileDimensions(subfile)
    local minVec = subfile:getBoundingBoxMin()
    local maxVec = subfile:getBoundingBoxMax()

    local width = maxVec:getX() - minVec:getX()
    local height = maxVec:getY() - minVec:getY()
    local depth = maxVec:getZ() - minVec:getZ()

    return width, height, depth, minVec:getX(), minVec:getY(), minVec:getZ(), maxVec:getX(), maxVec:getY(),
        maxVec:getZ()
end

function startsWith(str, start)
    return string.sub(str, 1, string.len(start)) == start
end

function startsWithAny(str, substrings)
    for _, substr in ipairs(substrings) do
        if startsWith(str, substr) then
            return true
        end
    end
    return false
end

function containsAllWords(str, words)
    for _, word in ipairs(words) do
        if not string.find(str, word, 1, true) then -- Using plain search
            return false
        end
    end
    return true
end

function rotateToSmallestSideUpIfCircle(width, height, depth, minX, minY, minZ, maxX, maxY, maxZ, newRef)
    local desc = newRef:getSubfile():getDescription()
    local newRefPosOri = ldc.matrix()
    local isRotated = false

    if startsWithAny(desc, {"Wheel", "Tyre"}) or containsAllWords(desc, {"Technic", "Gear", "Tooth"}) or
        containsAllWords(desc, {"Technic", "Wedge", "Belt", "Wheel"}) or startsWith(desc, "Technic Bush") or
        startsWith(desc, "Technic Pulley") then

        newRefPosOri:setRotate(90, ldc.vector(1, 0, 0))
        newRef:setPosOri(newRefPosOri)

        -- Swap depth and height
        width, height, depth = width, depth, height
        minX, minY, minZ, maxX, maxY, maxZ = minX, minZ, minY, maxX, maxZ, maxY
        isRotated = true

    elseif depth > width then

        newRefPosOri:setRotate(90, ldc.vector(0, 1, 0))
        newRef:setPosOri(newRefPosOri)

        -- Swap depth and width
        width, height, depth = depth, height, width
        minX, minY, minZ, maxX, maxY, maxZ = minZ, minY, minX, maxZ, maxY, maxX
        isRotated = true
    end

    return width, height, depth, minX, minY, minZ, maxX, maxY, maxZ, isRotated
end

function sortSubfiles(a, b, threshold)
    local widthA, heightA, depthA, minXA, minYA, minZA, maxXA, maxYA, maxZA = getSubfileDimensions(a.sf)
    local widthB, heightB, depthB, minXB, minYB, minZB, maxXB, maxYB, maxZB = getSubfileDimensions(b.sf)

    -- Rotate to smallest side up
    widthA, heightA, depthA, minXA, minYA, minZA, maxXA, maxYA, maxZA =
        rotateToSmallestSideUpIfCircle(widthA, heightA, depthA, minXA, minYA, minZA, maxXA, maxYA, maxZA, a.ref)
    widthB, depthB, heightB, minXB, minYB, minZB, maxXB, maxYB, maxZB =
        rotateToSmallestSideUpIfCircle(widthB, heightB, depthB, minXB, minYB, minZB, maxXB, maxYB, maxZB, b.ref)

    -- Determine largest sides after rotation
    local largestA = math.max(widthA, heightA, depthA)
    local largestB = math.max(widthB, heightB, depthB)

    if largestA ~= largestB then
        return largestA < largestB
    end

    -- Fallback to comparing by name if largest sides are equal or thresholds are not met
    local nameA = a.sf:getFileName()
    local nameB = b.sf:getFileName()
    if nameA ~= nameB then
        return nameA < nameB
    end

    -- Finally compare by color if names are also equal
    return a.color < b.color
end

function buildFlatList(parentSf, parentPosOri, list)
    local cnt = parentSf:getRefCount()
    for i = 1, cnt do
        local ref = parentSf:getRef(i)
        local sf = ref:getSubfile()
        local absPosOri = ref:getPosOri() * parentPosOri
        if sf:isPart() or sf:isShortCut() then
            table.insert(list, {
                ref = ref,
                color = ref:getColor(),
                sf = sf
            })
        else
            if sf:isGenerated() then
                -- collect/map flex info if needed, for now just skip it.
                -- print('flex: '..sf:getFileName())
            else
                buildFlatList(sf, absPosOri, list)
            end
        end
    end
end

function deleteAllLines(mainSf)
    for i = mainSf:getLineCount(), 1, -1 do
        local line = mainSf:getLine(i)
        line:delete()
    end
end

function zeroOrValue(val1, val2)
    local sum = val1 + val2
    if sum == 0 then
        return 0
    else
        return sum
    end
end

function knollingLayout()
    local ses = ldc.session()
    if not ses:isLinked() then
        ldc.dialog.runMessage('No active model.')
        return
    end

    local maxRowSize = tonumber(ldc.dialog.runInput('Target width in LDU (minimum 40)?', '2000'))
    if maxRowSize == nil or maxRowSize < 40 then
        return
    end

    local layoutDirection = ldc.dialog.runInput('Layout direction (width or depth)', 'depth')
    if layoutDirection == nil or (layoutDirection ~= 'depth' and layoutDirection ~= 'width') then
        return
    end

    local list = {}
    local mainSf = ldc.subfile()
    buildFlatList(mainSf, ldc.matrix(), list)
    local threshold = 50 -- Set this to your desired threshold value
    table.sort(list, function(a, b)
        return sortSubfiles(a, b, threshold)
    end)
    deleteAllLines(mainSf)

    local spacing = 20 -- Adjust spacing if needed
    local currentRowSize = 0
    local currentRowMaxDim = 0
    local mainOffset = 0
    local secondaryOffset = 0
    local newRefs = {}
    local pivotOffsetZ = 0

    for _, item in ipairs(list) do
        local isRotated = false
        local newRef = mainSf:addNewRef(item.sf:getFileName())
        newRef:setColor(item.color)

        local width, height, depth, minX, minY, minZ, maxX, maxY, maxZ = getSubfileDimensions(item.sf)

        -- Rotate to smallest side up if the other two sides are equal and get the new center coordinates
        width, height, depth, minX, minY, minZ, maxX, maxY, maxZ, isRotated =
            rotateToSmallestSideUpIfCircle(width, height, depth, minX, minY, minZ, maxX, maxY, maxZ, newRef)

        local primarySize, secondarySize, primaryMin, secondaryMin
        if layoutDirection == 'width' then
            primarySize = width
            secondarySize = depth
            primaryMin = minX
            secondaryMin = minZ
        else
            primarySize = depth
            secondarySize = width
            primaryMin = minZ
            secondaryMin = minX
        end

        if currentRowSize + primarySize + spacing > maxRowSize then
            mainOffset = 0
            secondaryOffset = secondaryOffset + currentRowMaxDim + spacing
            currentRowSize = 0
            currentRowMaxDim = 0
            lastMax = 0
        end

        local yPos = 0

        if isRotated and maxY ~= 0 then
            yPos = math.min(minY, -maxY)
        else
            yPos = -maxY
        end

        local pos

        if layoutDirection == 'width' then
            pos = ldc.vector(mainOffset - primaryMin + spacing, yPos, secondaryOffset - secondaryMin + spacing)
        else
            pos = ldc.vector(secondaryOffset - secondaryMin + spacing, yPos, mainOffset - primaryMin + spacing)
        end
        newRef:setPos(pos)

        mainOffset = mainOffset + primarySize + spacing
        currentRowSize = currentRowSize + primarySize + spacing
        currentRowMaxDim = math.max(currentRowMaxDim, secondarySize + spacing)
        table.insert(newRefs, newRef)
    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-23, 18:58

Forum Jump:


Users browsing this thread: 1 Guest(s)