MultiExport-FreeCAD/MultiExport.FCMacro

361 lines
10 KiB
Plaintext
Executable file

import importSVG
import FreeCAD
import FreeCADGui
import Draft
import os
import Mesh
from PySide2.QtWidgets import QWidget, QLabel, QSpinBox, QHBoxLayout, QListWidgetItem
from datetime import datetime
class ObjectListItem(QWidget):
def __init__(self, obj):
super().__init__()
self.obj = obj
self.label = QLabel(obj.Label)
self.spinbox = QSpinBox()
self.spinbox.setRange(0, 9999)
self.spinbox.setValue(1)
layout = QHBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.spinbox)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
def get_value(self):
return self.spinbox.value()
class WindowDialog():
def __init__(self):
self.selectedItems = []
self.outputRacine = ""
self.ui_file = os.path.join(FreeCAD.getUserMacroDir(True), 'MultiExport/MultiExport.ui')
self.form = FreeCADGui.PySideUic.loadUi(self.ui_file)
self._connect_widgets()
self._populate_selector_list()
# Connect the selection change signal to keep the list in sync
FreeCADGui.Selection.addObserver(self._on_selection_changed)
self.form.show()
def _connect_widgets(self):
self.form.pushButtonExport.pressed.connect(self.export)
self.form.listWidgetSelector.itemClicked.connect(self._on_list_item_clicked)
def _populate_selector_list(self):
self.form.listWidgetSelector.clear()
FreeCADGui.Selection.clearSelection()
for obj in FreeCAD.ActiveDocument.Objects:
if obj.Label.endswith("-SVG") or obj.Label.endswith("-STL"):
item = QListWidgetItem()
widget = ObjectListItem(obj)
item.setSizeHint(widget.sizeHint())
self.form.listWidgetSelector.addItem(item)
self.form.listWidgetSelector.setItemWidget(item, widget)
# Sync selection: add the object to the selection and select the item in the list
item.setSelected(True)
FreeCADGui.Selection.addSelection(obj)
def _on_list_item_clicked(self, item):
widget = self.form.listWidgetSelector.itemWidget(item)
obj = widget.obj
if obj in FreeCADGui.Selection.getSelection():
FreeCADGui.Selection.removeSelection(obj)
else:
FreeCADGui.Selection.addSelection(obj)
def _on_selection_changed(self, doc, objects):
# Clear current selection in the list
for index in range(self.form.listWidgetSelector.count()):
item = self.form.listWidgetSelector.item(index)
widget = self.form.listWidgetSelector.itemWidget(item)
item.setSelected(False) # Deselect in the list
# Sync the list selection with FreeCAD's current selection
for obj in FreeCADGui.Selection.getSelection():
for index in range(self.form.listWidgetSelector.count()):
item = self.form.listWidgetSelector.item(index)
widget = self.form.listWidgetSelector.itemWidget(item)
if widget.obj == obj:
item.setSelected(True) # Select the corresponding item in the list
break
def _createFolder(self):
date = datetime.today().strftime('%Y-%m-%d')
time = datetime.today().strftime('%H:%M:%S')
folderPath = f"{FreeCAD.activeDocument().getFileName().rpartition('.')[0]}-{date}-Exports/"
if not os.path.exists(folderPath):
os.makedirs(folderPath)
folderPath += f"{time}/"
if not os.path.exists(folderPath):
os.makedirs(folderPath)
return folderPath
# -------- MODIFICATION DE LA MACRO "exportSketchEnMasse-SVG" de Gauthier Brière -------- #
def _exportSketchBasic(self, sketchList):
for __O__ in sketchList:
__obj__ = []
__obj__.append(__O__)
print('Export SVG de : ' + __obj__[0].Label)
fichierSVG = u"" + self.outputRacine + __obj__[0].Label + '-' + '.svg'
importSVG.export(__obj__, fichierSVG)
FreeCAD.ActiveDocument.removeObject(__O__.Label)
FreeCAD.activeDocument().recompute()
def _exportSketchCalepinage(self, sketchList, plateLength, plateWidth, spacing):
copy_sketchList = sketchList[:]
__obj__ = []
currentX = 0.0
currentY = 0.0
isSVGFull = False
YBlockCounter = 0
SVGNameCounter = 1
maxY = 0.0
index = 0
listSize = len(copy_sketchList)
errorCount = 0
while listSize > 0:
while index < listSize:
__O__ = copy_sketchList[index]
boundingBox = __O__.Shape.BoundBox
if (boundingBox.XLength > plateLength) or (boundingBox.YLength > plateWidth):
copy_sketchList.pop(index)
listSize = len(copy_sketchList)
errorCount += 1
FreeCAD.ActiveDocument.removeObject(__O__.Label)
FreeCAD.activeDocument().recompute()
continue
if boundingBox.XLength + currentX > plateLength:
if boundingBox.YLength + currentY > plateWidth:
YBlockCounter += 1
if YBlockCounter >= listSize:
isSVGFull = True
break
index += 1
continue
if boundingBox.YLength + currentY > plateWidth:
YBlockCounter += 1
if YBlockCounter >= listSize:
isSVGFull = True
break
index += 1
continue
__obj__.insert(0, __O__)
maxY = boundingBox.YLength if boundingBox.YLength > maxY else maxY
__obj__[0].Placement=FreeCAD.Placement(FreeCAD.Vector(0,0,0), FreeCAD.Rotation(FreeCAD.Vector(1,0,0),0), FreeCAD.Vector(0,0,0))
if currentX == 0.0:
__obj__[0].Placement.Base = FreeCAD.Vector(-boundingBox.XMin + currentX, -boundingBox.YMin + currentY, 0.0)
currentX += boundingBox.XLength
else:
__obj__[0].Placement.Base = FreeCAD.Vector(-boundingBox.XMin + currentX + spacing, -boundingBox.YMin + currentY, 0.0)
currentX += boundingBox.XLength + spacing
copy_sketchList.pop(index)
listSize = len(copy_sketchList)
FreeCAD.activeDocument().recompute()
index = 0
YBlockCounter = 0
currentX = 0.0
currentY += maxY + spacing
maxY = 0.0
index = 0
listSize = len(copy_sketchList)
FreeCAD.activeDocument().recompute()
if listSize <= 0 or isSVGFull:
fichierSVG = u"" + self.outputRacine + 'CALEPINAGE-' + str(SVGNameCounter) + '-' + '.svg'
print('Export SVG de : ' + u"" + str(os.path.basename(self.outputRacine)) + 'CALEPINAGE-' + str(SVGNameCounter) + '-' + '.svg')
importSVG.export(__obj__, fichierSVG)
for __i__ in __obj__:
FreeCAD.ActiveDocument.removeObject(__i__.Label)
FreeCAD.activeDocument().recompute()
__obj__.clear()
maxY = 0.0
index = 0
currentX = 0.0
currentY = 0.0
isSVGFull = False
SVGNameCounter += 1
YBlockCounter = 0
listSize = len(copy_sketchList)
if errorCount == 1:
print(f"ERREUR : {errorCount} sketch ne rentre pas sur le plateau")
elif errorCount > 1:
print(f"ERREUR : {errorCount} sketchs ne rentrent pas sur le plateau")
# -------- END -------- #
def getLargestFaceDir(self, obj):
if not hasattr(obj, "Shape") or not obj.Shape.Faces:
return None
maxArea = 0.0
largestFace = None
for face in obj.Shape.Faces:
area = face.Area
if area > maxArea:
maxArea = area
largestFace = face
if largestFace:
# Get normal at center of face (parametric center)
u, v = largestFace.Surface.parameter(largestFace.CenterOfMass)
normal = largestFace.normalAt(u, v)
return normal.normalize()
return None
# def searchDir(self, obj, xres, yres, zres):
# x, y, z = xres, yres, zres
# if hasattr(obj, "Dir"):
# if obj.Dir.x != 0.0:
# x = obj.Dir.x
# if obj.Dir.y != 0.0:
# y = obj.Dir.y
# if obj.Dir.z != 0.0:
# z = obj.Dir.z
# if not hasattr(obj, "Objects"):
# return FreeCAD.Vector(x, y, z)
# else:
# res = FreeCAD.Vector(x, y, z)
# for i in obj.Objects:
# res = self.searchDir(i, x, y, z)
# return res
# elif hasattr(obj, "Objects"):
# res = FreeCAD.Vector(x, y, z)
# for i in obj.Objects:
# res = self.searchDir(i, x, y, z)
# return res
def exportSVG(self, isCalepinage):
plateLength = self.form.doubleSpinBoxLength.value()
plateWidth = self.form.doubleSpinBoxWidth.value()
spacing = self.form.doubleSpinBoxSpacing.value()
sketchList = []
for obj in FreeCADGui.Selection.getSelection():
if not obj.Label.endswith("-SVG"):
continue
count = 1 # default if not using spinboxes (or fetch from list if desired)
# Optional: match count from list
for index in range(self.form.listWidgetSelector.count()):
item = self.form.listWidgetSelector.item(index)
widget = self.form.listWidgetSelector.itemWidget(item)
if widget.obj == obj:
count = widget.get_value()
break
for _ in range(count):
if hasattr(obj, "Dir"):
vector = FreeCAD.Vector(obj.Dir.x, obj.Dir.y, obj.Dir.z)
else:
vector = self.getLargestFaceDir(obj)
# vector = self.searchDir(obj, 0, 0, 0)
sv0 = Draft.make_shape2dview(obj, vector)
FreeCAD.ActiveDocument.recompute()
sk = Draft.make_sketch(sv0, autoconstraints=True)
sk.ViewObject.LineColor = (1.0, 0.0, 0.0)
FreeCAD.ActiveDocument.recompute()
sketchList.append(sk)
if hasattr(sv0, 'Name'):
FreeCAD.ActiveDocument.removeObject(sv0.Name)
if isCalepinage:
self._exportSketchCalepinage(sketchList, plateLength, plateWidth, spacing)
else:
self._exportSketchBasic(sketchList)
self._populate_selector_list()
def exportSTL(self, singleMode):
objects_to_export = []
for obj in FreeCADGui.Selection.getSelection():
if not obj.Label.endswith("-STL"):
continue
count = 1
for index in range(self.form.listWidgetSelector.count()):
item = self.form.listWidgetSelector.item(index)
widget = self.form.listWidgetSelector.itemWidget(item)
if widget.obj == obj:
count = widget.get_value()
break
for i in range(count):
clone = Draft.clone(obj)
objects_to_export.append((clone, obj.Label, i + 1))
FreeCAD.ActiveDocument.recompute()
if not singleMode:
for obj, label, i in objects_to_export:
filename = os.path.join(self.outputRacine, f"{label}_{i}.stl")
Mesh.export([obj], filename)
print(f"Exported STL: {filename}")
FreeCAD.ActiveDocument.removeObject(obj.Name)
else:
export_objs = [obj for obj, _, _ in objects_to_export]
if export_objs:
label = FreeCAD.activeDocument().Name
filename = os.path.join(self.outputRacine, f"{label}.stl")
Mesh.export(export_objs, filename)
print(f"Exported merged STL: {filename}")
for obj in export_objs:
FreeCAD.ActiveDocument.removeObject(obj.Name)
FreeCAD.ActiveDocument.recompute()
self._populate_selector_list()
def export(self):
isCalepinage = self.form.checkBoxCalepinage.isChecked()
isCombine = self.form.checkBoxCombine.isChecked()
self.outputRacine = self._createFolder()
self.exportSVG(isCalepinage)
self.exportSTL(isCombine)
if __name__ == '__main__':
try:
mainWindow = WindowDialog()
except Exception as e:
print(e)