FreeCAD / src /Mod /CAM /CAMTests /TestMachine.py
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2025 Brad Collette *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# ***************************************************************************
import FreeCAD
import tempfile
import pathlib
import CAMTests.PathTestUtils as PathTestUtils
from Path.Machine.models.machine import (
Machine,
Spindle,
OutputOptions,
GCodeBlocks,
ProcessingOptions,
MachineFactory,
)
class TestMachineDataclass(PathTestUtils.PathTestBase):
"""Test the unified Machine dataclass"""
def setUp(self):
"""Set up test fixtures"""
self.default_machine = Machine()
def test_default_initialization(self):
"""Test that Machine initializes with sensible defaults"""
machine = Machine()
# Basic identification
self.assertEqual(machine.name, "Default Machine")
self.assertEqual(machine.manufacturer, "")
self.assertEqual(machine.description, "")
# Machine type is derived from axes configuration
self.assertEqual(machine.machine_type, "custom") # No axes configured yet
# Add axes and verify machine type updates
machine.add_linear_axis("X", FreeCAD.Vector(1, 0, 0))
machine.add_linear_axis("Y", FreeCAD.Vector(0, 1, 0))
self.assertEqual(machine.machine_type, "custom") # Still missing Z axis
machine.add_linear_axis("Z", FreeCAD.Vector(0, 0, 1))
self.assertEqual(machine.machine_type, "xyz") # Now has XYZ axes
# Add rotary axes and verify machine type updates
machine.add_rotary_axis("A", FreeCAD.Vector(1, 0, 0), -120, 120)
self.assertEqual(machine.machine_type, "xyza")
machine.add_rotary_axis("C", FreeCAD.Vector(0, 0, 1), -360, 360)
self.assertEqual(machine.machine_type, "xyzac")
# Coordinate system defaults
self.assertEqual(machine.reference_system["X"], FreeCAD.Vector(1, 0, 0))
self.assertEqual(machine.reference_system["Y"], FreeCAD.Vector(0, 1, 0))
self.assertEqual(machine.reference_system["Z"], FreeCAD.Vector(0, 0, 1))
self.assertEqual(machine.tool_axis, FreeCAD.Vector(0, 0, -1))
# Units and versioning
self.assertEqual(machine.configuration_units, "metric")
self.assertEqual(machine.version, 1)
self.assertIsNotNone(machine.freecad_version)
# Post-processor defaults
self.assertIsInstance(machine.output, OutputOptions)
self.assertIsInstance(machine.blocks, GCodeBlocks)
self.assertIsInstance(machine.processing, ProcessingOptions)
def test_custom_initialization(self):
"""Test Machine initialization with custom values and verify machine_type is derived"""
# Create a 5-axis machine (XYZAC)
machine = Machine(
name="Test Mill",
manufacturer="ACME Corp",
description="5-axis mill",
configuration_units="imperial",
)
# Add axes to make it a 5-axis machine
machine.add_linear_axis("X", FreeCAD.Vector(1, 0, 0))
machine.add_linear_axis("Y", FreeCAD.Vector(0, 1, 0))
machine.add_linear_axis("Z", FreeCAD.Vector(0, 0, 1))
machine.add_rotary_axis("A", FreeCAD.Vector(1, 0, 0), -120, 120)
machine.add_rotary_axis("C", FreeCAD.Vector(0, 0, 1), -360, 360)
self.assertEqual(machine.name, "Test Mill")
self.assertEqual(machine.manufacturer, "ACME Corp")
self.assertEqual(machine.description, "5-axis mill")
self.assertEqual(machine.machine_type, "xyzac")
self.assertEqual(machine.configuration_units, "imperial")
def test_configuration_units_property(self):
"""Test configuration_units property returns correct values"""
metric_machine = Machine(configuration_units="metric")
self.assertEqual(metric_machine.configuration_units, "metric")
imperial_machine = Machine(configuration_units="imperial")
self.assertEqual(imperial_machine.configuration_units, "imperial")
class TestOutputOptions(PathTestUtils.PathTestBase):
"""Test OutputOptions dataclass"""
def test_default_initialization(self):
"""Test OutputOptions initialization with defaults"""
opts = OutputOptions()
# Default values
self.assertTrue(opts.comments)
self.assertTrue(opts.blank_lines)
self.assertTrue(opts.header)
self.assertFalse(opts.line_numbers)
self.assertFalse(opts.bcnc_blocks)
self.assertFalse(opts.path_labels)
self.assertFalse(opts.machine_name)
self.assertTrue(opts.tool_change)
self.assertTrue(opts.doubles)
self.assertFalse(opts.adaptive)
def test_custom_initialization(self):
"""Test OutputOptions initialization with custom values"""
opts = OutputOptions(
comments=False,
blank_lines=False,
header=False,
line_numbers=True,
bcnc_blocks=True,
path_labels=True,
machine_name=True,
tool_change=False,
doubles=False,
adaptive=True,
)
# Verify custom values
self.assertFalse(opts.comments)
self.assertFalse(opts.blank_lines)
self.assertFalse(opts.header)
self.assertTrue(opts.line_numbers)
self.assertTrue(opts.bcnc_blocks)
self.assertTrue(opts.path_labels)
self.assertTrue(opts.machine_name)
self.assertFalse(opts.tool_change)
self.assertFalse(opts.doubles)
self.assertTrue(opts.adaptive)
def test_equality(self):
"""Test OutputOptions equality comparison"""
opts1 = OutputOptions()
opts2 = OutputOptions()
self.assertEqual(opts1, opts2)
opts2.comments = False
self.assertNotEqual(opts1, opts2)
class TestSpindle(PathTestUtils.PathTestBase):
"""Test Spindle dataclass"""
def test_spindle_initialization(self):
"""Test Spindle initialization with defaults"""
spindle = Spindle(
name="Main Spindle",
max_power_kw=5.5,
max_rpm=24000,
min_rpm=1000,
tool_change="automatic",
)
self.assertEqual(spindle.name, "Main Spindle")
self.assertEqual(spindle.max_power_kw, 5.5)
self.assertEqual(spindle.max_rpm, 24000)
self.assertEqual(spindle.min_rpm, 1000)
self.assertEqual(spindle.tool_change, "automatic")
# Default tool axis should be set
self.assertEqual(spindle.tool_axis, FreeCAD.Vector(0, 0, -1))
def test_spindle_custom_tool_axis(self):
"""Test Spindle with custom tool axis"""
spindle = Spindle(
name="Side Spindle",
tool_axis=FreeCAD.Vector(1, 0, 0),
)
self.assertEqual(spindle.tool_axis, FreeCAD.Vector(1, 0, 0))
def test_spindle_serialization(self):
"""Test to_dict and from_dict"""
spindle = Spindle(
name="Test Spindle",
id="spindle-001",
max_power_kw=3.0,
max_rpm=18000,
min_rpm=500,
tool_change="manual",
tool_axis=FreeCAD.Vector(0, 1, 0),
)
data = spindle.to_dict()
self.assertEqual(data["name"], "Test Spindle")
self.assertEqual(data["id"], "spindle-001")
self.assertEqual(data["max_power_kw"], 3.0)
self.assertEqual(data["tool_axis"], [0, 1, 0])
restored = Spindle.from_dict(data)
self.assertEqual(restored.name, spindle.name)
self.assertEqual(restored.id, spindle.id)
self.assertEqual(restored.max_power_kw, spindle.max_power_kw)
self.assertEqual(restored.tool_axis, spindle.tool_axis)
class TestMachineFactory(PathTestUtils.PathTestBase):
"""Test MachineFactory class for loading/saving configurations"""
def setUp(self):
"""Set up test fixtures with temporary directory"""
self.temp_dir = tempfile.mkdtemp()
self.temp_path = pathlib.Path(self.temp_dir)
MachineFactory.set_config_directory(self.temp_dir)
def tearDown(self):
"""Clean up temporary directory"""
import shutil
if self.temp_path.exists():
shutil.rmtree(self.temp_path)
def test_set_and_get_config_directory(self):
"""Test setting and getting configuration directory"""
test_dir = self.temp_path / "test_configs"
MachineFactory.set_config_directory(test_dir)
config_dir = MachineFactory.get_config_directory()
self.assertEqual(config_dir, test_dir)
self.assertTrue(config_dir.exists())
def test_save_and_load_configuration(self):
"""Test saving and loading a machine configuration"""
# Create a test machine
machine = Machine(
name="Test Machine",
manufacturer="Test Corp",
description="Test description",
configuration_units="metric",
)
# Add axes to make it an XYZ machine
machine.add_linear_axis("X", FreeCAD.Vector(1, 0, 0))
machine.add_linear_axis("Y", FreeCAD.Vector(0, 1, 0))
machine.add_linear_axis("Z", FreeCAD.Vector(0, 0, 1))
# Add a spindle
spindle = Spindle(
name="Main Spindle",
max_power_kw=5.5,
max_rpm=24000,
min_rpm=1000,
)
machine.spindles.append(spindle)
# Save configuration
filepath = MachineFactory.save_configuration(machine, "test_machine.fcm")
self.assertTrue(filepath.exists())
# Load configuration
loaded_machine = MachineFactory.load_configuration("test_machine.fcm")
# Verify loaded data
self.assertEqual(loaded_machine.name, "Test Machine")
self.assertEqual(loaded_machine.manufacturer, "Test Corp")
self.assertEqual(loaded_machine.description, "Test description")
self.assertEqual(loaded_machine.machine_type, "xyz")
self.assertEqual(loaded_machine.configuration_units, "metric")
self.assertEqual(len(loaded_machine.spindles), 1)
self.assertEqual(loaded_machine.spindles[0].name, "Main Spindle")
def test_save_configuration_auto_filename(self):
"""Test saving with automatic filename generation"""
machine = Machine(name="My Test Machine")
filepath = MachineFactory.save_configuration(machine)
# Should create file with sanitized name
self.assertTrue(filepath.exists())
self.assertEqual(filepath.name, "My_Test_Machine.fcm")
def test_load_nonexistent_file(self):
"""Test loading a file that doesn't exist"""
with self.assertRaises(FileNotFoundError):
MachineFactory.load_configuration("nonexistent.fcm")
def test_create_default_machine_data(self):
"""Test creating default machine data dictionary"""
data = MachineFactory.create_default_machine_data()
self.assertIsInstance(data, dict)
# The data structure has nested "machine" key
self.assertIn("machine", data)
self.assertEqual(data["machine"]["name"], "New Machine")
self.assertIn("spindles", data["machine"])
def test_list_configuration_files(self):
"""Test listing available configuration files"""
# Create some test configurations
machine1 = Machine(name="Machine 1")
machine2 = Machine(name="Machine 2")
MachineFactory.save_configuration(machine1, "machine1.fcm")
MachineFactory.save_configuration(machine2, "machine2.fcm")
# List configurations
configs = MachineFactory.list_configuration_files()
# Should include <any> plus our two machines
self.assertGreaterEqual(len(configs), 3)
self.assertEqual(configs[0][0], "<any>")
# Check that our machines are in the list (by display name, not filename)
names = [name for name, path in configs]
self.assertIn("Machine 1", names)
self.assertIn("Machine 2", names)
def test_list_configurations(self):
"""Test listing configuration names"""
machine = Machine(name="Test Machine")
MachineFactory.save_configuration(machine, "test.fcm")
configs = MachineFactory.list_configurations()
self.assertIsInstance(configs, list)
self.assertIn("<any>", configs)
# Returns display name from JSON, not filename
self.assertIn("Test Machine", configs)
def test_delete_configuration(self):
"""Test deleting a configuration file"""
machine = Machine(name="To Delete")
filepath = MachineFactory.save_configuration(machine, "delete_me.fcm")
self.assertTrue(filepath.exists())
# Delete the configuration
result = MachineFactory.delete_configuration("delete_me.fcm")
self.assertTrue(result)
self.assertFalse(filepath.exists())
# Try deleting again (should return False)
result = MachineFactory.delete_configuration("delete_me.fcm")
self.assertFalse(result)
def test_get_builtin_config(self):
"""Test getting built-in machine configurations"""
# Test each built-in config type
config_types = ["XYZ", "XYZAC", "XYZBC", "XYZA", "XYZB"]
for config_type in config_types:
machine = MachineFactory.get_builtin_config(config_type)
self.assertIsInstance(machine, Machine)
self.assertIsNotNone(machine.name)
def test_get_builtin_config_invalid_type(self):
"""Test getting built-in config with invalid type"""
with self.assertRaises(ValueError):
MachineFactory.get_builtin_config("INVALID")
def test_serialization_roundtrip(self):
"""Test full serialization roundtrip with complex machine"""
# Create a complex machine with all components
machine = Machine(
name="Complex Machine",
manufacturer="Test Mfg",
description="Full featured machine",
configuration_units="metric",
)
# Add spindle
machine.spindles.append(
Spindle(
name="Main",
max_power_kw=7.5,
max_rpm=30000,
)
)
# Configure post-processor settings
machine.output.output_comments = False
machine.output.axis_precision = 4
machine.output.line_increment = 5
# line_increment is set to default 10 in OutputOptions
# Save and load
filepath = MachineFactory.save_configuration(machine, "complex.fcm")
loaded = MachineFactory.load_configuration(filepath)
# Verify all components
self.assertEqual(loaded.name, machine.name)
self.assertEqual(loaded.manufacturer, machine.manufacturer)
self.assertEqual(len(loaded.spindles), 1)
self.assertFalse(loaded.output.output_comments)
self.assertEqual(loaded.output.axis_precision, 4)
self.assertEqual(loaded.output.line_increment, 5)