SimplyPrint Emulator Feasibility Study¶
Project: Forma3D.Connect
Version: 1.0
Date: January 28, 2026
Status: Research / Proposal
Executive Summary¶
This document evaluates the feasibility of building a 3D printer emulator to test Forma3D.Connect software integration with SimplyPrint without requiring physical printer hardware. Based on research conducted in January 2026, a fully functional virtual printer emulator is not only feasible but already exists as an official example in the SimplyPrint WebSocket client library.
Recommendation¶
Leverage the existing simplyprint-ws-client Python library to create a virtual printer emulator for testing. The library provides:
- A complete VirtualClient example implementation
- Full WebSocket communication with SimplyPrint servers
- Realistic simulation of printer states, temperatures, and print progress
- Automatic setup code generation for registering the virtual printer in SimplyPrint
Problem Statement¶
Testing Forma3D.Connect's integration with SimplyPrint currently requires: - Physical 3D printer hardware - Maintaining printer connectivity and operational status - Executing actual print jobs for end-to-end testing - Limited ability to test edge cases (errors, failures, specific states)
A virtual printer emulator would enable: - Development without physical hardware - Automated integration testing in CI/CD pipelines - Testing of edge cases and error scenarios - Parallel testing with multiple virtual printers - Consistent, reproducible test environments
SimplyPrint WebSocket Client Library¶
Overview¶
SimplyPrint provides an official Python library for connecting custom printer implementations to their platform.
| Property | Details |
|---|---|
| Package Name | simplyprint-ws-client |
| Current Version | 1.0.1rc42 (pre-release) |
| Python Requirement | Python 3.9+ |
| License | AGPL-3.0-or-later |
| Repository | github.com/SimplyPrint/simplyprint-ws-client |
| PyPI | pypi.org/project/simplyprint-ws-client |
Key Dependencies¶
psutil>=6.0.0
pydantic>=2.11.7
aiohttp>=3.12.15
click>=8.2.1
sentry-sdk>=2.35.1
Existing Virtual Printer Implementation¶
The library includes a complete VirtualClient example that already implements a functional virtual printer emulator.
Source Location¶
https://github.com/SimplyPrint/simplyprint-ws-client/blob/main/example/virtual_client.py
Features Implemented¶
| Feature | Implementation Status | Details |
|---|---|---|
| Printer identification | ✅ Complete | Name, version, firmware info |
| Temperature simulation | ✅ Complete | Bed and tool temperatures with realistic smoothing |
| Print job progress | ✅ Complete | Exponential smoothing for realistic progress |
| File download simulation | ✅ Complete | Simulates download progress |
| G-code command handling | ✅ Complete | Temperature commands (M104, M140) |
| Print lifecycle | ✅ Complete | Start, cancel, complete states |
| Virtual camera | ✅ Complete | Static test image feed |
| Material/filament info | ✅ Complete | Multiple materials per tool |
| Mesh data generation | ✅ Complete | Fake bed level visualization data |
| Status transitions | ✅ Complete | OPERATIONAL → PRINTING → CANCELLING, etc. |
Code Architecture¶
class VirtualClient(DefaultClient[VirtualConfig], ClientCameraMixin):
"""Virtual 3D printer that simulates all printer behaviors"""
# Core lifecycle methods
async def init(self): ... # Initialize printer state
async def tick(self, _): ... # Update temperatures, progress
async def halt(self): ... # Stop operations
async def teardown(self): ... # Cleanup
# Event handlers
async def on_connected(self): ... # WebSocket connected
async def on_gcode(self, data): ... # G-code commands received
async def on_file(self, data): ... # File download request
async def on_start_print(self, _): ... # Print job started
async def on_cancel(self, _): ... # Print cancelled
Temperature Simulation¶
The virtual client uses exponential smoothing for realistic temperature transitions:
def expt_smooth(target, actual, alpha, dt) -> float:
return actual + (target - actual) * (1.0 - math.exp(-alpha * dt))
This creates natural heating/cooling curves that match real printer behavior.
Implementation Approach¶
Option 1: Direct Library Usage (Recommended)¶
Use the existing simplyprint-ws-client library directly:
# Install the library
pip install simplyprint-ws-client
# Or with uv (recommended)
uv pip install simplyprint-ws-client
Entry point example:
from simplyprint_ws_client import (
ClientApp,
ConfigManagerType,
ClientSettings,
ConnectionMode,
)
from simplyprint_ws_client.shared.logging import setup_logging
# Import VirtualClient from example or custom implementation
from virtual_client import VirtualClient, VirtualConfig
if __name__ == "__main__":
settings = ClientSettings(
name="Forma3D-VirtualPrinter",
mode=ConnectionMode.MULTI, # Support multiple virtual printers
client_factory=VirtualClient,
config_factory=VirtualConfig,
allow_setup=True,
config_manager_t=ConfigManagerType.JSON,
development=True, # Enable development mode
)
setup_logging(settings)
app = ClientApp(settings)
# Add a new virtual printer or use existing config
if len(app.config_manager.get_all()) > 0:
config = app.config_manager.get_all()[0]
else:
config = VirtualConfig.get_new()
client = app.add(config)
print(f"Setup code: {client.config.short_id}")
app.run_blocking()
Option 2: Fork and Customize¶
Fork the simplyprint-ws-client repository and customize VirtualClient for specific testing needs:
- Add configurable failure scenarios
- Implement specific printer model behaviors
- Add telemetry/logging for test analysis
- Integrate with our test framework
Option 3: TypeScript/Node.js Implementation¶
If Python is not preferred, implement a Node.js client based on the WebSocket protocol:
// Conceptual - would require reverse-engineering the protocol
interface PrinterState {
status: 'operational' | 'printing' | 'paused' | 'error';
bedTemp: { actual: number; target: number };
toolTemp: { actual: number; target: number };
progress: number;
}
class VirtualPrinter {
private ws: WebSocket;
private state: PrinterState;
async connect(): Promise<void> {
// Connect to SimplyPrint WebSocket server
}
async tick(): Promise<void> {
// Update state and send to server
}
}
Note: This would require significant protocol reverse-engineering and is not recommended given the official Python library exists.
Setup and Registration Process¶
How Virtual Printer Registration Works¶
- First Run: Library generates a unique
unique_id(UUID) - Server Connection: Connects to SimplyPrint WebSocket servers
- Setup Code: Server assigns a
short_id(e.g., "9ZZM") - Account Linking: Use setup code at simplyprint.io/setup-guide
- Configuration Saved: Token and settings persist to JSON file
First Run Output Example¶
$ python -m example
PrinterConfig(
id=0,
token='0',
name=None,
in_setup=None,
short_id=None,
unique_id='08086eeb-812b-4bda-9159-99852aff509b'
)
After Registration¶
$ python -m example
PrinterConfig(
id=123456,
token='595a482a-745f-41a6-88c7-1402cffa1e9c',
name='Virtual Printer 1',
in_setup=False,
short_id='9ZZM',
unique_id='08086eeb-812b-4bda-9159-99852aff509b'
)
Testing Scenarios Enabled¶
Basic Functionality¶
| Scenario | Virtual Printer Action |
|---|---|
| Printer online detection | Connect and report OPERATIONAL status |
| Temperature monitoring | Report simulated bed/tool temperatures |
| Print job assignment | Accept file, simulate download, start print |
| Progress tracking | Report incremental progress with realistic timing |
| Job completion | Transition to completed state, cool down temps |
| Job cancellation | Handle cancel command, transition to OPERATIONAL |
Edge Cases and Error Scenarios¶
| Scenario | Implementation Approach |
|---|---|
| Connection loss | Terminate WebSocket mid-operation |
| Temperature runaway | Report abnormally high temperatures |
| Print failure | Transition to ERROR state during print |
| File download failure | Report failed download state |
| Firmware crash | Disconnect unexpectedly |
| Multiple printers | Run multiple VirtualClient instances |
Automated Testing Integration¶
import pytest
import asyncio
from virtual_printer_emulator import VirtualPrinter
@pytest.fixture
async def virtual_printer():
printer = VirtualPrinter()
await printer.connect()
yield printer
await printer.disconnect()
@pytest.mark.asyncio
async def test_print_job_lifecycle(virtual_printer):
# Start print
await virtual_printer.start_print("test_file.gcode")
assert virtual_printer.status == "printing"
# Simulate progress
await virtual_printer.simulate_progress(100)
assert virtual_printer.status == "operational"
assert virtual_printer.last_job_completed == True
Multi-Printer Testing¶
The library supports ConnectionMode.MULTI for simulating print farms:
settings = ClientSettings(
name="Forma3D-PrintFarm",
mode=ConnectionMode.MULTI,
# ... other settings
)
app = ClientApp(settings)
# Add multiple virtual printers
for i in range(5):
config = VirtualConfig.get_new()
config.name = f"Virtual Printer {i+1}"
app.add(config)
app.run_blocking()
This enables testing: - Print queue distribution - Load balancing across printers - Farm-wide status monitoring - Concurrent job handling
Integration with Forma3D.Connect¶
Architecture Diagram¶
┌─────────────────────────────────────────────────────────────────┐
│ Development/Testing Environment │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌─────────────────────────────┐ │
│ │ Virtual Printer │ ◄─────► │ SimplyPrint Cloud │ │
│ │ Emulator │ WS │ (Production Servers) │ │
│ │ (Python) │ │ │ │
│ └──────────────────┘ └─────────────┬───────────────┘ │
│ │ │
│ │ Webhooks/API │
│ │ │
│ ┌─────────────▼───────────────┐ │
│ │ Forma3D.Connect API │ │
│ │ (NestJS) │ │
│ └─────────────┬───────────────┘ │
│ │ │
│ ┌─────────────▼───────────────┐ │
│ │ Forma3D.Connect Web │ │
│ │ (React PWA) │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Proposed Directory Structure¶
forma-3d-connect/
├── tools/
│ └── virtual-printer/
│ ├── pyproject.toml
│ ├── README.md
│ ├── src/
│ │ └── virtual_printer/
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── client.py # Custom VirtualClient
│ │ ├── config.py # Custom config with scenarios
│ │ └── scenarios/
│ │ ├── happy_path.py
│ │ ├── error_states.py
│ │ └── print_farm.py
│ └── tests/
│ └── test_scenarios.py
Feasibility Assessment¶
Technical Feasibility: ✅ HIGH¶
| Factor | Assessment |
|---|---|
| Library maturity | Pre-release but functional, actively developed |
| Documentation | Tutorial available, example code provided |
| Protocol stability | WebSocket protocol is stable |
| Python compatibility | Python 3.9+ widely available |
| Async support | Full asyncio support |
Implementation Effort: LOW¶
| Task | Estimated Effort |
|---|---|
| Install and configure library | 1-2 hours |
| Register virtual printer | 30 minutes |
| Basic testing setup | 2-4 hours |
| Custom scenarios | 4-8 hours |
| CI/CD integration | 4-8 hours |
| Total | 12-24 hours |
Risks and Mitigations¶
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Library API changes (pre-release) | Medium | Medium | Pin version, monitor releases |
| SimplyPrint rate limiting | Low | Low | Use development mode, dedicated test account |
| Python dependency conflicts | Low | Low | Use isolated virtual environment |
| WebSocket protocol changes | Low | High | Monitor library updates |
Comparison with Alternatives¶
Alternative 1: Physical Printer Only¶
| Aspect | Physical | Virtual |
|---|---|---|
| Hardware cost | \(200-\)2000+ | $0 |
| Maintenance | High | None |
| Test repeatability | Low | High |
| CI/CD compatible | No | Yes |
| Edge case testing | Limited | Unlimited |
| Development speed | Slow | Fast |
Alternative 2: Mock SimplyPrint API¶
| Aspect | Mock API | Virtual Printer |
|---|---|---|
| Implementation effort | High (reverse-engineer API) | Low (use library) |
| Accuracy | Approximation | Exact protocol |
| Maintenance burden | High | Low (library maintained) |
| Integration testing | Partial | Full end-to-end |
Alternative 3: SimplyPrint Sandbox (if available)¶
SimplyPrint does not currently provide a documented sandbox or staging environment. The virtual printer approach using their official library is the recommended testing strategy.
Implementation Roadmap¶
Phase 1: Basic Setup (Day 1)¶
- Create
tools/virtual-printerdirectory - Set up Python environment with
uv - Install
simplyprint-ws-client - Run VirtualClient example
- Register virtual printer in SimplyPrint test account
Phase 2: Custom Emulator (Days 2-3)¶
- Fork/extend VirtualClient for Forma3D needs
- Add configurable test scenarios
- Implement logging and telemetry
- Document setup and usage
Phase 3: CI/CD Integration (Days 4-5)¶
- Create Docker container for virtual printer
- Add to CI/CD pipeline
- Implement automated test scenarios
- Add health checks and monitoring
Phase 4: Advanced Scenarios (Ongoing)¶
- Multi-printer farm simulation
- Error injection and recovery testing
- Performance/load testing
- Long-running stability tests
Conclusion¶
Building a SimplyPrint emulator is highly feasible and can be accomplished with minimal effort by leveraging the official simplyprint-ws-client Python library. The existing VirtualClient example provides a complete, working implementation that:
- Connects to SimplyPrint's production servers
- Simulates realistic printer behavior
- Supports the full range of printer operations
- Can be registered as a real printer in SimplyPrint
Recommended Next Steps¶
- Create a dedicated SimplyPrint test account for virtual printer testing
- Set up the virtual printer environment in
tools/virtual-printer - Register 1-2 virtual printers for initial testing
- Document the setup process for team members
- Integrate into CI/CD for automated testing
References¶
- SimplyPrint WebSocket Client Repository
- SimplyPrint WS Client Tutorial
- SimplyPrint API Documentation
- SimplyPrint Setup Guide
- PyPI Package
Appendix A: Complete VirtualClient Example¶
The following is the complete VirtualClient implementation from the official library, demonstrating all features of a virtual printer emulator:
import asyncio
import math
import random
from typing import Optional
from simplyprint_ws_client import (
PrinterConfig,
DefaultClient,
GcodeDemandData,
PrinterStatus,
FileDemandData,
FileProgressStateEnum,
)
def expt_smooth(target, actual, alpha, dt) -> float:
"""Exponential smoothing for realistic temperature/progress transitions"""
return actual + (target - actual) * (1.0 - math.exp(-alpha * dt))
class VirtualConfig(PrinterConfig):
"""Custom configuration for virtual printer"""
pass
class VirtualClient(DefaultClient[VirtualConfig]):
"""Virtual 3D printer emulator for testing"""
job_progress_alpha: float = 0.1
pending_job: Optional[FileDemandData] = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.printer.firmware.name = "Virtual Printer Firmware"
self.printer.firmware.version = "1.0.0"
self.printer.set_info("Virtual Printer", "0.0.1")
self.printer.tool_count = 1
async def init(self):
"""Initialize printer to default state"""
self.printer.bed.temperature.actual = 20.0
self.printer.bed.temperature.target = 0.0
self.printer.tool0.temperature.actual = 20.0
self.printer.tool0.temperature.target = 0.0
self.printer.status = PrinterStatus.OPERATIONAL
async def tick(self, _):
"""Called periodically to update printer state"""
await self.send_ping()
# Simulate temperature changes
if self.printer.bed.temperature.target:
self.printer.bed.temperature.actual = expt_smooth(
self.printer.bed.temperature.target,
self.printer.bed.temperature.actual,
1, 0.1,
)
else:
self.printer.bed.temperature.actual = 20.0
if self.printer.tool0.temperature.target:
self.printer.tool0.temperature.actual = expt_smooth(
self.printer.tool0.temperature.target,
self.printer.tool0.temperature.actual,
1, 0.1,
)
else:
self.printer.tool0.temperature.actual = 20.0
# Simulate print progress
if self.printer.status == PrinterStatus.PRINTING:
self.printer.job_info.progress = expt_smooth(
100.0, self.printer.job_info.progress,
self.job_progress_alpha, 0.1,
)
if round(self.printer.job_info.progress) >= 100.0:
self.printer.job_info.finished = True
self.printer.status = PrinterStatus.OPERATIONAL
self.printer.bed.temperature.target = 0.0
self.printer.tool0.temperature.target = 0.0
async def on_file(self, data: FileDemandData):
"""Handle file download request"""
self.printer.status = PrinterStatus.DOWNLOADING
self.printer.file_progress.state = FileProgressStateEnum.DOWNLOADING
# Simulate download
while self.printer.file_progress.percent < 100.0:
self.printer.file_progress.percent += 10
await asyncio.sleep(0.1)
self.pending_job = data
self.printer.file_progress.state = FileProgressStateEnum.READY
if data.auto_start:
await self.on_start_print(data)
else:
self.printer.status = PrinterStatus.OPERATIONAL
async def on_start_print(self, _):
"""Start a print job"""
self.pending_job = None
self.printer.status = PrinterStatus.PRINTING
self.printer.job_info.started = True
self.printer.job_info.progress = 0.0
self.printer.bed.temperature.target = 60.0
self.printer.tool0.temperature.target = 225.0
async def on_cancel(self, _):
"""Cancel current print job"""
self.printer.status = PrinterStatus.CANCELLING
self.printer.job_info.cancelled = True
await asyncio.sleep(2)
self.printer.status = PrinterStatus.OPERATIONAL
self.printer.bed.temperature.target = 0.0
self.printer.tool0.temperature.target = 0.0
Appendix B: Quick Start Script¶
Save this as run_virtual_printer.py:
#!/usr/bin/env python3
"""
Quick start script for running a virtual printer emulator.
Usage:
pip install simplyprint-ws-client
python run_virtual_printer.py
"""
from simplyprint_ws_client import (
ClientApp,
ConfigManagerType,
ClientSettings,
PrinterConfig,
DefaultClient,
PrinterStatus,
)
from simplyprint_ws_client.shared.logging import setup_logging
class VirtualPrinter(DefaultClient[PrinterConfig]):
"""Minimal virtual printer implementation"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.printer.set_info("Forma3D Virtual Printer", "1.0.0")
self.printer.firmware.name = "Virtual Firmware"
self.printer.firmware.version = "1.0.0"
self.printer.tool_count = 1
async def init(self):
self.printer.status = PrinterStatus.OPERATIONAL
self.printer.bed.temperature.actual = 20.0
self.printer.tool0.temperature.actual = 20.0
print("Virtual printer initialized and OPERATIONAL")
async def on_connected(self):
print(f"Connected! Setup code: {self.config.short_id}")
print("Add this printer at: https://simplyprint.io/setup-guide")
if __name__ == "__main__":
settings = ClientSettings(
name="Forma3D-VirtualPrinter",
client_factory=VirtualPrinter,
config_factory=PrinterConfig,
allow_setup=True,
config_manager_t=ConfigManagerType.JSON,
development=True,
)
setup_logging(settings)
app = ClientApp(settings)
# Load or create config
configs = app.config_manager.get_all()
config = configs[0] if configs else PrinterConfig.get_new()
app.add(config)
print("Starting virtual printer...")
app.run_blocking()