FreeCAD Logo FreeCAD 1.0
  • Anglais Afrikaans Arabe Biélorusse Catalan Tchèque Allemand Grec Espagnol Espagnol Basque Finnois Philippin Français Galicien Croate Hongrois Indonésien Italien Japonais Kabyle Coréen Lituanien Néerlandais Norvégien classique Polonais Portugais Portugais Roumain Russe Slovaque Slovène Serbe Suédois Turc Ukrainien Valencien Vietnamien Chinois Chinois
  • Fonctions
  • Télécharger
  • Blog
  • Documentation
    Index de la documentation Premiers pas Documentation pour les utilisateurs Manuel de FreeCAD Documentation des ateliers Documentation sur le codage en Python Documentation pour les développeurs Tutoriels Foire aux questions Politique de confidentialité À propos de FreeCAD
  • Contribuer
    Comment aider Sponsor Signaler un bogue Faire une demande de modification (PR) Emplois et financements Guide pour les contributions Manuel pour les développeurs Traductions
  • Communauté
    Code de conduite Forum The FPA GitHub GitLab Codeberg Mastodon Matrix IRC IRC via Webchat Gitter Discord Reddit Twitter Facebook LinkedIn Calendrier
  • ♥ Donate

Donate

$
Informations SEPA
Veuillez configurer votre virement bancaire SEPA pour:
Beneficiary: The FreeCAD project association
IBAN: BE04 0019 2896 4531
BIC/SWIFT: GEBABEBBXXX
Agence bancaire: BNP Paribas Fortis
Adresse: Rue de la Station 64, 1360 Perwez, Belgium

While Stripe doesn't support monthly donations, you can still become a sponsor! Simply make a one-time donation equivalent to 12 months of support, and you'll gain access to the corresponding sponsoring tier. It's an easy and flexible way to contribute.

If you are not sure or not able to commit to a regular donation, but still want to help the project, you can do a one-time donation, of any amount.

Choose freely the amount you wish to donate one time only.

You can support FreeCAD by sponsoring it as an individual or organization through various platforms. Sponsorship provides a steady income for developers, allowing the FPA to plan ahead and enabling greater investment in FreeCAD. To encourage sponsorship, we offer different tiers, and unless you choose to remain anonymous, your name or company logo will be featured on our website accordingly.

from 1 USD / 1 EUR per month. You will not have your name displayed here, but you will have helped the project a lot anyway. Together, normal sponsors maintain the project on its feet as much as the bigger sponsors.

from 25 USD / 25 EUR per month. Your name or company name is displayed on this page.

from 100 USD / 100 EUR per month. Your name or company name is displayed on this page, with a link to your website, and a one-line description text.

from 200 USD / 200 EUR per month. Your name or company name and logo displayed on this page, with a link to your website and a custom description text. Companies that have helped FreeCAD early on also appear under Gold sponsors.

Instead of donating each month, you might find it more comfortable to make a one-time donation that, when divided by twelve, would give you right to enter a sponsoring tier. Don't hesitate to do so!

Choose freely the amount you wish to donate each month.

Please inform your forum name or twitter handle as a notein your transfer, or reach to us, so we can give you proper credits!

Macro Server

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

Description

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) :

  • document
  • objet
  • feuille de calcul
  • chemin d'accès (principalement pour l'exportation)

Les opérations suivantes peuvent nécessiter un document/objet/tableur/chemin d'accès.

(la plupart de ces arguments sont facultatifs)

  • la valeur donnée est utilisée en premier
  • puis la valeur globale
  • et enfin la sélection active/par défaut dans FreeCAD lui-même

par exemple :

  • Vous pouvez obtenir la liste des objets dans le document actif à l'aide de la commande « O ».
  • Vous pouvez également obtenir la liste des objets dans un document concret à l'aide de la commande « Oconcrete_document ».

(la liste de tous les documents est renvoyée par la commande « D »)

Cela vous permet également d'obtenir la liste des :

  • documents ouverts
  • objets avec leur nom et leur étiquette
  • cellules dans une feuille de calcul avec leurs valeurs

Et vous pouvez même :

  • obtenir la valeur d'une cellule
  • définir la valeur d'une cellule
  • recalculer (nécessaire après avoir défini les cellules)
  • exporter

Utilisation

Serveur

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.

Client

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.

Remarque

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.

Protocole de communication

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.

Format des commandes

<one-letter-mod><name> <data string>

par exemple :

!B1 123

où :

  • ! est mod/commande
  • B1 est le nom
  • 123 est la valeur de la chaîne de caractère (peut être un autre nom ou simplement une valeur, selon la situation)

Cela définit la cellule B1 (dans le document global ou actif et dans la feuille de calcul) sur la valeur 123.

Commandes

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é

Exemples

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

Extensions

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.

Script

Macro serveur

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')

Script client

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)

Cette page est extraite de https://wiki.freecad.org/Macro_Server

Contactez-nous !
Forum GitHub Mastodon Matrix IRC Gitter.im Discord Reddit Twitter Facebook LinkedIn

© L'équipe FreeCAD. Crédits des images de la page d'accueil (de haut en bas) : ppemawm, r-frank, epileftric, regis, rider_mortagnais, bejant.

Ce projet est soutenu par : , KiCad Services Corp. et autres parrains

GitHubAméliorer cette page sur GitHub