Script48_TrueShapePack.lua
-- Lua example for Autodesk Netfabb 2025.0
-- Demonstrates UI and packing using True Shape
function RotationTypeChange()
local Item = RotationType.selecteditem
RotationStepX.enabled = Item == 0
RotationStepY.enabled = Item == 0
RotationStepZ.enabled = Item == 0
RotationMultiAxis.enabled = Item == 0
RotationList.enabled = Item == 1
end
function ComponentPlacementChanged()
InterlockCheck.enabled = AdvancedSettings.checked
CompPlacement.enabled = AdvancedSettings.checked
Priorities.enabled = AdvancedSettings.checked
Axis.enabled = AdvancedSettings.checked and CompPlacement.selecteditem == 0
SweetSpotX.enabled = AdvancedSettings.checked and CompPlacement.selecteditem == 1
SweetSpotY.enabled = AdvancedSettings.checked and CompPlacement.selecteditem == 1
end
function CloneParent(Parent, Target, Count)
local SourceMeshes = {}
for Index = 0, Parent.meshcount - 1 do
table.insert(SourceMeshes, Parent:getmesh(Index))
end
for k, v in pairs(SourceMeshes) do
for Clone = 1, Count do
if not v.lockedposition then
local Mesh = Target:addmesh(v.mesh)
Mesh.name = v.name .. "_" .. tostring(Clone)
Mesh:setmatrix(v.matrix)
end
end
end
for Index = 0, Parent.groupcount - 1 do
for Clone = 1, Count do
local Group = Parent:getsubgroup(Index)
local NewGroup = Target:addsubgroup(Group.name .. "_" .. tostring(Clone))
NewGroup.locked = Group.locked
CloneParent(Group, NewGroup, 1)
end
end
end
-- Move the parts of the group outside the platform,
-- otherwise parts which cannot be packed are not moved at all
function MovePartsOutsidePlatform(Group)
for Index = 0, Group.meshcount - 1 do
local Mesh = Group:getmesh(Index)
if not Mesh.lockedposition then
local BoundingBox = Mesh.outbox
Mesh:translate(-(BoundingBox.maxx + BoundingBox.sizex), -(BoundingBox.maxy + BoundingBox.sizey), 0)
end
end
for Index = 0, Group.groupcount - 1 do
local SubGroup = Group:getsubgroup(Index)
local BoundingBox = Group.outbox
SubGroup:translate(-(BoundingBox.maxx + BoundingBox.sizex), -(BoundingBox.maxy + BoundingBox.sizey), 0)
system:messagedlg(
"Move group: " ..
tostring(-(BoundingBox.maxx + BoundingBox.sizex)) ..
", " .. tostring(-(BoundingBox.maxy + BoundingBox.sizey))
)
end
end
function Change2dPacking()
UseShadow.enabled = TwoDPacking.checked
Axis:clear()
Axis:additem("Positive X axis", 0, 0, false)
Axis:additem("Positive Y axis", 1, 1, false)
if not TwoDPacking.checked then
Axis:additem("Positive Z Axis", 2, 2, false)
end
Axis:additem("Negative X Axis", 3, 3, false)
Axis:additem("Negative Y Axis", 4, 4, false)
if not TwoDPacking.checked then
Axis:additem("Negative Z Axis", 5, 5, false)
end
Axis:updateitems()
end
function BtnPack()
Dialog:close(true)
end
function BtnClose()
Dialog:close(false)
end
system:setloggingtooglwindow(true)
CaptionWidth = 180
Dialog = application:createdialog()
Dialog.caption = "Lua automation demo for True Shape packing"
Dialog.translatecaption = false
Dialog.width = 360
Group = Dialog:addgroupbox()
Group.caption = "Settings"
Group.translate = false
TwoDPacking = Group:addcheckbox()
TwoDPacking.caption = "2d packing"
TwoDPacking.translate = false
TwoDPacking.onclick = "Change2dPacking"
UseShadow = Group:addcheckbox()
UseShadow.caption = "Avoid packing parts in the shadow of other parts"
UseShadow.translate = false
UseShadow.enabled = false
PackingOrder = Group:adddropdown()
PackingOrder.caption = "Packing order"
PackingOrder.captionwidth = CaptionWidth
PackingOrder.translatecaption = false
PackingOrder.translate = false
PackingOrder:additem("Part volume or priority", 0, 0, false)
PackingOrder:additem("Project tree", 1, 1, false)
VoxelSize = Group:addedit()
VoxelSize.caption = "Voxel size"
VoxelSize.translate = false
VoxelSize.captionwidth = CaptionWidth
VoxelSize.text = "4.0"
VoxelSize.numbersonly = true
PartCopies = Group:addedit()
PartCopies.caption = "Global part duplicates"
PartCopies.translate = false
PartCopies.captionwidth = CaptionWidth
PartCopies.text = "1"
PartCopies.numbersonly = true
PartDistance = Group:addedit()
PartDistance.caption = "Minimum part distance (mm)"
PartDistance.translate = false
PartDistance.captionwidth = CaptionWidth
PartDistance.text = "2.0"
PartDistance.numbersonly = true
DistanceXY = Group:addedit()
DistanceXY.caption = "Distance to side walls (XY, mm)"
DistanceXY.translate = false
DistanceXY.captionwidth = CaptionWidth
DistanceXY.text = "2.0"
DistanceXY.numbersonly = true
DistancePlatform = Group:addedit()
DistancePlatform.caption = "Distance to platform (mm)"
DistancePlatform.translate = false
DistancePlatform.captionwidth = CaptionWidth
DistancePlatform.text = "2.0"
DistancePlatform.numbersonly = true
DistanceCeiling = Group:addedit()
DistanceCeiling.caption = "Distance to ceiling (mm)"
DistanceCeiling.translate = false
DistanceCeiling.captionwidth = CaptionWidth
DistanceCeiling.text = "2.0"
DistanceCeiling.numbersonly = true
RotationType = Group:adddropdown()
RotationType.caption = "Rotation type"
RotationType.captionwidth = CaptionWidth
RotationType.translatecaption = false
RotationType.translate = false
RotationType.onchange = "RotationTypeChange"
RotationType:additem("Rotation steppings", 0, 0, false)
RotationType:additem("Rotations list", 1, 1, false)
RotationStepX = Group:adddropdown()
RotationStepX.caption = "X rotation"
RotationStepX.captionwidth = CaptionWidth
RotationStepX.translatecaption = false
RotationStepX.translate = false
RotationStepX:additem("0.0", 0, 0, false)
RotationStepX:additem("180.0", 1, 1, false)
RotationStepX:additem("90.0", 2, 2, false)
RotationStepX:additem("60.0", 3, 3, false)
RotationStepX:additem("45.0", 4, 4, false)
RotationStepX:additem("36.0", 5, 5, false)
RotationStepX:additem("30.0", 6, 6, false)
RotationStepX:additem("20.0", 7, 7, false)
RotationStepY = Group:adddropdown()
RotationStepY.caption = "Y rotation"
RotationStepY.captionwidth = CaptionWidth
RotationStepY.translatecaption = false
RotationStepY.translate = false
RotationStepY:additem("0.0", 0, 0, false)
RotationStepY:additem("180.0", 1, 1, false)
RotationStepY:additem("90.0", 2, 2, false)
RotationStepY:additem("60.0", 3, 3, false)
RotationStepY:additem("45.0", 4, 4, false)
RotationStepY:additem("36.0", 5, 5, false)
RotationStepY:additem("30.0", 6, 6, false)
RotationStepY:additem("20.0", 7, 7, false)
RotationStepZ = Group:adddropdown()
RotationStepZ.caption = "Z rotation"
RotationStepZ.captionwidth = CaptionWidth
RotationStepZ.translatecaption = false
RotationStepZ.translate = false
RotationStepZ:additem("0.0", 0, 0, false)
RotationStepZ:additem("180.0", 1, 1, false)
RotationStepZ:additem("90.0", 2, 2, false)
RotationStepZ:additem("60.0", 3, 3, false)
RotationStepZ:additem("45.0", 4, 4, false)
RotationStepZ:additem("36.0", 5, 5, false)
RotationStepZ:additem("30.0", 6, 6, false)
RotationStepZ:additem("20.0", 7, 7, false)
RotationStepZ.selecteditem = 2
RotationMultiAxis = Group:addcheckbox()
RotationMultiAxis.caption = "Include mulit-axis variants"
RotationMultiAxis.translate = false
RotationList = Group:addedit()
RotationList.caption = "Rotations list (X;Y;Z)"
RotationList.translate = false
RotationList.captionwidth = CaptionWidth
RotationList.text = "(0;0;0)(0;0;90)(0;0;180)(0;0;270)"
RotationList.enabled = false
AdvancedSettings = Group:addcheckbox()
AdvancedSettings.caption = "Advanced packing settings"
AdvancedSettings.translate = false
AdvancedSettings.checked = false
AdvancedSettings.onclick = "ComponentPlacementChanged"
InterlockCheck = Group:addcheckbox()
InterlockCheck.caption = "Interlock check"
InterlockCheck.translate = false
InterlockCheck.checked = true
CompPlacement = Group:adddropdown()
CompPlacement.caption = "Part placement"
CompPlacement.captionwidth = CaptionWidth
CompPlacement.translatecaption = false
CompPlacement.translate = false
CompPlacement.onchange = "ComponentPlacementChanged"
CompPlacement:additem("Along axis", 0, 0, false)
CompPlacement:additem("Sweet spot", 1, 1, false)
CompPlacement:additem("Center bias", 2, 2, false)
Axis = Group:adddropdown()
Axis.caption = "Direction"
Axis.captionwidth = CaptionWidth
Axis.translatecaption = false
Axis.translate = false
Axis:additem("Positive X Axis", 0, 0, false)
Axis:additem("Positive Y Axis", 1, 1, false)
Axis:additem("Positive Z Axis", 2, 2, false)
Axis:additem("Negative X Axis", 3, 3, false)
Axis:additem("Negative Y Axis", 4, 4, false)
Axis:additem("Negative Z Axis", 5, 5, false)
SweetSpotX = Group:addedit()
SweetSpotX.caption = "Sweet spot X position"
SweetSpotX.translate = false
SweetSpotX.captionwidth = CaptionWidth
SweetSpotX.text = "0.0"
SweetSpotX.numbersonly = true
SweetSpotY = Group:addedit()
SweetSpotY.caption = "Sweet spot Y position"
SweetSpotY.translate = false
SweetSpotY.captionwidth = CaptionWidth
SweetSpotY.text = "0.0"
SweetSpotY.numbersonly = true
Priorities = Group:adddropdown()
Priorities.caption = "Placement priorities"
Priorities.captionwidth = CaptionWidth
Priorities.translatecaption = false
Priorities.translate = false
Priorities:additem("Example 1", 0, 0, false)
Priorities:additem("Example 2", 1, 1, false)
Priorities:additem("Example 3", 2, 2, false)
Priorities:additem("Example 4", 3, 3, false)
Priorities:additem("All Options", 4, 4, false)
Splitter = Dialog:addsplitter()
Splitter:settoleft()
ButtonPack = Splitter:addbutton()
ButtonPack.caption = "Pack"
ButtonPack.translate = false
ButtonPack.onclick = "BtnPack"
Splitter:settoright()
ButtonClose = Splitter:addbutton()
ButtonClose.caption = "Close"
ButtonClose.translate = false
ButtonClose.onclick = "BtnClose"
ComponentPlacementChanged()
if Dialog:show() == true then
local Copies = tonumber(PartCopies.text)
if Copies > 1 then
CloneParent(tray.root, tray.root, Copies - 1)
end
local Done = false
local Tray = tray
repeat
MovePartsOutsidePlatform(Tray.root)
local Packer = Tray:createpacker(Tray.packingid_trueshape)
Packer.showprogress = true
Packer.packing_2d = TwoDPacking.checked
Packer.packing_use_shadow_2d = UseShadow.checked
Packer.rotation_use_compound = RotationMultiAxis.checked
Packer.rotation_use_list = RotationType.selecteditem == 1
Packer.borderspacingxy = tonumber(DistanceXY.text)
Packer.borderspacingz = tonumber(DistancePlatform.text)
Packer.minimaldistance = tonumber(PartDistance.text)
Packer.voxel_size = tonumber(VoxelSize.text)
Packer.rotation_x = tonumber(RotationStepX:getitemtext(RotationStepX.selecteditem))
Packer.rotation_y = tonumber(RotationStepY:getitemtext(RotationStepY.selecteditem))
Packer.rotation_z = tonumber(RotationStepZ:getitemtext(RotationStepZ.selecteditem))
Packer.rotation_list = RotationList.text
-- If wanted we apply the advanced settings
if AdvancedSettings.checked == true then
Packer.avoid_interlocking = InterlockCheck.checked
local Placement = CompPlacement.selecteditem
if Placement == 0 then
Packer.part_placement = Packer.place_alongaxis
if TwoDPacking.checked then
-- For 2D packing only two values are passed to "setdirectionaxis"
-- using packing constants "axis_[positive|negative]_[x|y|z]
if Axis.selecteditem == 0 then
Packer:setdirectionaxis(Packer.axis_positive_x, Packer.axis_positive_y)
elseif Axis.selecteditem == 1 then
Packer:setdirectionaxis(Packer.axis_positive_y, Packer.axis_positive_x)
elseif Axis.selecteditem == 3 then
Packer:setdirectionaxis(Packer.axis_negative_x, Packer.axis_negative_y)
elseif Axis.selecteditem == 4 then
Packer:setdirectionaxis(Packer.axis_negative_y, Packer.axis_negative_x)
end
else
-- Setting the axes for 3d packing needs three values passed
-- to the "setdirectionaxis" method, using the packing constants
-- "axis_[positive|negative]_[x|y|z]
if Axis.selecteditem == 0 then
Packer:setdirectionaxis(Packer.axis_positive_x, Packer.axis_positive_y, Packer.axis_positive_z)
elseif Axis.selecteditem == 1 then
Packer:setdirectionaxis(Packer.axis_positive_y, Packer.axis_positive_x, Packer.axis_positive_z)
elseif Axis.selecteditem == 2 then
Packer:setdirectionaxis(Packer.axis_positive_z, Packer.axis_positive_x, Packer.axis_positive_y)
elseif Axis.selecteditem == 3 then
Packer:setdirectionaxis(Packer.axis_negative_x, Packer.axis_negative_y, Packer.axis_positive_z)
elseif Axis.selecteditem == 4 then
Packer:setdirectionaxis(Packer.axis_negative_y, Packer.axis_negative_x, Packer.axis_positive_z)
elseif Axis.selecteditem == 5 then
Packer:setdirectionaxis(Packer.axis_negative_z, Packer.axis_negative_x, Packer.axis_negative_y)
end
end
elseif Placement == 1 then
Packer.part_placement = Packer.place_sweetspot
Packer.sweetspot_x = tonumber(SweetSpotX.text)
Packer.sweetspot_y = tonumber(SweetSpotY.text)
elseif Placement == 2 then
-- The "Center bias" option puts the sweet spot
-- at the center of the platform
Packer.part_placement = Packer.place_sweetspot
Packer.sweetspot_x = Tray.machinesize_x / 2.0
Packer.sweetspot_y = Tray.machinesize_x / 2.0
end
-- Here we pass some example values for priority ranking.
-- The meshes are sorted using the first criterion, if no
-- distinction was detected the second is used, and so on.
-- All possible options are used in the four examples. Here
-- we only pass 3 options but all 6 can be used as well.
-- Example 5 shows that
if Priorities.selecteditem == 0 then
Packer:setplacementpriorities(
Packer.minimum_buildbox_volume,
Packer.minimum_build_height,
Packer.maximum_contact_area
)
elseif Priorities.selecteditem == 1 then
Packer:setplacementpriorities(
Packer.minimum_build_height,
Packer.maximum_contact_area,
Packer.maximum_box_overlap
)
elseif Priorities.selecteditem == 2 then
Packer:setplacementpriorities(
Packer.maximum_contact_area,
Packer.maximum_box_overlap,
Packer.minimum_part_box_volume
)
elseif Priorities.selecteditem == 3 then
Packer:setplacementpriorities(
Packer.maximum_box_overlap,
Packer.minimum_part_box_volume,
Packer.minimum_part_height
)
elseif Priorities.selecteditem == 4 then
Packer:setplacementpriorities(
Packer.minimum_buildbox_volume,
Packer.minimum_build_height,
Packer.maximum_contact_area,
Packer.maximum_box_overlap,
Packer.minimum_part_box_volume,
Packer.minimum_part_height
)
end
end
-- set some of the additional parameters if the null packer is used
local errorcode = Packer:pack()
local UnPackedList = {}
local UnPackedCount = 0
local PackedCount = 0
for Index = 0, Tray.root.meshcount - 1 do
local Mesh = Tray.root:getmesh(Index)
if Mesh.packingstate == Packer.isLeftover then
UnPackedCount = UnPackedCount + 1
table.insert(UnPackedList, Mesh)
elseif tray.root:getmesh(Index).packingstate == Packer.isPacked then
PackedCount = PackedCount + 1
end
end
if UnPackedCount == 0 then
system:messagedlg("All parts were packed, packing finished.")
Done = true
else
local Input =
system:yesnodlg(
tostring(UnPackedCount) .. " parts were not packed. Do you want to continue on a new platform?"
)
if Input == 0 then
Done = true
else
local NewTray =
netfabbtrayhandler:addtray(
"NewPlatform_" .. tostring(netfabbtrayhandler.traycount),
Tray.machinesize_x,
Tray.machinesize_y,
Tray.machinesize_z
)
for k, v in pairs(UnPackedList) do
NewTray.root:addmesh(v.mesh)
Tray.root:removemesh(v)
end
Tray = NewTray
end
end
until Done == true
end