How to Connect External Sensors to FreeCAD Using Python
Connecting external hardware and sensors to FreeCAD transforms the parametric 3D modeling software into an interactive digital twin or real-time visualization platform. By leveraging FreeCAD’s embedded Python interpreter along with serial communication libraries, you can ingest live telemetry from microcontrollers like Arduino, Raspberry Pi, or ESP32 and directly manipulate 3D geometry properties. This guide outlines the essential steps to configure communication, handle asynchronous data streams, and update FreeCAD document objects in real time.
1. Install Required Python Libraries
FreeCAD uses its own internal Python environment. To communicate with
external hardware over USB or serial ports, you must install the
pyserial library into FreeCAD's Python environment.
Open the FreeCAD Python console (View > Panels > Python console) and run:
import subprocess
import sys
subprocess.run([sys.executable, "-m", "pip", "install", "pyserial"])2. Configure Hardware Data Output
Set up your microcontroller (e.g., Arduino) to read your sensor (such as an ultrasonic distance sensor, potentiometer, or IMU) and print structured values over the serial interface.
Example Arduino snippet:
void setup() {
Serial.begin(9600);
}
void loop() {
int sensorValue = analogRead(A0); // Read sensor
Serial.println(sensorValue); // Send raw value followed by newline
delay(50); // Limit transmission rate
}3. Implement Non-Blocking Data Polling with PySide
Reading serial data inside a continuous while loop
within FreeCAD will block the main application thread and freeze the
user interface. To prevent this, use PySide's QTimer to
poll the serial port periodically without interrupting FreeCAD’s event
loop.
Run the following script inside FreeCAD's Macro editor (Macro > Macros > Create):
import FreeCAD as App
import FreeCADGui as Gui
from PySide import QtCore
import serial
# Configure serial connection
SERIAL_PORT = "COM3" # Replace with your port (e.g., '/dev/ttyUSB0' on Linux)
BAUD_RATE = 9600
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=0.1)
except Exception as e:
App.Console.PrintError(f"Failed to connect to serial port: {e}\n")
ser = None
def read_sensor_and_update():
if ser and ser.in_waiting > 0:
try:
raw_line = ser.readline().decode("utf-8").strip()
if raw_line:
sensor_value = float(raw_line)
# Reference your target 3D object in the active document
doc = App.ActiveDocument
obj = doc.getObject("Box") # Name of your target object
if obj:
# Update a property based on sensor data
# Example: Mapping sensor value to box height
obj.Height = sensor_value
# Recompute the document to reflect visual changes
doc.recompute()
except ValueError:
pass # Handle incomplete serial strings safely
# Setup QTimer for polling every 50 milliseconds
if ser and ser.is_open:
timer = QtCore.QTimer()
timer.timeout.connect(read_sensor_and_update)
timer.start(50)
# Keep a reference to prevent garbage collection
App.sensor_timer = timer
App.Console.PrintMessage("Sensor listening active.\n")4. Drive Parametric Properties
Once the data stream is linked via QTimer, you can alter
any standard FreeCAD property:
- Linear Position: Modify
obj.Placement.Base.x,y, orzto simulate actuators or positioning stages. - Rotational Orientation: Apply sensor readings from
gyroscopes to
obj.Placement.Rotationusing Euler angles or quaternions. - Dimensions: Update parametric parameters such as
Length,Width,Radius, or custom Sketcher constraints to show dynamic deformation or fluid levels.
Always call doc.recompute() after modifying values to
ensure the 3D viewport updates immediately.
5. Stopping the Data Stream
To safely close the connection and stop polling without restarting FreeCAD, execute:
if hasattr(App, "sensor_timer"):
App.sensor_timer.stop()
del App.sensor_timer
if "ser" in locals() and ser.is_open:
ser.close()
App.Console.PrintMessage("Sensor communication stopped.\n")