| Description |
|---|
| Permet le contrôle externe de FreeCAD à des fins d'automatisation. Version macro : 1.0 Date dernière modification : 2026-01-30 Version FreeCAD : Toutes Téléchargement : Media:Macro_server.svg Auteur: Jjustra |
| Auteur |
| Jjustra |
| Téléchargement |
| Media:Macro_server.svg |
| Liens |
| Page des macros Comment installer une macro Comment créer une barre d'outils |
| Version Macro |
| 1.0 |
| Dernière modification |
| 2026-01-30 |
| Version(s) FreeCAD |
| Toutes |
| Raccourci clavier |
| None |
| Voir aussi |
| None |
Outil utile pour l'automatisation de FreeCAD. L'objectif est de créer un programme compact, polyvalent et fonctionnant uniquement au « niveau supérieur ».
Gestion à distance de FreeCAD, sans modification réelle du modèle (pour cela, voir Extensions).
Vous avez besoin d'un client pour envoyer des commandes à ce serveur. Un utilitaire simple en ligne de commande Python est inclus à titre d'implémentation de référence.
Cela vous permet de sélectionner globalement (mais toujours dans l'espace du serveur et non dans FreeCAD lui-même) :
Les opérations suivantes peuvent nécessiter un document/objet/tableur/chemin d'accès.
(la plupart de ces arguments sont facultatifs)
par exemple :
(la liste de tous les documents est renvoyée par la commande « D »)
Cela vous permet également d'obtenir la liste des :
Et vous pouvez même :
Vous démarrez le serveur en exécutant la macro. Vous pouvez l'arrêter de la même manière. La macro agit donc comme un simple bouton d'activation, et la véritable magie opère ailleurs.
Le script client inclus accepte les commandes sous forme d'arguments de ligne de commande. Chaque argument correspond à une commande.
par exemple : ./freecad-ctl.py 'dMy_document_1' C '!B1 123' C
Cela sélectionne « My_document_1 », renvoie toutes les valeurs des cellules dans la feuille de calcul par défaut, définit la cellule B1 sur 123 et renvoie à nouveau toutes les valeurs.
N'oubliez pas de définir le chemin d'accès ROOT vers les fichiers stdin/stdout du serveur, à la fois dans le client ET dans le serveur, avant de l'exécuter (variable ROOT au début des deux codes source). Les fichiers stdin/stdout seront créés automatiquement.
Les informations sont échangées via deux fichiers. Du point de vue du serveur, l'un est une entrée, l'autre une sortie. Du point de vue du client, l'entrée du serveur est sa sortie et vice versa.
Chaque commande renvoie une réponse.
<one-letter-mod><name> <data string>
par exemple :
!B1 123
où :
Cela définit la cellule B1 (dans le document global ou actif et dans la feuille de calcul) sur la valeur 123.
I : pour obtenir l'identifiant du serveur
D : pour obtenir la liste des documents ouverts sous la forme : d<document>
O[<document>] : pour obtenir la liste des objets sous la forme : o<object> <label>
C[<document>] [<spreadsheet>] : pour obtenir la liste des cellules sous la forme : c<cell-id> <value>
d<document> : pour sélectionner un document
s[<document>] <spreadsheet> : pour sélectionner une feuille de calcul
o[<document>] <object-name> : pour sélectionner un objet
p <file-path> : pour sélectionner/définir un chemin d'accès
@<cell-id> [<spreadsheet>] : pour obtenir la valeur d'une cellule sous la forme : c<cell-id> <value>
!<cell-id> <value> : pour définir la valeur d'une cellule
r[<document>] : pour recalculer un document
e[<object>] [<file-path>] : pour exporter un objet sélectionné
Sélectionnez le document My_document_0.
dMy_document_0
Récupérez la liste des objets à partir de My_document_1.
OMy_document_1
Récupérez la liste des objets à partir de My_document_0.
O
Désélectionnez le document.
d
Récupérez la liste des objets du document actif de FreeCAD
O
Récupérez la liste des cellules de la feuille de calcul par défaut dans My_document_0
CMy_document_0
Récupérez la liste des cellules de My_spreadsheet dans My_document_0
CMy_document_0 My_spreadsheet
Définissez un chemin d'accès
p /tmp/exported-part.stl
Exportez Object_0 vers un chemin d'accès sélectionné
eObject_0
Si des fonctions supplémentaires sont nécessaires, au-delà des capacités de ce serveur, des ajouts peuvent être facilement réalisés.
Chaque commande doit renvoyer une réponse.
Macro_Server.FCMacro
## created on ##
# OS: Windows 10 build 19045
# Architecture: x86_64
# Version: 1.0.2.39319 (Git) Conda
# Build type: Release
# Branch: (HEAD detached at 1.0.2)
# Hash: 256fc7eff3379911ab5daf88e10182c509aa8052
# Python 3.11.13, Qt 5.15.15, Coin 4.0.3, Vtk 9.3.0, OCC 7.8.1
# Locale: Czech/Czech Republic (cs_CZ)
# Stylesheet/Theme/QtStyle: FreeCAD Dark.qss/FreeCAD Dark/Fusion
# Installed mods:
# * freecad.gears 1.3.0
# * sheetmetal 0.7.58
__Title__="server"
__Author__ = "Jjustra"
__Version__ = "1.0"
__Date__ = "2026-01-30"
__Comment__ = "This is the comment of the macro"
__Web__ = "https://forum.freecad.org"
__Wiki__ = "https://wiki.freecad.org/index.php?title=Macro_server"
__Icon__ = "/usr/lib/freecad/Mod/plugins/icons/server"
__IconW__ = "C:/Users/YourUserName/AppData/Roaming/FreeCAD"
__Help__ = "start the macro and run client"
__Status__ = "stable"
__Requires__ = "freecad all"
__Communication__ = "https://wiki.freecad.org/index.php?title=User:Jjustra"
import FreeCAD
# Location of the input/output files
ROOT = 'c:/tmp'
if 'server' not in dir(FreeCAD):
# Server not running - we will start it then
import os
from threading import Event,Thread
import Mesh
import time
## --- brcko lib start --- ##
import sys,os
stdin = []
stdinpos = []
stdout = []
def addstdin(path):
global stdin,stdinpos
if os.path.exists(path):
pos = os.path.getsize(path)
else:
pos = 0
stdin.append(path)
stdinpos.append(pos)
def addstdout(path):
global stdout
stdout.append(path)
def getstdin():
global stdin,stdinpos
data = []
for i,path in enumerate(stdin):
if not os.path.exists(path): continue
f = open(path)
f.seek(stdinpos[i])
_data = f.read()
f.close()
# Line-buffered
_i = _data.rfind('\n') + 1
_data = _data[:_i]
data.append(_data)
stdinpos[i] += _i
return data
def putstdout(data,i=0):
global stdout
if i >= len(stdout): return
path = stdout[i]
if path == '-':
sys.stdout.write(data)
else:
f = open(path,'a', encoding="utf-8", newline='\n')
f.write(data)
f.close()
## --- brcko lib end --- ##
_cb_d = {}
id = 0
path = ''
_id = 0
_doc = 0
_ss = 0
_obj = 0
_path = 0
## --- utils start --- ##
def getDoc(n=0):
'''Get document'''
global _doc
if n:
try:
doc = App.getDocument(n)
except NameError:
return 0
elif _doc:
doc = _doc
else:
doc = App.ActiveDocument
return doc
def getSS(s=0,n=0):
'''Get spreadsheet'''
global _ss
doc = getDoc(n)
if not doc: return 0
if s:
ss = doc.getObject(s)
elif _ss:
ss = _ss
else:
ss = doc.getObject('Spreadsheet')
return ss
def getObj(s=0,n=0):
'''Get object'''
global _obj
doc = getDoc(n)
if not doc: return 0
if s:
obj = doc.getObject(s)
elif _obj:
obj = _obj
else:
obj = doc.ActiveObject
return obj
def getPath(s=0, default_fn=0):
'''Get file path (for export mainly)'''
global _path,ROOT
path = ''
if not default_fn:
default_fn='file'
if s:
path = s
elif _path:
path = _path
else:
path = ROOT
path = path.replace('\\','/')# make it objectively right ;)
if os.path.isdir(path):
path = path + '/' + default_fn
if '.' not in path.split('/')[-1]:
path += '.stl'
return path
def dlgln(ln):
m = ''
n = ''
s = ''
m = ln[0]
ln = ln[1:]
if ln:
if ln[0] == ' ':
s = ln[1:]
else:
if ' ' in ln:
n,s = ln.split(' ',1)
else:
n = ln
return m,n,s
def register(m, fc):
'''Register command function with respective m-code'''
global _cb_d
_cb_d[m] = fc
## --- utils end --- ##
## --- thread function start --- ##
def _th(qe,id):
global _cb_d,_id
while not qe.is_set():
for data in getstdin():
for ln in data.split('\n'):
if not ln: continue
print('#D : server : got input :',ln)
m,n,s = dlgln(ln)
if m in _cb_d:
if not _id or _id == id or _cb_d[m] == fc_selectId:
# Only process commands if :
# id is empty
# id equals this instance's id
# we are about to execute selectId command
_cb_d[m](m,n,s)
else:
print('#E : server : unknown command :',m)
time.sleep(1)
## --- thread function end --- ##
## --- stdlib start --- ##
def fc_getId(m,n,s):
resp = 'i%s\n' % FreeCAD.server[0]
#resp += '#I : server : fc_getId : done\n'
putstdout(resp)
def fc_docList(m,n,s):
resp = ''
for k,_ in App.listDocuments().items():
resp += 'd%s\n' % k
if not resp:
resp += '#I : server : fc_docList : empty\n'
putstdout(resp)
def fc_objList(m,n,s):
resp = ''
doc = getDoc(n)
if not doc:
resp += '#E : server : fc_objList : no doc\n'
else:
for obj in doc.Objects:
resp += 'o%s %s\n' % (obj.Name, obj.Label)
if not resp:
resp += '#I : server : fc_objList : empty\n'
putstdout(resp)
def fc_cellsList(m,n,s):
resp = ''
ss = getSS(s,n)
if not ss:
resp += '#E : server : fc_cellsList : no sheet\n'
else:
# Build list of non-empty cells
for k in ss.getNonEmptyCells():
v = ss.get(k)
resp += 'c%s %s\n' % (k,v)
if not resp:
resp += '#I : server : fc_cellsList : empty\n'
# Sends it
putstdout(resp)
def fc_selectId(m,n,s):
global _id
_id = n
putstdout('#I : server : fc_selectId : done\n')
def fc_selectDoc(m,n,s):
global _doc
resp = ''
if n:
try:
_doc = App.getDocument(n)
except NameError:
resp = '#E : server : fc_selectDoc : no doc\n'
else:
_doc = 0
if not resp:
resp = '#I : server : fc_selectDoc : done\n'
putstdout(resp)
def fc_selectSpreadsheet(m,n,s):
global _ss
resp = ''
doc = getDoc(n)
if not doc:
resp += '#E : server : fc_selectSpreadsheet : no doc\n'
else:
if s:
_ss = doc.getObject(s)
else:
_ss = 0
resp = '#I : server : fc_selectSpreadsheet : done\n'
putstdout(resp)
def fc_selectObj(m,n,s):
global _obj
resp = ''
doc = getDoc(n)
if not doc:
resp += '#E : server : fc_selectObj : no doc\n'
else:
if s:
_obj = doc.getObject(s)
else:
_obj = 0
if not resp:
resp = '#I : server : fc_selectObj : done\n'
putstdout(resp)
def fc_selectPath(m,n,s):
global _path
if s:
_path = s
else:
_path = 0
putstdout('#I : server : fc_selectPath : done\n')
def fc_getCell(m,n,s):
resp = ''
ss = getSS(s)
if not ss:
resp = '#E : server : fc_getCell : no sheet\n'
else:
try:
v = ss.get(n)
resp = 'c%s %d\n' % (n, v)
except ValueError:
resp = '#E : server : fc_getCell : no cell\n'
#resp += '#I : server : fc_getCell : done\n'
putstdout(resp)
def fc_setCell(m,n,s):
resp = ''
ss = getSS()
if not ss:
resp = '#E : server : fc_setCell : no sheet\n'
else:
v = ss.set(n,s)
resp = '#I : server : fc_setCell : done\n'
putstdout(resp)
def fc_recompute(m,n,s):
getDoc(n).recompute()
putstdout('#I : server : fc_recompute : done\n')
def fc_export(m,n,s):
resp = ''
obj = getObj(n)
path = getPath(s, obj.Label)
if not obj:
resp = '#E : server : fc_export : no object\n'
else:
if not path:
resp = '#E : server : fc_export : no path\n'
else:
if hasattr(Mesh, "exportOptions"):
options = Mesh.exportOptions(path)
Mesh.export([obj], path, options)
else:
Mesh.export([obj], path)
resp += '#I : server : fc_export : done : %s\n' % path
putstdout(resp)
def fc_(m,n,s):
resp = ''
putstdout(resp)
def fc_(m,n,s):
resp = ''
putstdout(resp)
def fc_(m,n,s):
resp = ''
putstdout(resp)
## --- stdlib end --- ##
## --- Extensions start --- ##
## --- Extensions end --- ##
# Register all command functions with their codes
register('I', fc_getId)
register('D', fc_docList)
register('O', fc_objList)
register('C', fc_cellsList)
register('i', fc_selectId)
register('d', fc_selectDoc)
register('s', fc_selectSpreadsheet)
register('o', fc_selectObj)
register('p', fc_selectPath)
register('@', fc_getCell)
register('!', fc_setCell)
register('r', fc_recompute)
register('e', fc_export)
#register('', fc_)
## --- Extensions registration start --- ##
#register('', fc_)
## --- Extensions registration end --- ##
# Setup brcko lib
path = os.path.abspath(ROOT)
# in <-> in
# out <-> out
addstdin('%s/server.stdin' %path)
addstdout('%s/server.stdout' %path)
# Little bit of flexing O:)
genid = lambda seed,lvl: 'QWERTYUIOPASDFGHJKLZXCVBNM'[seed%26]+genid((seed*12345)%56789,lvl-1) if lvl>0 else ''
id = genid(int(time.time()*1000),8)
qe = Event()
th = Thread(target=_th,args=(qe,id))
FreeCAD.server = (id,qe,th)
th.start()
print('#I : server : started :',id)
else:
# Server is running - let's stop it now
(id,qe,th) = FreeCAD.server
qe.set()
del(FreeCAD.server)
print('#I : server : stopped')
freecad-ctl.py
#!/usr/bin/python3
import os
import time
# Location of the input/output files
ROOT = 'c:/tmp'
## --- brcko lib start --- ##
import sys,os
stdin = []
stdinpos = []
stdout = []
def addstdin(path):
global stdin,stdinpos
if os.path.exists(path):
pos = os.path.getsize(path)
else:
pos = 0
stdin.append(path)
stdinpos.append(pos)
def addstdout(path):
global stdout
stdout.append(path)
def getstdin():
global stdin,stdinpos
data = []
for i,path in enumerate(stdin):
if not os.path.exists(path): continue
f = open(path)
f.seek(stdinpos[i])
_data = f.read()
f.close()
# Line-buffered
_i = _data.rfind('\n') + 1
_data = _data[:_i]
data.append(_data)
stdinpos[i] += _i
return data
def putstdout(data,i=0):
global stdout
if i >= len(stdout): return
path = stdout[i]
if path == '-':
sys.stdout.write(data)
else:
f = open(path,'a', encoding="utf-8", newline='\n')
f.write(data)
f.close()
## --- brcko lib end --- ##
# Process command line arguments
cmd = ''
if len(sys.argv) > 1:
# Use command line arguments to build command script
for a in sys.argv[1:]:
if a == '-':
# Read list of commands from (real) stdin
cmd + sys.stdin.read().replace('\r','\n')
else:
cmd += a
cmd += '\n'
#else:
# Default : get document list
# cmd = 'D\n'
# Setup brcko lib
path = os.path.abspath(ROOT)
# in <-> out
# out <-> in
addstdin('%s/server.stdout' %path)
addstdout('%s/server.stdin' %path)
# Send command script
if cmd:
putstdout(cmd)
try:
# Wait for response (indicated by file size change / file creation)
if not os.path.exists(stdin[0]):
while not os.path.exists(stdin[0]):
time.sleep(.1)
else:
sz = os.path.getsize(stdin[0])
while sz == os.path.getsize(stdin[0]):
time.sleep(.1)
# Just to be sure write ended
time.sleep(.5)
except KeyboardInterrupt:
sys.exit()
# Print response
for data in getstdin():
print(data)