C'est surtout que l'argument de QtGui.QDialog.__init__ sert à indiquer où dessiner les contrôles. Dans ton cas, celui d'une boite de dialogue, c'est la fenêtre juste créée qui les reçoit donc il ne faut pas le renseigner. Là tu demandes de dessiner sur la fenêtre principale de FreeCAD.
Macro pour obtenir un stl avec des arrondis parfaits et piloter octoprint
Forum rules
Be nice to others! Read the FreeCAD code of conduct!
Be nice to others! Read the FreeCAD code of conduct!
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Non, le paramètre indique le parent du widget, donc c'est correct. Le problème vient d'ailleurs.flachyjoe wrote: ↑Tue Dec 15, 2020 1:21 pm C'est surtout que l'argument de QtGui.QDialog.__init__ sert à indiquer où dessiner les contrôles. Dans ton cas, celui d'une boite de dialogue, c'est la fenêtre juste créée qui les reçoit donc il ne faut pas le renseigner. Là tu demandes de dessiner sur la fenêtre principale de FreeCAD.
Ce que tu as fait est correct, cela révèle juste un problème sous-jacent (que j'ai aussi levé) sur la façon dont tu gères tes widgets.
Même si tu as l'impression d'empirer les choses, tu avances dans le bon sens.
J'espère que je vais arriver à expliquer clairement :
* Dans ce que tu avais avant, ton dialogue n'avait pas de parent et Qt le considérait donc comme une fenêtre "à part". Du coup il lui applique un fond.
* Avec la nouvelle méthode, la fenêtre principale de FC est parent de ton dialogue. Du coup Qt par défaut met un fond transparent
Comme tu crées tout un paquet de widgets indépendants que tu bouges manuellement sur le canevas, l'espace entre les widgets n'est pas rempli et tu vois en fait le fond.
La bonne façon de régler le problème est d'améliorer ta gestion des widgets.
Aujourd'hui tu as :
Code: Select all
_ Dialogue
\_ Widget1
\_ Widget2
\_ etc...
Code: Select all
_ Dialogue
\_ Widget (un simple QWidget sans rien d'autre) -- ce widget est optionnel car le layout devrait remplir le fond, mais c'est une bonne habitude
\_ QGridLayout
\_ Widget1
\_ Widget2
\_ Widget3
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Pas mal aussi ! Merci !mario52 wrote: ↑Mon Dec 14, 2020 8:44 pm Bonsoir
ici pour mettre un icône dans un bouton et un toolTip (avec image dans un fichier):
ToolTipIcon00.pngCode: Select all
cancelButton.setIcon(QtGui.QIcon("completePath/nomIcone.png")) # sur un bouton cancelButton.setToolTip("Cancel <img src= completePath/nomIcone.png" + " />") # dans un toolTip
mario
Là, ça oblige l'utilisateur qui télécharge la macro à télécharger aussi des fichiers images et de les installer au bon endroit. N' y a t il pas des icônes prédéfinies inclues dans python ou FreeCAD de façon à ne pas avoir cet inconvénient ?
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Tu peux utiliser le format XPM pour créer des icônes intégrés dans le code.
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Merci openBrain pour les remarques !!!
Le nouveau code complet est à la fin de ce message.
Ca s'affiche correctement :
Par contre, mon exe
me retourne toujours 0 quelque soit le bouton appuyé
Fait
Fait
Fait
Fait
Le code actuel :
Le nouveau code complet est à la fin de ce message.
Bon ça, j'ai pas encore attaqué. J'ai cherché, pais pas vraiment trouvé ce qu'il faut faire. A suivre.Comme d'hab, je fais les remarques en vrac. Je vais essayer de mettre les numéros de ligne.
* L29-30 : tu ne dois pas utiliser PySide et PySide2 en même temps. C'est un peu compliqué à expliquer, mais en version simple, PySide permet d'être compatible avec les vieilles versions FreeCAD. Concrètement, dans PySide les widgets sont dans QtGui, alors que dans PySide2 ils sont dans un namespace séparé nommé QtWidgets. Il faut que tu choisisses entre les 2.
Bon ça s'est facile. C'est fait* Comme tu utilises maintenant le GUI, il faudrait ajouter 'import FreeCADGui as Gui' dans les imports
fait
* L134 : c'est important dans Qt de respecter au mieux la parenté. Il faudrait donc que ta fonction init soit '__init__(self, parent=None)', puis la ligne du dessous : 'super(BoiteDialogueSTLGuiClass, self).__init__(parent)'. Ensuite en L457, tu instancies avec 'BoiteDialogueSTLGuiClass(mw)'
Code: Select all
class BoiteDialogueSTLGuiClass(QtGui.QDialog):
def __init__(self, parent=None):
super(BoiteDialogueSTLGuiClass, self).__init__(parent)
self.initUI()
............
J'ai fait cela :* L207-212 : pour les boutons OK/Cancel, on utilise plutôt une QDialogButtonBox qui permet d'afficher facilement les boutons standards de la manière traditionnelle de l'OS (et traduits), et on connecte simplement le signal 'accepted' au slot 'accept' du dialogue, et 'rejected' à 'reject'
* L458-459 : si tu utilises les boutons standard, tu peux utiliser le retour de 'exec_()' pour savoir ce qu'il s'est passé (documentation), ce qui fait que tu as juste à faire 'if not formBoiteDialogueSTL.exec_(): return' et tu n'as pas à gérer toi même la valeur de retour
Code: Select all
cancelButton = QtGui.QDialogButtonBox (self)
cancelButton.clicked.connect(self.onCancel)
# cancelButton.setAutoDefault(True)
cancelButton.setStandardButtons(QtGui.QDialogButtonBox.Cancel)
lay.addWidget(cancelButton, posLigne+1, 1)
# OK button
okButton = QtGui.QDialogButtonBox (self)
okButton.clicked.connect(self.onOk)
okButton.setStandardButtons(QtGui.QDialogButtonBox.Ok)
lay.addWidget(okButton, posLigne+1, 2)
Code: Select all
formBoiteDialogueSTL =BoiteDialogueSTLGuiClass(mw)
print(formBoiteDialogueSTL.exec_())
: fait. Ca marche bien !* Pour ce qui est de l'affichage des widgets, tu devrais passer par les layouts (notamment dans ton cas QGridLayout). Ca permet aux éléments d'être toujours bien placés, et ça t'évite d'avoir à gérer toi-même la taille de la fenêtre ou la position avec les 'move()' partout...
Tout à fait.* L143 : le 'WindowsStayOnTopHint' n'a pas de raison d'être. En associant ton dialogue avec la mainwindow (voir au-dessus) et en utilisant la fonction 'exec_()', ton dialogue sera toujours au-dessus de la fenêtre FreeCAD. Là ce que tu rajoutes fait que ton dialogue sera au-dessus de toutes les fenêtres ouvertes (genre le navigateur, ...) ce qui est plus gênant qu'utile.
Fait
mince, comme j'ai changé le code depuis ta version et je n'avais pas sauvé précisément la version en question, je ne sais pas à quoi ça correspond. Tu pourrais me donner les lignes ? soit les recopier, soit les num de ligne du code complet mies en bas de ce message ?* L147+152+162 : les connexions signaux/slots ne servent pas à grand-chose à part polluer la vue rapport. Je pense que tu peux les supprimer.
Tout à fait d'accord, c'est beaucoup plus propre ainsi.* L156 : Horreur ! La variable 'commandes' est une liste, donc la boucle for s'écrit simplement 'for cmd in commandes' et ensuite tu utilises le pointeur'cmd' pour récupérer les éléments
* L416 : Rehorreur ! voir plus haut
Fait
trés bien le QDoubleSpinbox* L182 : un QLineEdit n'est pas forcément le mieux pour récupérer un float. Il faut à minima lui mettre un Validator (voir ce lien ou un inputMask (comme tu as fait)), mais moi j'utiliserais plutôt un QDoubleSpinbox
Fait
Fait* L200 : ne pas mettre 'self.show()' dans le créateur (sauf raison bien particulière). De toute façon tu utilises 'exec_()' plus loin, donc ça affichera ton dialogue
Fait* L468 : comme ta macro gère tous les types d'objet, tu devrais remplacer 'body' par 'solide'
C'est une bétise : je l'avais mis pour débugger* L470 : je ne vois pas à quoi sert cette messagebox
Fait
Le code actuel :
Code: Select all
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Macro qui permet de créer un fichier "stl" avec des arrondis parfaits
= sans facettes visibles.
Elle permet aussi de lancer des programmes de votre choix
Par exemple pour automatiser la cheine FreeCAD -> Slicer -> impression
Principe du lissage : elle modifie la propriété deviation des solide avant
génération du stl puis replace les anciennes valeures
A la fin, elle propose de lancer le fichier stl qui s'ouvrira par exemple sous
cura si l'extension stl a été associé à cura dans votre système d'exploitation.
Lancement d'autres programmes ou commande :
Vous pouvez lui demander tout programme ou commande que vous pourriez taper
dans un terminal. Pour cela, vous devez modifier ce qu'il y a dans la section
"paramètres pouvant être changés".
Exemples d'applications :
- Allumer l'imprimante et la lumière (nécessite par exemple une prise commandée)
- Connecter octoprint à l'imprimante
- Lancer la préchauffe du plateau
- Sauvegarder votre fichier FreeCAD et stl
....
Fonctionne sous windows. Testé ausi sous Linux
"""
import FreeCAD as App
import Mesh
from PySide2 import QtWidgets
from PySide import QtGui, QtCore
import os, sys, subprocess
import time
#================================
# Paramètres pouvant être changés
#================================
# Gestion du lissage
#--------------------
# deviation : valeur par défaut qui s'affichera dans la boite de dialogue.
# 0.5 par défaut dans FreeCad, 0.05 permet un lissage plus fort.
# 0.01 est parfait du point de vue lissage.
# plus la valeur est faible et plus la qualité est bonne,
# mais plus la taille du fichier stl est grande
# la valeur doit être comprise entre 0 et 1
deviation=0.01
doitLancerFichier=True # si True, il sera proposer de lancer le fichier stl
delaiMax=10 # si l'utilisateur entre un délai plus grand, il est réduit à delaiMax
# lancement automatique de programmes,
# Automatisation de la chaine de production
#------------------------------------------
#Pour lancer des programmes de votre choix
#Typiquement par exemple un programme domotique,
#allumer votre imprimante ou/et une lumière.
#Utilisez la syntaxe suivante en ajoutant une ligne dans commandes=....:
# [ ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',temps_attente ],
#
# 'commande à exécuter' : la commande que vous voulez exécuter.
# 'parame 1, param 2,...' : si votre programme a besoin de paramètres.
# Ces paramètres ne serons pris en compte que si extention=''
#
# 'extention': si non vide alors le premier paramètre sera remplacé par le nom de
# votre fichier FreeCAD mais avec cette extension :
# 'question à poser' : la question à afficher dans la boite de dialogue
# temps_attente : facuktatif : nb de seconde à attendre après exécution
# permet par exemple d'attendre que l'imprimante ait démarré avant de lui
# demander de chauffer le plateau
# A noter les doubles \ en remplacement de \ (car les caractères spéciaux
# doivent être précédés d'un \ en python). idem pour par exemple l'apostrophe
# les lignes
#[ ['commande à exécuter','param 1','param 2' ] ,'.extension','Question à poser' ],
# doivent être insérées plus bas après commande=[
#
# Exemples de lancements possibles :
"""
[ ['calc.exe' ] ,'' ,'Lancer la calculatrice ?'],
[ ['C:\\WINDOWS\\system32\\mspaint.exe','dessin.jpg' ] ,'' ,'Lancer Paint ?' ],
[ ['C:\\Program Files\\Ultimaker Cura 4.8.0\\Cura.exe'] ,'.stl','Lancer Cura ?' ],
[ ['curl','http://pidomotique/connecte.php' ] , '' ,'Connecter l\'imprimante ?'],
[ ['C:\\Program Files\\Mozilla Firefox\\firefox.exe','https://octopi.local'],'','Lancer Octoprint ?'],
pour linux:
[ [ 'firefox','https://octopi.local' ] , '' ,'Lancer Octoprint ?' ],
Exemple de pilotage de l'imprimante via octoprint (si vous l'avez...) :
mise en température du plateau
- Remplacez les XXX se trouvant après X-Api-Key: par votre clef API
(que l'on trouve dans les paramètres d'octoprint)
- Remplacez http://octopi.local par l'url avec laquelle vous accédez à octoprint
- Modifiez la valeur 050 dans "M140 S050" : S050 pour chauffer à 50° si vous souhaitez 60° : S060
- 6 lignes à recopier :
[
[ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: XXXXXXXXXXXXXXXXXXXXX', '-X', 'POST',
'-d {"command":"M140 S050"}','http://octopi.local/api/printer/command'
]
, '','Chauffer le plateau de l\'imprimante ?'
],
"""
commandes=[
# insérez ici vos lignes.
[ ['calc.exe' ] ,'' ,'Lancer la calculatrice ?',2],
[ ['C:\\Program Files\\Ultimaker Cura 4.8.0\\Cura.exe'] ,'.stl','Lancer Cura ?' ],
[ ['curl','http://pidomotique/connecte.php' ] , '' ,'Allumer et connecter l\'imprimante ?',4],
[ ['C:\\Program Files\\Mozilla Firefox\\firefox.exe','https://octopi.local'],'','Lancer Octoprint ?'],
[
[ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: xxxxxxxxxxxxxx', '-X', 'POST',
'-d {"command":"M140 S050"}','http://octopi.local/api/printer/command'
]
, '','Chauffer le plateau de l\'imprimante ?'
],
]
#===============
#Initialisations
#===============
dictionnaireOrigineDeviation={}
mw = Gui.getMainWindow()
# Constant definitions
userCancelled= "Cancelled"
userOK= "OK"
indiceCommandeEtParam=0
indiceExtFileNameParamCommandeALancer=1
indiceTextAutreCommandeALancer=2
indiceDelai=3
# UI Class definitions
class BoiteDialogueSTLGuiClass(QtGui.QDialog):
def __init__(self, parent=None):
super(BoiteDialogueSTLGuiClass, self).__init__(parent)
self.initUI()
def initUI(self):
self.result = userCancelled
self.tabCheckboxTextCommandes=[] # tab des checkbox commandes à lancer
self.setGeometry( 250, 250, 400, 150)
self.setWindowTitle("Titre de la fenêtre")
lay = QtGui.QGridLayout(self)
# checkboxes
self.checkboxGenererSTL = QtGui.QCheckBox("Générer le STL", self)
self.checkboxGenererSTL.clicked.connect(self.onCheckboxGenererSTL)
self.checkboxGenererSTL.setCheckState(QtCore.Qt.Checked)# mise à checked
lay.addWidget(self.checkboxGenererSTL, 0, 0)
if doitLancerFichier :
self.checkboxLancerSTL = QtGui.QCheckBox("Lancer le slicer", self)
self.checkboxLancerSTL.clicked.connect(self.onCheckboxLancerSTL)
self.checkboxLancerSTL.setCheckState(QtCore.Qt.Checked)
lay.addWidget(self.checkboxLancerSTL, 2, 0)
posLigne=3
for cmd in commandes :
#decalPositionY=decalPositionY+20
textCommande=cmd[indiceTextAutreCommandeALancer]
self.checkboxTextCommande = QtGui.QCheckBox(textCommande, self)
# mémorisation de cette checbox dans un tableau :
self.tabCheckboxTextCommandes.append(self.checkboxTextCommande)
self.checkboxTextCommande.clicked.connect(self.onCheckboxGenererSTL)
self.checkboxTextCommande.setCheckState(QtCore.Qt.Checked)# mise à checked
lay.addWidget(self.checkboxTextCommande, posLigne, 0)
posLigne=posLigne+1
# numeric input field
self.label2 = QtGui.QLabel('Précision (param deviation) \nentre 0.01 et 1\n'+
'0.01 pour une grande qualité', self)
lay.addWidget(self.label2, 0, 1)
self.precision = QtGui.QDoubleSpinBox(self)
self.precision.setValue(deviation)
self.precision.setRange(0.01,1)
self.precision.setSingleStep(0.01)
self.precision.setToolTip('Plus le chiffre est grand, plus la taille ' +
'du fichier est grande \net meilleur est la précision.\n'+
'Voir le paramètre deviation dans FreeCAD')
lay.addWidget(self.precision, 0, 2)
cancelButton = QtGui.QDialogButtonBox (self)
cancelButton.clicked.connect(self.onCancel)
# cancelButton.setAutoDefault(True)
cancelButton.setStandardButtons(QtGui.QDialogButtonBox.Cancel)
lay.addWidget(cancelButton, posLigne+1, 1)
# OK button
okButton = QtGui.QDialogButtonBox (self)
okButton.clicked.connect(self.onOk)
okButton.setStandardButtons(QtGui.QDialogButtonBox.Ok)
lay.addWidget(okButton, posLigne+1, 2)
#okButton = QtGui.QPushButton('OK', self)
#okButton.clicked.connect(self.onOk)
#lay.addWidget(okButton, posLigne+1, 2)
def onCheckboxGenererSTL(self):
print('onCheckboxGenererSTL')
def onCheckboxLancerSTL(self):
print('onCheckboxLancerSTL')
def onCancel(self):
self.result = False
print('On cancel')
self.close()
def onOk(self):
print('onOK')
self.result = True
self.close()
#===============
#Fonctions
#===============
def octopi(api,commande,urlOctopi,apiKey) :
"""
Fonction qui renvoie le paramètre à passer à subprocess.Popen si on veut lancer
une AI octopi. Non utilisez ici, mais ça peut servir
Arguments :
api l'api à exécuter ex : '/api/printer/command'
commande : la commande de l'API en question ex: '{"command":"M140 S050"}'
urlOctoprint : l'url de votre octopi ex : 'https://octopi.local'
apiKey : la clef API que vous trouvez dans setting/API d'octoprint
Returns :
Example
API lanant un gcode. Ici la chauffe du plateau de l'imprimante
subprocess.Popen(
octopi(
'/api/printer/command',
'{"command":"M140 S050"}',
'https://octopi.local',
'XXXXKEYXAPIXXXXXXXX'))
,close_fds=True)
"""
commandeCurl=[
'curl','-H', 'Content-Type: application/json','-H',
'X-Api-Key: '+apiKey,
'-X', 'POST',
'-d '+commande,urlOctopi+api
]
return commandeCurl
def nameFileStl(dictionnaireOrigineDeviation):
"""
Fonction qui renvoie le nom de fichier compris chemin
où sera enregistré le stl
Le document contenant les objets du dictionaire doit être ouvert et
sauvé
Arguments :
dictionnaireOrigineDeviation (dictionary) :
un dictionnaire dont les clefs sont des solide.
le nom de fichier sera obtenu à partir du nom de fichier
du document contenant le premier objet (solide) de ce
dictionnaire
Returns :
'' si le document n'existe pas,
sinon le nom du fichier où sera enregistré le stl
Example
nomFichierStl=nameFileStl(dictionnaireOrigineDeviation)
"""
doc=list(dictionnaireOrigineDeviation.keys())[0].Document
if doc is None:
QtWidgets.QMessageBox.information(mw,'Attention',\
"Vous n'avez pas de document ouvert")
return ''
if doc.FileName=='':
QtWidgets.QMessageBox.information(mw,
'Attention','Sauvez votre document avant de relancer la commande')
return ''
return(os.path.splitext(doc.FileName)[0]+'.stl')
def memoriseObjEtDeviation(selection,deviationImpose):
"""
Mémorise les solide qui sont contenus dans selection et leur
propriété deviation et remplace cette dernière par la valeur
de deviationImpose
Elle impose un recompute ultérieur par un touch.
Recompute() doit être éventuellement lancé après
Arguments :
selection : une sélection d'objets
deviationImpose (float) : valeur que l'on veut imposer à deviation
Returns:
un dictionnaire contenant l'objet en key et la déviation en value
Renvoie {} s'il n'y a pas d'objet adéquate dans la sélection
Example:
memoriseObjEtDeviation(Gui.Selection.getSelection(),deviation)
"""
dicoObjDeviation={}
for objData in selection :
if objData.isDerivedFrom('Part::Feature') \
and not objData.isDerivedFrom('PartDesign::Feature')\
and not objData.isDerivedFrom('Part::Part2DObject'):
dicoObjDeviation[objData]=objData.ViewObject.Deviation
objData.ViewObject.Deviation=deviationImpose
# pour imposer un recompute même si la déviation est plus petite
# qu'avant :
objData.touch()
for o in objData.ViewObject.claimChildren():
o.touch()
return dicoObjDeviation
def restitueDeviation(dicoOrigineDeviation):
"""
Fonction qui remplace la propriété deviation dans les objets
Elle impose un recompute par un touch. recompute() doit être
lancé après
Arguments:
dicoOrigineDeviation un dictionnaire contenant l'objet en key
et la valeur à imposer dans la propriété déviation de l'objet
en value
Returns: Ne retourne rien
Example:
restitueDeviation(dicoOrigineDeviation)
"""
for ob2 in dicoOrigineDeviation:
ob2.ViewObject.Deviation=dicoOrigineDeviation[ob2]
# pour imposer un recompute même si la déviation est plus petite
# qu'avant :
ob2.touch()
for o in ob2.ViewObject.claimChildren():
o.touch()
def openFile(fileName):
"""
fonction qui lance un fichier en fonction du système d'exploitation
vérifié pour l'instant uniquement sous windows
Arguments:
fileName (string) le nom du fichier à lancer
Returns
Ne retourne rien
Example:
openFile(unfichier.stl)
openFile(calc.exe)
"""
if sys.platform == "win32":
os.startfile(fileName)
else:
opener ="open" if sys.platform == "darwin" else "xdg-open"
#subprocess.call([opener, fileName]) #le problème avec call est que freecad est bloqué en attendant que l'on sorte de l'application
subprocess.Popen([opener, fileName],close_fds=True)
def recalcul(dicoObjs):
"""
lance un recompute pour chaque Document des objets du dico
seulement si cela n'a pas déjà été fait
Arguments:
dicoObjs un dictionnaire contenant l'objet en key
Returns
Ne retourne rien
Example:
recalcul(dicoMesObjets)
"""
docDejaRecalcul=[]
for obj in dicoObjs:
if obj.Document not in docDejaRecalcul:
#print('recompute {}'.format(obj.Document.Name))
obj.Document.recompute()
docDejaRecalcul.append(obj.Document)
def lanceCommmandes(commandesAlancer,fileStlName,formBoiteDialogueSTL):
"""
lance les commmandes
Arguments:
commandesAlancer sous la forme d'un tableau de tableaux
[ ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',délai ],
['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',délai ],
...
]
'commande à exécuter' : la commande que vous voulez exécuter.
Par exemple 'monProgramme.exe' si besoin mettre aussi son chemin.
'param 1, param2... ' : si votre programme a besoin de paramètres.
Ne sera pris en compte que si extention=''
'extention': si non vide, le paramètre sera le nom de votre
fichier FreeCAD mais avec votre extension :
'question à poser' : la question à afficher dans la boite de dialogue
délai : temps d'attente en secondes avant de passer à la commande suivante.
Returns
Ne retourne rien
Example:
lanceCommmandes(commandes,fileStlName) # lance les commandes que l'on a décrites dans commandes
"""
#for i in range(len(formBoiteDialogueSTL.tabCheckboxTextCommandes)) :
for tbktc in formBoiteDialogueSTL.tabCheckboxTextCommandes :
# if formBoiteDialogueSTL.tabCheckboxTextCommandes[i].isChecked() :
if tbktc.isChecked() :
#commande=commandesAlancer[i]
i= formBoiteDialogueSTL.tabCheckboxTextCommandes.index(tbktc)
commande=commandesAlancer[i] ################################## PAS TOP : à reconsidérer
#print('{}'.format(commande))
commandeEtParam=commande[indiceCommandeEtParam]
extFileNameParamCommandeALancer=commande[indiceExtFileNameParamCommandeALancer]
textAutreCommandeALancer=commande[indiceTextAutreCommandeALancer]
delai=0
try :
if commande[indiceDelai:indiceDelai+1]!=[] : #si l'utilisateur a rentré un délai
delai=commande[indiceDelai]
except :
print ('Délai non pris en compte car valeur numérique non valide'+
'pour la commande '+ textAutreCommandeALancer)
if delai > delaiMax :
delai=delaiMax
if extFileNameParamCommandeALancer!='':
if commandeEtParam[2:3]==[] : # Il n'y a pas de paramètre.
# On ajoute un élément pour y mettre notre nom de fichier
# avec notre extension.
commandeEtParam.append('')
commandeEtParam[1]= os.path.splitext(fileStlName)[0]+ extFileNameParamCommandeALancer
subprocess.Popen(commandeEtParam,close_fds=True)
time.sleep(delai)# Pause de durée delai secondes
def run(objs=Gui.Selection.getSelection(), dev=deviation):
"""
fonction principale
Arguments:
doc : le document actif
objs : les objets que l'on a au préalable sélectionnés
dev : la valeur de la propriété déviation qui sera
imposée avant la génération du stl
Returns
Ne retourne rien
Example:
run()
"""
# Boite de dialogue de ce que l'on veut faire
#formBoiteDialogueSTL = BoiteDialogueSTLGuiClass()
formBoiteDialogueSTL =BoiteDialogueSTLGuiClass(mw)
print(formBoiteDialogueSTL.exec_())
if not formBoiteDialogueSTL.result : return
dev=formBoiteDialogueSTL.precision.value()
# on mémorise les solide sélectionnés et leur déviation,
# on fait un touch() de leur child pour prise en compte dans recompute :
dictionnaireOrigineDeviation=memoriseObjEtDeviation(objs,dev)
if len(dictionnaireOrigineDeviation)==0: # si on a trouvé aucun solide
QtWidgets.QMessageBox.information(mw, 'Attention',\
'Sélectionnez un ou plusieurs solide avant de lancer la macro')
return
# on récupère le nom du fichier où l'on devra enregistrer le stl
fileStlName=nameFileStl(dictionnaireOrigineDeviation)
if fileStlName == '' :
return
# l'export lui-même. On passe en paramètre les objets solide qui
# sont dans dictionnaireOrigineDeviation
if formBoiteDialogueSTL.checkboxGenererSTL.isChecked():
Mesh.export(list(dictionnaireOrigineDeviation.keys()), fileStlName)
print('Export fait')
# restitution de la propriété deviation d'origine pour les solide :
restitueDeviation(dictionnaireOrigineDeviation)
recalcul(dictionnaireOrigineDeviation)
if formBoiteDialogueSTL.checkboxLancerSTL.isChecked():
#lance par exemple cura si cura a été associé aux fichiers *.stl
openFile(fileStlName)
lanceCommmandes(commandes,fileStlName,formBoiteDialogueSTL) # lance les commandes que l'on a décrites dans commandes
if __name__ == '__main__':
run()
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Je supprime du post tout ce qui est pris en compte sinon ce sera illisible.
Mais déjà bravo pour le travail. Je sais ce que ça représente comme effort, et c'est des fois un peu ennuyeux d'avoir l'impression de faire plusieurs fois le boulot, ou de faire des choses qui ne changent rien pour l'utilisation.
Tu devrais avoir quelquechose du genre (je n'ai pas forcément pris les bon noms de variables) :
Mais déjà bravo pour le travail. Je sais ce que ça représente comme effort, et c'est des fois un peu ennuyeux d'avoir l'impression de faire plusieurs fois le boulot, ou de faire des choses qui ne changent rien pour l'utilisation.
C'est un point un peu complexe. Dans un 1er temps, le plus simple AMHA est d'enlever l'import de PySide2, et de remplacer 'QtWidgets' par 'QtGui' dans toute la macro. Après on verra si tu veux repasser complètement en PySide2.2cv001 wrote: ↑Wed Dec 16, 2020 4:51 pmBon ça, j'ai pas encore attaqué. J'ai cherché, pais pas vraiment trouvé ce qu'il faut faire. A suivre.Comme d'hab, je fais les remarques en vrac. Je vais essayer de mettre les numéros de ligne.
* L29-30 : tu ne dois pas utiliser PySide et PySide2 en même temps. C'est un peu compliqué à expliquer, mais en version simple, PySide permet d'être compatible avec les vieilles versions FreeCAD. Concrètement, dans PySide les widgets sont dans QtGui, alors que dans PySide2 ils sont dans un namespace séparé nommé QtWidgets. Il faut que tu choisisses entre les 2.
Pas bon. Un QDialogButtonBox est un container qui peut déjà contenir tous les boutons.J'ai fait cela :* L207-212 : pour les boutons OK/Cancel, on utilise plutôt une QDialogButtonBox qui permet d'afficher facilement les boutons standards de la manière traditionnelle de l'OS (et traduits), et on connecte simplement le signal 'accepted' au slot 'accept' du dialogue, et 'rejected' à 'reject'
* L458-459 : si tu utilises les boutons standard, tu peux utiliser le retour de 'exec_()' pour savoir ce qu'il s'est passé (documentation), ce qui fait que tu as juste à faire 'if not formBoiteDialogueSTL.exec_(): return' et tu n'as pas à gérer toi même la valeur de retour
Tu devrais avoir quelquechose du genre (je n'ai pas forcément pris les bon noms de variables) :
Code: Select all
butBox = QtGui.QDialogButtonBox(dialog)
butBox.setOrientation(QtCore.Qt.Horizontal)
butBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
layout.addWidget(butBox) #ligne à modifier en fonction du layout utilisé)
butBox.accepted.connect(self.accept)
butBox.rejected.connect(self.reject)
Maintenant ce sont les lignes 156+161+172mince, comme j'ai changé le code depuis ta version et je n'avais pas sauvé précisément la version en question, je ne sais pas à quoi ça correspond. Tu pourrais me donner les lignes ? soit les recopier, soit les num de ligne du code complet mies en bas de ce message ?* L147+152+162 : les connexions signaux/slots ne servent pas à grand-chose à part polluer la vue rapport. Je pense que tu peux les supprimer.
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Merci !Mais déjà bravo pour le travail. Je sais ce que ça représente comme effort, et c'est des fois un peu ennuyeux d'avoir l'impression de faire plusieurs fois le boulot, ou de faire des choses qui ne changent rien pour l'utilisation.
Effort, effectivement, ça m'oblige à aller fouiller sur internet afin de tenter de comprendre. Le problème des tutos ou exemples que l'on y trouve, est qu'ils donnent une solution, pas toujours la meilleur donc, mais ils n'expliquent pas vraiment. Ou alors je n'ai pas trouvé le bon (je pense à psyde2...=) Mais en même temps, c'est un jeu dans lequel le but du jeu est d'y arriver. Et apprendre, c'est du plaisir donc...
Fait et donc en restant en Pyside.C'est un point un peu complexe. Dans un 1er temps, le plus simple AMHA est d'enlever l'import de PySide2, et de remplacer 'QtWidgets' par 'QtGui' dans toute la macro. Après on verra si tu veux repasser complètement en PySide2.
J'ai du coup ce message dont on avait déjà parlé comme étant "normal" :
07:33:13 C:/Datas/data/Imprimante 3D/Freecad/ExportStlV12.FCMacro:205: DeprecationWarning: an integer is required (got type PySide2.QtWidgets.QDialogButtonBox.StandardButton). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
butBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
Bizarre d’ailleurs que dans ce message il fasse référence à pyside2.
FaitTu devrais avoir quelque chose du genre (je n'ai pas forcément pris les bon noms de variables) :Code: Select all
butBox = QtGui.QDialogButtonBox(dialog) butBox.setOrientation(QtCore.Qt.Horizontal) butBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok) layout.addWidget(butBox) #ligne à modifier en fonction du layout utilisé) butBox.accepted.connect(self.accept) butBox.rejected.connect(self.reject)
IL a juste fallu que je remplace butBox = QtGui.QDialogButtonBox(dialog) par butBox = QtGui.QDialogButtonBox(self) car il ne connaissait pas 'dialog' mais ça marche maintenant. Je pense que ce qu'il manquait principalement, c'est le connect(self.accepted) et le butBox.rejected.connect(self.reject). Et le fait de "mixer" les deux boutons grâce au | dans s(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
Effectivement. Fait* L147+152+162 : les connexions signaux/sl. Faitots ne servent pas à grand-chose à part polluer la vue rapport. Je pense que tu peux les supprimer.
Maintenant ce sont les lignes 156+161+172
Maintenant ça marche Fait
* L458-459 : si tu utilises les boutons standard, tu peux utiliser le retour de 'exec_()' pour savoir ce qu'il s'est passé (documentation), ce qui fait que tu as juste à faire 'if not formBoiteDialogueSTL.exec_(): return' et tu n'as pas à gérer toi même la valeur de retour
Un grand merci openBrain pour la mise à disposition de ton expertise !
Ci-dessous le nouvau source. Je me donne encore un temps pour relire etc. puis le le mettrai à la place du fichier joint dans le premier message de ce sujet.
Code: Select all
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Macro qui permet de créer un fichier "stl" avec des arrondis parfaits
= sans facettes visibles.
Elle permet aussi de lancer des programmes de votre choix
Par exemple pour automatiser la cheine FreeCAD -> Slicer -> impression
Principe du lissage : elle modifie la propriété deviation des solide avant
génération du stl puis replace les anciennes valeures
A la fin, elle propose de lancer le fichier stl qui s'ouvrira par exemple sous
cura si l'extension stl a été associé à cura dans votre système d'exploitation.
Lancement d'autres programmes ou commande :
Vous pouvez lui demander tout programme ou commande que vous pourriez taper
dans un terminal. Pour cela, vous devez modifier ce qu'il y a dans la section
"paramètres pouvant être changés".
Exemples d'applications :
- Allumer l'imprimante et la lumière (nécessite par exemple une prise commandée)
- Connecter octoprint à l'imprimante
- Lancer la préchauffe du plateau
- Sauvegarder votre fichier FreeCAD et stl
....
Fonctionne sous windows. Testé ausi sous Linux
"""
import FreeCAD as App
import Mesh
#from PySide2 import QtWidgets
from PySide import QtGui, QtCore
import os, sys, subprocess
import time
#================================
# Paramètres pouvant être changés
#================================
# Gestion du lissage
#--------------------
# deviation : valeur par défaut qui s'affichera dans la boite de dialogue.
# 0.5 par défaut dans FreeCad, 0.05 permet un lissage plus fort.
# 0.01 est parfait du point de vue lissage.
# plus la valeur est faible et plus la qualité est bonne,
# mais plus la taille du fichier stl est grande
# la valeur doit être comprise entre 0 et 1
deviation=0.01
doitLancerFichier=True # si True, il sera proposer de lancer le fichier stl
delaiMax=10 # si l'utilisateur entre un délai plus grand, il est réduit à delaiMax
# lancement automatique de programmes,
# Automatisation de la chaine de production
#------------------------------------------
#Pour lancer des programmes de votre choix
#Typiquement par exemple un programme domotique,
#allumer votre imprimante ou/et une lumière.
#Utilisez la syntaxe suivante en ajoutant une ligne dans commandes=....:
# [ ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',temps_attente ],
#
# 'commande à exécuter' : la commande que vous voulez exécuter.
# 'parame 1, param 2,...' : si votre programme a besoin de paramètres.
# Ces paramètres ne serons pris en compte que si extention=''
#
# 'extention': si non vide alors le premier paramètre sera remplacé par le nom de
# votre fichier FreeCAD mais avec cette extension :
# 'question à poser' : la question à afficher dans la boite de dialogue
# temps_attente : facuktatif : nb de seconde à attendre après exécution
# permet par exemple d'attendre que l'imprimante ait démarré avant de lui
# demander de chauffer le plateau
# A noter les doubles \ en remplacement de \ (car les caractères spéciaux
# doivent être précédés d'un \ en python). idem pour par exemple l'apostrophe
# les lignes
#[ ['commande à exécuter','param 1','param 2' ] ,'.extension','Question à poser' ],
# doivent être insérées plus bas après commande=[
#
# Exemples de lancements possibles :
"""
[ ['calc.exe' ] ,'' ,'Lancer la calculatrice ?'],
[ ['C:\\WINDOWS\\system32\\mspaint.exe','dessin.jpg' ] ,'' ,'Lancer Paint ?' ],
[ ['C:\\Program Files\\Ultimaker Cura 4.8.0\\Cura.exe'] ,'.stl','Lancer Cura ?' ],
[ ['curl','http://pidomotique/connecte.php' ] , '' ,'Connecter l\'imprimante ?'],
[ ['C:\\Program Files\\Mozilla Firefox\\firefox.exe','https://octopi.local'],'','Lancer Octoprint ?'],
pour linux:
[ [ 'firefox','https://octopi.local' ] , '' ,'Lancer Octoprint ?' ],
Exemple de pilotage de l'imprimante via octoprint (si vous l'avez...) :
mise en température du plateau
- Remplacez les XXX se trouvant après X-Api-Key: par votre clef API
(que l'on trouve dans les paramètres d'octoprint)
- Remplacez http://octopi.local par l'url avec laquelle vous accédez à octoprint
- Modifiez la valeur 050 dans "M140 S050" : S050 pour chauffer à 50° si vous souhaitez 60° : S060
- 6 lignes à recopier :
[
[ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: XXXXXXXXXXXXXXXXXXXXX', '-X', 'POST',
'-d {"command":"M140 S050"}','http://octopi.local/api/printer/command'
]
, '','Chauffer le plateau de l\'imprimante ?'
],
"""
commandes=[
# insérez ici vos lignes.
[ ['calc.exe' ] ,'' ,'Lancer la calculatrice ?',2],
[ ['C:\\Program Files\\Ultimaker Cura 4.8.0\\Cura.exe'] ,'.stl','Lancer Cura ?' ],
[ ['curl','http://pidomotique/connecte.php' ] , '' ,'Allumer et connecter l\'imprimante ?',4],
[ ['C:\\Program Files\\Mozilla Firefox\\firefox.exe','https://octopi.local'],'','Lancer Octoprint ?'],
[
[ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: xxxxxxxxxxxxxxx', '-X', 'POST',
'-d {"command":"M140 S050"}','http://octopi.local/api/printer/command'
]
, '','Chauffer le plateau de l\'imprimante ?'
],
]
#===============
#Initialisations
#===============
dictionnaireOrigineDeviation={}
mw = Gui.getMainWindow()
# Constant definitions
userCancelled= "Cancelled"
userOK= "OK"
indiceCommandeEtParam=0
indiceExtFileNameParamCommandeALancer=1
indiceTextAutreCommandeALancer=2
indiceDelai=3
# UI Class definitions
class BoiteDialogueSTLGuiClass(QtGui.QDialog):
def __init__(self, parent=None):
super(BoiteDialogueSTLGuiClass, self).__init__(parent)
self.initUI()
def initUI(self):
self.result = userCancelled
self.tabCheckboxTextCommandes=[] # tab des checkbox commandes à lancer
self.setGeometry( 250, 250, 400, 150)
self.setWindowTitle("Titre de la fenêtre")
lay = QtGui.QGridLayout(self)
# checkboxes
self.checkboxGenererSTL = QtGui.QCheckBox("Générer le STL", self)
#self.checkboxGenererSTL.clicked.connect(self.onCheckboxGenererSTL)
self.checkboxGenererSTL.setCheckState(QtCore.Qt.Checked)# mise à checked
lay.addWidget(self.checkboxGenererSTL, 0, 0)
if doitLancerFichier :
self.checkboxLancerSTL = QtGui.QCheckBox("Lancer le slicer", self)
# self.checkboxLancerSTL.clicked.connect(self.onCheckboxLancerSTL)
self.checkboxLancerSTL.setCheckState(QtCore.Qt.Checked)
lay.addWidget(self.checkboxLancerSTL, 2, 0)
posLigne=3
for cmd in commandes :
#decalPositionY=decalPositionY+20
textCommande=cmd[indiceTextAutreCommandeALancer]
self.checkboxTextCommande = QtGui.QCheckBox(textCommande, self)
# mémorisation de cette checbox dans un tableau :
self.tabCheckboxTextCommandes.append(self.checkboxTextCommande)
# self.checkboxTextCommande.clicked.connect(self.onCheckboxGenererSTL)
self.checkboxTextCommande.setCheckState(QtCore.Qt.Checked)# mise à checked
lay.addWidget(self.checkboxTextCommande, posLigne, 0)
posLigne=posLigne+1
# numeric input field
self.label2 = QtGui.QLabel('Précision (param deviation) \nentre 0.01 et 1\n'+
'0.01 pour une grande qualité', self)
lay.addWidget(self.label2, 0, 1)
self.precision = QtGui.QDoubleSpinBox(self)
self.precision.setValue(deviation)
self.precision.setRange(0.01,1)
self.precision.setSingleStep(0.01)
self.precision.setToolTip('Plus le chiffre est grand, plus la taille ' +
'du fichier est grande \net meilleur est la précision.\n'+
'Voir le paramètre deviation dans FreeCAD')
lay.addWidget(self.precision, 0, 2)
"""
cancelButton = QtGui.QDialogButtonBox (self)
cancelButton.clicked.connect(self.onCancel)
# cancelButton.setAutoDefault(True)
cancelButton.setStandardButtons(QtGui.QDialogButtonBox.Cancel)
lay.addWidget(cancelButton, posLigne+1,1)
# OK button
okButton = QtGui.QDialogButtonBox (self)
okButton.clicked.connect(self.onOk)
okButton.setStandardButtons(QtGui.QDialogButtonBox.Ok)
lay.addWidget(okButton, posLigne+1, 2)
"""
butBox = QtGui.QDialogButtonBox(self)
butBox.setOrientation(QtCore.Qt.Horizontal)
butBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
lay.addWidget(butBox)
butBox.accepted.connect(self.accept)
butBox.rejected.connect(self.reject)
def onCheckboxGenererSTL(self):
print('onCheckboxGenererSTL')
def onCheckboxLancerSTL(self):
print('onCheckboxLancerSTL')
"""
def onCancel(self):
self.result = False
print('On cancel')
self.close()
def onOk(self):
print('onOK')
self.result = True
self.close()
"""
#===============
#Fonctions
#===============
def octopi(api,commande,urlOctopi,apiKey) :
"""
Fonction qui renvoie le paramètre à passer à subprocess.Popen si on veut lancer
une AI octopi. Non utilisez ici, mais ça peut servir
Arguments :
api l'api à exécuter ex : '/api/printer/command'
commande : la commande de l'API en question ex: '{"command":"M140 S050"}'
urlOctoprint : l'url de votre octopi ex : 'https://octopi.local'
apiKey : la clef API que vous trouvez dans setting/API d'octoprint
Returns :
Example
API lanant un gcode. Ici la chauffe du plateau de l'imprimante
subprocess.Popen(
octopi(
'/api/printer/command',
'{"command":"M140 S050"}',
'https://octopi.local',
'XXXXKEYXAPIXXXXXXXX'))
,close_fds=True)
"""
commandeCurl=[
'curl','-H', 'Content-Type: application/json','-H',
'X-Api-Key: '+apiKey,
'-X', 'POST',
'-d '+commande,urlOctopi+api
]
return commandeCurl
def nameFileStl(dictionnaireOrigineDeviation):
"""
Fonction qui renvoie le nom de fichier compris chemin
où sera enregistré le stl
Le document contenant les objets du dictionaire doit être ouvert et
sauvé
Arguments :
dictionnaireOrigineDeviation (dictionary) :
un dictionnaire dont les clefs sont des solide.
le nom de fichier sera obtenu à partir du nom de fichier
du document contenant le premier objet (solide) de ce
dictionnaire
Returns :
'' si le document n'existe pas,
sinon le nom du fichier où sera enregistré le stl
Example
nomFichierStl=nameFileStl(dictionnaireOrigineDeviation)
"""
doc=list(dictionnaireOrigineDeviation.keys())[0].Document
if doc is None:
QtGui.QMessageBox.information(mw,'Attention',\
"Vous n'avez pas de document ouvert")
return ''
if doc.FileName=='':
QtGui.QMessageBox.information(mw,
'Attention','Sauvez votre document avant de relancer la commande')
return ''
return(os.path.splitext(doc.FileName)[0]+'.stl')
def memoriseObjEtDeviation(selection,deviationImpose):
"""
Mémorise les solide qui sont contenus dans selection et leur
propriété deviation et remplace cette dernière par la valeur
de deviationImpose
Elle impose un recompute ultérieur par un touch.
Recompute() doit être éventuellement lancé après
Arguments :
selection : une sélection d'objets
deviationImpose (float) : valeur que l'on veut imposer à deviation
Returns:
un dictionnaire contenant l'objet en key et la déviation en value
Renvoie {} s'il n'y a pas d'objet adéquate dans la sélection
Example:
memoriseObjEtDeviation(Gui.Selection.getSelection(),deviation)
"""
dicoObjDeviation={}
for objData in selection :
if objData.isDerivedFrom('Part::Feature') \
and not objData.isDerivedFrom('PartDesign::Feature')\
and not objData.isDerivedFrom('Part::Part2DObject'):
dicoObjDeviation[objData]=objData.ViewObject.Deviation
objData.ViewObject.Deviation=deviationImpose
# pour imposer un recompute même si la déviation est plus petite
# qu'avant :
objData.touch()
for o in objData.ViewObject.claimChildren():
o.touch()
return dicoObjDeviation
def restitueDeviation(dicoOrigineDeviation):
"""
Fonction qui remplace la propriété deviation dans les objets
Elle impose un recompute par un touch. recompute() doit être
lancé après
Arguments:
dicoOrigineDeviation un dictionnaire contenant l'objet en key
et la valeur à imposer dans la propriété déviation de l'objet
en value
Returns: Ne retourne rien
Example:
restitueDeviation(dicoOrigineDeviation)
"""
for ob2 in dicoOrigineDeviation:
ob2.ViewObject.Deviation=dicoOrigineDeviation[ob2]
# pour imposer un recompute même si la déviation est plus petite
# qu'avant :
ob2.touch()
for o in ob2.ViewObject.claimChildren():
o.touch()
def openFile(fileName):
"""
fonction qui lance un fichier en fonction du système d'exploitation
vérifié pour l'instant uniquement sous windows
Arguments:
fileName (string) le nom du fichier à lancer
Returns
Ne retourne rien
Example:
openFile(unfichier.stl)
openFile(calc.exe)
"""
if sys.platform == "win32":
os.startfile(fileName)
else:
opener ="open" if sys.platform == "darwin" else "xdg-open"
subprocess.Popen([opener, fileName],close_fds=True)
def recalcul(dicoObjs):
"""
lance un recompute pour chaque Document des objets du dico
seulement si cela n'a pas déjà été fait
Arguments:
dicoObjs un dictionnaire contenant l'objet en key
Returns
Ne retourne rien
Example:
recalcul(dicoMesObjets)
"""
docDejaRecalcul=[]
for obj in dicoObjs:
if obj.Document not in docDejaRecalcul:
#print('recompute {}'.format(obj.Document.Name))
obj.Document.recompute()
docDejaRecalcul.append(obj.Document)
def lanceCommmandes(commandesAlancer,fileStlName,formBoiteDialogueSTL):
"""
lance les commmandes
Arguments:
commandesAlancer sous la forme d'un tableau de tableaux
[ ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',délai ],
['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',délai ],
...
]
'commande à exécuter' : la commande que vous voulez exécuter.
Par exemple 'monProgramme.exe' si besoin mettre aussi son chemin.
'param 1, param2... ' : si votre programme a besoin de paramètres.
Ne sera pris en compte que si extention=''
'extention': si non vide, le paramètre sera le nom de votre
fichier FreeCAD mais avec votre extension :
'question à poser' : la question à afficher dans la boite de dialogue
délai : temps d'attente en secondes avant de passer à la commande suivante.
Returns
Ne retourne rien
Example:
lanceCommmandes(commandes,fileStlName) # lance les commandes que l'on a décrites dans commandes
"""
#for i in range(len(formBoiteDialogueSTL.tabCheckboxTextCommandes)) :
for tbktc in formBoiteDialogueSTL.tabCheckboxTextCommandes :
# if formBoiteDialogueSTL.tabCheckboxTextCommandes[i].isChecked() :
if tbktc.isChecked() :
#commande=commandesAlancer[i]
i= formBoiteDialogueSTL.tabCheckboxTextCommandes.index(tbktc)
commande=commandesAlancer[i] ################################## PAS TOP : à reconsidérer
#print('{}'.format(commande))
commandeEtParam=commande[indiceCommandeEtParam]
extFileNameParamCommandeALancer=commande[indiceExtFileNameParamCommandeALancer]
textAutreCommandeALancer=commande[indiceTextAutreCommandeALancer]
delai=0
try :
if commande[indiceDelai:indiceDelai+1]!=[] : #si l'utilisateur a rentré un délai
delai=commande[indiceDelai]
except :
print ('Délai non pris en compte car valeur numérique non valide'+
'pour la commande '+ textAutreCommandeALancer)
if delai > delaiMax :
delai=delaiMax
if extFileNameParamCommandeALancer!='':
if commandeEtParam[2:3]==[] : # Il n'y a pas de paramètre.
# On ajoute un élément pour y mettre notre nom de fichier
# avec notre extension.
commandeEtParam.append('')
commandeEtParam[1]= os.path.splitext(fileStlName)[0]+ extFileNameParamCommandeALancer
subprocess.Popen(commandeEtParam,close_fds=True)
time.sleep(delai)# Pause de durée delai secondes
def run(objs=Gui.Selection.getSelection(), dev=deviation):
"""
fonction principale
Arguments:
doc : le document actif
objs : les objets que l'on a au préalable sélectionnés
dev : la valeur de la propriété déviation qui sera
imposée avant la génération du stl
Returns
Ne retourne rien
Example:
run()
"""
# Boite de dialogue de ce que l'on veut faire
formBoiteDialogueSTL =BoiteDialogueSTLGuiClass(mw)
if not formBoiteDialogueSTL.exec_() : return
dev=formBoiteDialogueSTL.precision.value()
# on mémorise les solide sélectionnés et leur déviation,
# on fait un touch() de leur child pour prise en compte dans recompute :
dictionnaireOrigineDeviation=memoriseObjEtDeviation(objs,dev)
if len(dictionnaireOrigineDeviation)==0: # si on a trouvé aucun solide
QtGui.QMessageBox.information(mw, 'Attention',\
'Sélectionnez un ou plusieurs solide avant de lancer la macro')
return
# on récupère le nom du fichier où l'on devra enregistrer le stl
fileStlName=nameFileStl(dictionnaireOrigineDeviation)
if fileStlName == '' :
return
# l'export lui-même. On passe en paramètre les objets solide qui
# sont dans dictionnaireOrigineDeviation
if formBoiteDialogueSTL.checkboxGenererSTL.isChecked():
Mesh.export(list(dictionnaireOrigineDeviation.keys()), fileStlName)
print('Export fait')
# restitution de la propriété deviation d'origine pour les solide :
restitueDeviation(dictionnaireOrigineDeviation)
recalcul(dictionnaireOrigineDeviation)
if formBoiteDialogueSTL.checkboxLancerSTL.isChecked():
#lance par exemple cura si cura a été associé aux fichiers *.stl
openFile(fileStlName)
lanceCommmandes(commandes,fileStlName,formBoiteDialogueSTL) # lance les commandes que l'on a décrites dans commandes
if __name__ == '__main__':
run()
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Les remarques :
* Manque toujours le 'import FreeCADGui as Gui'
* L129+130+148+... : normalement tu n'as plus besoin des variables userCancelled, userOK, self.result, ...
* L151 : tu devrais mettre comme titre du dialogue un truc plus parlant que "Titre de la fenêtre"
* L165+166+175 : plutôt que de gérer à la main un compteur, il vaut mieux utiliser la façon pythonic : 'for posLigne, cmd in enumerate(commandes, start=3):'
* Pas mal de code mort (commenté) entre les lignes 190 et 225
* Je suis surpris que ça marche en fait car il te manque une ligne à la fin du initUI : 'self.setLayout(lay)'
* L210-213 : fonctions inutiles maintenant je pense
* L472 : j'avais dû l'écrire comme ça pour faire court, mais tu devrais mettre le 'return' sur la ligne suivante avec une indentation (façon classique)
Du coup je vais essayer d'expliquer un peu plus.Fait et donc en restant en Pyside.
J'ai du coup ce message dont on avait déjà parlé comme étant "normal" :
07:33:13 C:/Datas/data/Imprimante 3D/Freecad/ExportStlV12.FCMacro:205: DeprecationWarning: an integer is required (got type PySide2.QtWidgets.QDialogButtonBox.StandardButton). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
butBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
Bizarre d’ailleurs que dans ce message il fasse référence à pyside2.
'PySide' (nom générique) est le nom du binding Python pour Qt (c'est-à-dire la couche qui permet de se servir de Qt -- qui est en C++ -- depuis Python).
PySide est né du temps de Qt4. Le module Python 'PySide' (nom spécifique) est donc --normalement-- celui associé à Qt4, et PySide2 est celui associé à Qt5 -- qui est la version courante.
La différence principale quand on l'utilise est que PySide n'a que 2 namespaces principaux : QtGui (tout ce qui concerne les UI) et QtCore (toutes les fonctions de bases).
PySide2 a scindé QtGui en 2 et il a donc 3 principaux namespaces : QtWidgets (tout ce qui concerne les widgets), QtGui (tout ce qui concerne les UI mais pas les widgets) et QtCore (toutes les fonctions de bases). En gros ce qui concerne spécifiquement les widgets a été sorti de QtGui et mis dans un namespace spécifique QtWidgets.
FreeCAD 0.18 (c'est peut-être encore le cas en 0.19 mais pas sûr) était compilé à la fois en version Qt4 et en version Qt5. Et du coup, en théorie, il aurait fallu que les macros et workbenches en Python existent en 2 versions : PySide et PySide2. C'est ingérable. Les auteurs auraient aussi pu gérer chacun dans leur macro/workbench la détection de la version Qt et créer des wrappers -- des fonctions qui appellent soit PySide soit PySide2 -- en fonction. Mais ça aurait été le bazar.
Donc les développeurs FreeCAD ont fait quelque chose de très malin en gérant tout ça au niveau de l'import. En fait, de manière spécifique dans FreeCAD, quand tu fais 'from PySide import QtGui', il peut se passer 2 choses différentes qui sont transparentes pour toi :
* Si tu es sur une version Qt4, ça importe PySide.QtGui normalement
* Si tu es sur une version Qt5, ça importe PySide2.QtGui + PySide2.QtWidgets et ça remappe l'ensemble sur le namespace PySide.QtGui
Du coup les macros 'PySide' fonctionnent de la même manière en Qt4 ou Qt5.
Comme tu le vois, ça peut générer quelques warnings et ça empêche d'avoir accès aux fonctions les plus récentes de Qt, mais globalement ça marche très bien.
Je pense toutefois qu'à la sortie officielle de la 0.19, on arrêtera PySide et les nouvelles macros seront en PySide2. Dans ce cas, il faudra bien travailler avec QtWidgets pour tout ce qui concerne les widgets.
En fait j'ai copié le code d'un autre topic où le dialogue n'était pas une classe, et du coup j'ai oublié de corriger ça.IL a juste fallu que je remplace butBox = QtGui.QDialogButtonBox(dialog) par butBox = QtGui.QDialogButtonBox(self) car il ne connaissait pas 'dialog' mais ça marche maintenant.
Pas sûr qu'on soit au niveau de l'expertise là. Ceci dit c'est fait avec plaisir vu que tu fais aussi les efforts de ton côté et que tu es volontaire pour apprendre.Un grand merci openBrain pour la mise à disposition de ton expertise !
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
openBrain wrote: ↑Thu Dec 17, 2020 12:10 pm Les remarques :
* Manque toujours le 'import FreeCADGui as Gui'* L129+130+148+... : normalement tu n'as plus besoin des variables userCancelled, userOK, self.result, ...Tiens, effectivement, sans doute un retour arrière trop fort à un moment. désolé..
Fait* L151 : tu devrais mettre comme titre du dialogue un truc plus parlant que "Titre de la fenêtre"fait* L165+166+175 : plutôt que de gérer à la main un compteur, il vaut mieux utiliser la façon pythonic : 'for posLigne, cmd in enumerate(commandes, start=3):'Un mauvais couper coller d'un mini programme en parallèle sur lequel je travaillais pour comprendre comment ça fonctionnait et sur lequel je faisait plein d'essais.
Fait* Pas mal de code mort (commenté) entre les lignes 190 et 225C'est très puissant ! J'ai encore appris quelque chose.
Fait* Je suis surpris que ça marche en fait car il te manque une ligne à la fin du initUI : 'self.setLayout(lay)'J'ai supprimé un peu partout les codes morts dont tu parles, mais aussi des print() qui me servaient à débugger, ainsi que des commentaires devenus inutiles* L210-213 : fonctions inutiles maintenant je penseLes mystères de l'informatique...
Je l'ai mis en dernière ligne mais je suppose que l'on peut le mettre où l'on veut après le ' lay = QtWidgets.QGridLayout(self) 'Code: Select all
def initUI(self): self.tabCheckboxTextCommandes=[] # tab des checkbox commandes à lancer self.setGeometry( 250, 250, 400, 150) self.setWindowTitle("Export et lancement de programmes") lay = QtWidgets.QGridLayout(self) ............................. ...................... butBox.accepted.connect(self.accept) butBox.rejected.connect(self.reject) self.setLayout(lay)
* L472 : j'avais dû l'écrire comme ça pour faire court, mais tu devrais mettre le 'return' sur la ligne suivante avec une indentation (façon classique)J'ai fait du ménageDu coup je vais essayer d'expliquer un peu plus.Rectifié.
'PySide' (nom générique) est .......
.....................Dans ce cas, il faudra bien travailler avec QtWidgets pour tout ce qui concerne les widgets.Encore merci openBrain pour ton temps passé !Bravo pour l'explication ! tout compris ! (enfin je crois ) J'ai donc fait la modif. Du coup d'ailleurs, j'ai pu retirer QtGui dans l'import :
from PySide2 import QtCore, QtWidgets car sauf erreur, il n'y a pas d'endroit où j'avais besoin de QtGui
Par contre, j'ai toujours les messages "warning"
" butBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
17:07:52 C:/Datas/data/Imprimante 3D/Freecad/ExportStlV14.FCMacro:183: DeprecationWarning: an integer is required (got type PySide2.QtWidgets.QDialogButtonBox.StandardButton). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python."
Demain à tête reposée, je relie tout encore une fois
Michel
Le nouveau code :Code: Select all
#!/usr/bin/env python # -*- coding: utf-8 -*- """ Macro qui permet de créer un fichier "stl" avec des arrondis parfaits = sans facettes visibles. Elle permet aussi de lancer des programmes de votre choix Par exemple pour automatiser la cheine FreeCAD -> Slicer -> impression Principe du lissage : elle modifie la propriété deviation des solides avant génération du stl puis replace les anciennes valeurs A la fin, elle propose de lancer le fichier stl qui s'ouvrira par exemple sous cura si l'extension stl a été associée à cura dans votre système d'exploitation. Lancement d'autres programmes ou commandes : Vous pouvez lui demander d'enchainer tout programme ou commande que vous pourriez taper dans un terminal. Pour cela, vous devez modifier ce qu'il y a dans la section "paramètres pouvant être changés". Exemples d'applications : - Allumer l'imprimante et la lumière (nécessite par exemple une prise commandée) - Connecter octoprint à l'imprimante - Lancer la préchauffe du plateau - Sauvegarder votre fichier FreeCAD et stl .... Fonctionne sous windows. Testé ausi sous Linux """ import FreeCAD as App import Mesh from PySide2 import QtCore, QtWidgets import os, sys, subprocess import time import FreeCADGui as Gui #================================ # Paramètres pouvant être changés #================================ # Gestion du lissage #-------------------- # deviation : valeur par défaut qui s'affichera dans la boite de dialogue. # 0.5 par défaut dans FreeCad, 0.05 permet un lissage plus fort. # 0.01 est parfait du point de vue lissage. # plus la valeur est faible et plus la qualité est bonne, # mais plus la taille du fichier stl est grande # la valeur doit être comprise entre 0 et 1 deviation=0.01 doitLancerFichier=True # si True, il sera proposer de lancer le fichier stl. # sinon mettre False # lancement automatique de programmes, # Automatisation de la chaine de production #------------------------------------------ delaiMax=10 # si l'utilisateur entre un délaiplus grand que cette valeur, # il sera réduit à delaiMax # delai=nb de secondes entre les programmes #Pour lancer des programmes de votre choix #Typiquement par exemple un programme domotique, #allumer votre imprimante ou/et une lumière etc. #Utilisez la syntaxe suivante en ajoutant une ligne plus bas, dans commandes=[....: # [ ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',temps_attente ], # # 'commande à exécuter' : la commande que vous voulez exécuter. # 'parame 1, param 2,...' : si votre programme a besoin de paramètres. # Ces paramètres ne serons pris en compte que si extention='' # # 'extention': si non vide alors le premier paramètre sera remplacé par le nom de # votre fichier FreeCAD mais avec cette extension : # 'question à poser' : la question à afficher dans la boite de dialogue # temps_attente : facuktatif : nb de secondes à attendre après exécution # permet par exemple d'attendre que l'imprimante ait démarré avant de lui # demander de chauffer le plateau # A noter les doubles \ en remplacement de \ (car les caractères spéciaux # doivent être précédés d'un \ en python). idem pour par exemple l'apostrophe # les lignes en question sont à insérer plu sbas après commandes=[... # # Exemples de lancements possibles : """ [ ['calc.exe' ] ,'' ,'Lancer la calculatrice ?'], [ ['C:\\WINDOWS\\system32\\mspaint.exe','dessin.jpg' ] ,'' ,'Lancer Paint ?' ], [ ['C:\\Program Files\\Ultimaker Cura 4.8.0\\Cura.exe'] ,'.stl','Lancer Cura ?' ], [ ['curl','http://pidomotique/connecte.php' ] , '' ,'Connecter l\'imprimante ?'], [ ['C:\\Program Files\\Mozilla Firefox\\firefox.exe','https://octopi.local'],'','Lancer Octoprint ?'], pour linux: [ [ 'firefox','https://octopi.local' ] , '' ,'Lancer Octoprint ?' ], Exemple de pilotage de l'imprimante via octoprint (si vous l'avez...) : mise en température du plateau - Remplacez les XXX se trouvant après X-Api-Key: par votre clef API (que l'on trouve dans les paramètres d'octoprint) - Remplacez http://octopi.local par l'url avec laquelle vous accédez à octoprint - Modifiez la valeur 050 dans "M140 S050". S050 indique qu'il faut chauffer à 50° si vous souhaitez 60°, remplacez S050 par S060 - 6 lignes à recopier après l'avoir adapté: [ [ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: XXXXXXXXXXXXXXXXXXXXX', '-X', 'POST', '-d {"command":"M140 S050"}','http://octopi.local/api/printer/command' ] , '','Chauffer le plateau de l\'imprimante ?' ], """ commandes=[ # insérez ici vos lignes. [ ['calc.exe' ] ,'' ,'Lancer la calculatrice ?',2], [ ['C:\\Program Files\\Ultimaker Cura 4.8.0\\Cura.exe'] ,'.stl','Lancer Cura ?' ], [ ['curl','http://pidomotique/connecte.php' ] , '' ,'Allumer et connecter l\'imprimante ?',4], [ ['C:\\Program Files\\Mozilla Firefox\\firefox.exe','https://octopi.local'],'','Lancer Octoprint ?'], [ [ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: xxxxxxxxxxxxxxx', '-X', 'POST', '-d {"command":"M140 S050"}','http://octopi.local/api/printer/command' ] , '','Chauffer le plateau de l\'imprimante ?' ], ] #=============== #Initialisations #=============== dictionnaireOrigineDeviation={} mw = Gui.getMainWindow() indiceCommandeEtParam=0 indiceExtFileNameParamCommandeALancer=1 indiceTextAutreCommandeALancer=2 indiceDelai=3 # UI Class definitions class BoiteDialogueSTLGuiClass(QtWidgets.QDialog): def __init__(self, parent=None): super(BoiteDialogueSTLGuiClass, self).__init__(parent) self.initUI() def initUI(self): self.tabCheckboxTextCommandes=[] # tab des checkbox commandes à lancer self.setGeometry( 250, 250, 400, 150) self.setWindowTitle("Export et lancement de programmes") lay = QtWidgets.QGridLayout(self) # checkboxes self.checkboxGenererSTL = QtWidgets.QCheckBox("Générer le STL", self) self.checkboxGenererSTL.setCheckState(QtCore.Qt.Checked) lay.addWidget(self.checkboxGenererSTL, 0, 0) if doitLancerFichier : self.checkboxLancerSTL = QtWidgets.QCheckBox("Lancer le slicer", self) self.checkboxLancerSTL.setCheckState(QtCore.Qt.Checked) lay.addWidget(self.checkboxLancerSTL, 2, 0) for posLigne, cmd in enumerate(commandes, start=3): textCommande=cmd[indiceTextAutreCommandeALancer] self.checkboxTextCommande = QtWidgets.QCheckBox(textCommande, self) # mémorisation de cette checbox dans un tableau : self.tabCheckboxTextCommandes.append(self.checkboxTextCommande) self.checkboxTextCommande.setCheckState(QtCore.Qt.Checked) lay.addWidget(self.checkboxTextCommande, posLigne, 0) # numeric input field self.label2 = QtWidgets.QLabel('Précision (param deviation) \nentre 0.01 et 1\n'+ '0.01 pour une grande qualité', self) lay.addWidget(self.label2, 0, 1) self.precision = QtWidgets.QDoubleSpinBox(self) self.precision.setValue(deviation) self.precision.setRange(0.01,1) self.precision.setSingleStep(0.01) self.precision.setToolTip('Plus le chiffre est grand, plus la taille ' + 'du fichier est grande \net meilleur est la précision.\n'+ 'Voir le paramètre deviation dans FreeCAD') lay.addWidget(self.precision, 0, 2) butBox = QtWidgets.QDialogButtonBox(self) butBox.setOrientation(QtCore.Qt.Horizontal) butBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok) lay.addWidget(butBox) butBox.accepted.connect(self.accept) butBox.rejected.connect(self.reject) self.setLayout(lay) #=============== #Fonctions #=============== def octopi(api,commande,urlOctopi,apiKey) : """ Fonction qui renvoie le paramètre à passer à subprocess.Popen si on veut lancer une AI octopi. Non utilisée ici. Mais pour mémoire et une prochaine évolution Arguments : api l'api à exécuter ex : '/api/printer/command' commande : la commande de l'API en question ex: '{"command":"M140 S050"}' urlOctoprint : l'url de votre octopi ex : 'https://octopi.local' apiKey : la clef API que vous trouvez dans setting/API d'octoprint Returns : Example API lanant un gcode. Ici la chauffe du plateau de l'imprimante subprocess.Popen( octopi( '/api/printer/command', '{"command":"M140 S050"}', 'https://octopi.local', 'XXXXKEYXAPIXXXXXXXX')) ,close_fds=True) """ commandeCurl=[ 'curl','-H', 'Content-Type: application/json','-H', 'X-Api-Key: '+apiKey, '-X', 'POST', '-d '+commande,urlOctopi+api ] return commandeCurl def nameFileStl(dictionnaireOrigineDeviation,mw=Gui.getMainWindow()): """ Fonction qui détermine et renvoie le nom de fichier compris chemin où sera enregistré le stl Le document FreeCAD contenant les objets du dictionaire doit être ouvert etsauvé Arguments : dictionnaireOrigineDeviation (dictionary) : un dictionnaire dont les clefs sont des solides FreeCAD. le nom de fichier sera obtenu à partir du nom de fichier du document contenant le premier objet (solide) de ce dictionnaire Returns : '' si le document n'existe pas, sinon le nom du fichier où sera enregistré le stl Example nomFichierStl=nameFileStl(dictionnaireOrigineDeviation) """ doc=list(dictionnaireOrigineDeviation.keys())[0].Document if doc is None: QtWidgets.QMessageBox.information(mw,'Attention',\ "Vous n'avez pas de document ouvert") return '' if doc.FileName=='': QtWidgets.QMessageBox.information(mw, 'Attention','Sauvez votre document avant de relancer la commande') return '' return(os.path.splitext(doc.FileName)[0]+'.stl') def memoriseObjEtDeviation(selection,deviationImpose): """ Mémorise les solides qui sont contenus dans selection et leur propriété deviation et remplace cette dernière par la valeur de deviationImpose Elle impose un recompute ultérieur par un touch. Recompute() doit être éventuellement lancé après Arguments : selection : une sélection d'objets deviationImpose (float) : valeur que l'on veut imposer à deviation Returns: un dictionnaire contenant l'objet en key et la déviation en value Renvoie {} s'il n'y a pas d'objet adéquate dans la sélection Example: memoriseObjEtDeviation(Gui.Selection.getSelection(),deviation) """ dicoObjDeviation={} for objData in selection : if objData.isDerivedFrom('Part::Feature') \ and not objData.isDerivedFrom('PartDesign::Feature')\ and not objData.isDerivedFrom('Part::Part2DObject'): dicoObjDeviation[objData]=objData.ViewObject.Deviation objData.ViewObject.Deviation=deviationImpose # pour imposer un recompute même si la déviation est plus petite # qu'avant : objData.touch() for o in objData.ViewObject.claimChildren(): o.touch() return dicoObjDeviation def restitueDeviation(dicoOrigineDeviation): """ Fonction qui remplace la propriété deviation dans les objets Elle impose un recompute par un touch. recompute() doit être lancé après Arguments: dicoOrigineDeviation un dictionnaire contenant l'objet en key et la valeur à imposer dans la propriété déviation de l'objet en value Returns: Ne retourne rien Example: restitueDeviation(dicoOrigineDeviation) """ for ob2 in dicoOrigineDeviation: ob2.ViewObject.Deviation=dicoOrigineDeviation[ob2] # pour imposer un recompute même si la déviation est plus petite # qu'avant : ob2.touch() for o in ob2.ViewObject.claimChildren(): o.touch() def openFile(fileName): """ fonction qui lance un fichier en fonction du système d'exploitation vérifié pour l'instant uniquement sous windows Arguments: fileName (string) le nom du fichier à lancer Returns Ne retourne rien Example: openFile(unfichier.stl) openFile(calc.exe) """ if sys.platform == "win32": os.startfile(fileName) else: opener ="open" if sys.platform == "darwin" else "xdg-open" subprocess.Popen([opener, fileName],close_fds=True) def recalcul(dicoObjs): """ lance un recompute pour chaque Document des objets du dico seulement si cela n'a pas déjà été fait Arguments: dicoObjs un dictionnaire contenant l'objet en key Returns Ne retourne rien Example: recalcul(dicoMesObjets) """ docDejaRecalcul=[] for obj in dicoObjs: if obj.Document not in docDejaRecalcul: obj.Document.recompute() docDejaRecalcul.append(obj.Document) def lanceCommmandes(commandesAlancer,fileStlName,formBoiteDialogueSTL): """ lance les commmandes Arguments: commandesAlancer sous la forme d'un tableau de tableaux [ ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',délai ], ['commande à exécuter', 'param 1', param 2, ] ,'.extention','question à poser',délai ], ... ] 'commande à exécuter' : la commande que vous voulez exécuter. Par exemple 'monProgramme.exe' si besoin mettre aussi son chemin. 'param 1, param2... ' : si votre programme a besoin de paramètres. Ne sera pris en compte que si extention='' 'extention': si non vide, le paramètre param 1 sera le nom de votre fichier FreeCAD mais avec votre extension : 'question à poser' : la question à afficher dans la boite de dialogue délai : temps d'attente en secondes avant de passer à la commande suivante. Returns Ne retourne rien Example: lanceCommmandes(commandes,fileStlName,formBoiteDialogueSTL) # lance les commandes que l'on a décrites dans commandes """ for tbktc in formBoiteDialogueSTL.tabCheckboxTextCommandes : if tbktc.isChecked() : i= formBoiteDialogueSTL.tabCheckboxTextCommandes.index(tbktc) commande=commandesAlancer[i] commandeEtParam=commande[indiceCommandeEtParam] extFileNameParamCommandeALancer=commande[indiceExtFileNameParamCommandeALancer] textAutreCommandeALancer=commande[indiceTextAutreCommandeALancer] delai=0 try : if commande[indiceDelai:indiceDelai+1]!=[] : #si l'utilisateur a rentré un délai delai=commande[indiceDelai] except : print ('Délai non pris en compte car valeur numérique non valide'+ 'pour la commande '+ textAutreCommandeALancer) if delai > delaiMax : delai=delaiMax if extFileNameParamCommandeALancer!='': if commandeEtParam[2:3]==[] : # Il n'y a aucun paramètre. # On ajoute un paramètre pour y mettre notre nom de fichier # avec notre extension. commandeEtParam.append('') commandeEtParam[1]= os.path.splitext(fileStlName)[0]+ extFileNameParamCommandeALancer subprocess.Popen(commandeEtParam,close_fds=True) time.sleep(delai)# Pause de durée delai secondes def run(objs=Gui.Selection.getSelection(), dev=deviation): """ fonction principale Arguments: doc : le document actif objs : les objets que l'on a au préalable sélectionnés dev : la valeur de la propriété déviation qui sera imposée avant la génération du stl Returns Ne retourne rien Example: run() """ # Boite de dialogue de ce que l'on veut faire formBoiteDialogueSTL=BoiteDialogueSTLGuiClass(mw) if not formBoiteDialogueSTL.exec_() : return dev=formBoiteDialogueSTL.precision.value() # on mémorise les solide sélectionnés et leur déviation, # on fait un touch() de leur child pour prise en compte dans recompute : dictionnaireOrigineDeviation=memoriseObjEtDeviation(objs,dev) if len(dictionnaireOrigineDeviation)==0: # si on a trouvé aucun solide QtWidgets.QMessageBox.information(mw, 'Attention',\ '-Sélectionnez un ou plusieurs solide avant de lancer la macro') return # on récupère le nom du fichier où l'on devra enregistrer le stl fileStlName=nameFileStl(dictionnaireOrigineDeviation) if fileStlName == '' : return # l'export lui-même. On passe en paramètre les solides qui # sont dans dictionnaireOrigineDeviation if formBoiteDialogueSTL.checkboxGenererSTL.isChecked(): Mesh.export(list(dictionnaireOrigineDeviation.keys()), fileStlName) print('Export fait') # restitution de la propriété deviation d'origine pour les solide : restitueDeviation(dictionnaireOrigineDeviation) recalcul(dictionnaireOrigineDeviation) if formBoiteDialogueSTL.checkboxLancerSTL.isChecked(): #lance par exemple cura si cura a été associé aux fichiers *.stl openFile(fileStlName) lanceCommmandes(commandes,fileStlName,formBoiteDialogueSTL) # lance les commandes que l'on a décrites dans commandes if __name__ == '__main__': run()
Macro Sketch Constraint From Spreadsheet :
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
https://wiki.freecad.org/Macro_Sketch_C ... adsheet/fr
Re: Macro pour obtenir un stl avec des arrondis parfaits / Piloter octoprint
Il y a dû y avoir un problème avec les 'quote' dans ton message.
Après il ne te restera plus qu'à intégrer un mécanisme de traduction pour toucher un public international.
Exact2cv001 wrote: ↑Thu Dec 17, 2020 4:43 pmLes mystères de l'informatique...* Je suis surpris que ça marche en fait car il te manque une ligne à la fin du initUI : 'self.setLayout(lay)'
Je l'ai mis en dernière ligne mais je suppose que l'on peut le mettre où l'on veut après le ' lay = QtWidgets.QGridLayout(self) '
Oui c'est "normal" que tu n'es pas de QtGui. En PySide2, il sert pour des choses graphiques mais pas liées au widget, genre manipulation d'images, du clavier, de la souris, ...Bravo pour l'explication ! tout compris ! (enfin je crois ) J'ai donc fait la modif. Du coup d'ailleurs, j'ai pu retirer QtGui dans l'import :
from PySide2 import QtCore, QtWidgets car sauf erreur, il n'y a pas d'endroit où j'avais besoin de QtGui
Par contre, j'ai toujours les messages "warning"
" butBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
17:07:52 C:/Datas/data/Imprimante 3D/Freecad/ExportStlV14.FCMacro:183: DeprecationWarning: an integer is required (got type PySide2.QtWidgets.QDialogButtonBox.StandardButton). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python."
Après il ne te restera plus qu'à intégrer un mécanisme de traduction pour toucher un public international.