Skip to main content
Glama
test_surface_modification.py16 kB
""" Test suite for SurfaceModificationTool Tests for surface modification operations including fillet, chamfer, draft, thickness, and offset operations. Author: jango-blockchained """ import os import sys import unittest # Add the parent directory to the path to import the tool sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) try: import FreeCAD as App import Part from advanced_primitives import AdvancedPrimitivesTool from surface_modification import SurfaceModificationTool FREECAD_AVAILABLE = True except ImportError: FREECAD_AVAILABLE = False print("Warning: FreeCAD not available, tests will be skipped") @unittest.skipUnless(FREECAD_AVAILABLE, "FreeCAD not available") class TestSurfaceModificationTool(unittest.TestCase): """Test cases for SurfaceModificationTool.""" def setUp(self): """Set up test fixtures.""" self.tool = SurfaceModificationTool() self.primitives_tool = AdvancedPrimitivesTool() # Create a new document for each test if App.ActiveDocument: App.closeDocument(App.ActiveDocument.Name) self.doc = App.newDocument("TestDoc") def tearDown(self): """Clean up after each test.""" if App.ActiveDocument: App.closeDocument(App.ActiveDocument.Name) def test_tool_initialization(self): """Test tool initialization.""" self.assertEqual(self.tool.name, "surface_modification") self.assertEqual( self.tool.description, "Modify surfaces and edges for manufacturing" ) def test_get_available_modifications(self): """Test getting available modifications list.""" modifications = self.tool.get_available_modifications() self.assertIn("surface_modifications", modifications) surface_mods = modifications["surface_modifications"] # Check all expected modifications are present expected_modifications = ["fillet", "chamfer", "draft", "thickness", "offset"] for mod in expected_modifications: self.assertIn(mod, surface_mods) self.assertIn("description", surface_mods[mod]) self.assertIn("parameters", surface_mods[mod]) self.assertIn("manufacturing_use", surface_mods[mod]) def _create_test_box(self, name="TestBox", length=10, width=8, height=6): """Helper method to create a test box for surface modification.""" # Create a box using Part workbench directly box_shape = Part.makeBox(length, width, height) box_obj = self.doc.addObject("Part::Feature", name) box_obj.Shape = box_shape box_obj.Label = name self.doc.recompute() return box_obj.Name def _create_test_cylinder(self, name="TestCylinder", radius=5, height=10): """Helper method to create a test cylinder for surface modification.""" # Create a cylinder using Part workbench directly cylinder_shape = Part.makeCylinder(radius, height) cylinder_obj = self.doc.addObject("Part::Feature", name) cylinder_obj.Shape = cylinder_shape cylinder_obj.Label = name self.doc.recompute() return cylinder_obj.Name # Fillet Tests def test_fillet_single_edge(self): """Test filleting a single edge.""" box_name = self._create_test_box("FilletBox1", 10, 8, 6) result = self.tool.fillet_edges( object_name=box_name, edge_indices=[0], # First edge radius=2.0, name="TestFillet", ) self.assertTrue(result["success"]) self.assertIn("object_name", result) self.assertIn("properties", result) props = result["properties"] self.assertEqual(props["source_object"], box_name) self.assertEqual(props["edge_indices"], [0]) self.assertEqual(props["radius"], 2.0) self.assertEqual(props["edges_filleted"], 1) self.assertGreater(props["volume"], 0) def test_fillet_multiple_edges(self): """Test filleting multiple edges.""" box_name = self._create_test_box("FilletBox2", 12, 10, 8) result = self.tool.fillet_edges( object_name=box_name, edge_indices=[0, 1, 2, 3], # Multiple edges radius=1.5, ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["edges_filleted"], 4) self.assertEqual(props["radius"], 1.5) def test_fillet_invalid_parameters(self): """Test fillet with invalid parameters.""" box_name = self._create_test_box("FilletBox3") # Zero radius result = self.tool.fillet_edges(box_name, [0], radius=0.0) self.assertFalse(result["success"]) self.assertIn("Radius must be positive", result["error"]) # Empty edge list result = self.tool.fillet_edges(box_name, [], radius=1.0) self.assertFalse(result["success"]) self.assertIn("Edge indices must be a non-empty list", result["error"]) # Invalid edge index result = self.tool.fillet_edges(box_name, [999], radius=1.0) self.assertFalse(result["success"]) self.assertIn("Invalid edge index", result["error"]) def test_fillet_nonexistent_object(self): """Test fillet with non-existent object.""" result = self.tool.fillet_edges("NonExistentObject", [0], radius=1.0) self.assertFalse(result["success"]) self.assertIn("Object 'NonExistentObject' not found", result["error"]) # Chamfer Tests def test_chamfer_single_edge(self): """Test chamfering a single edge.""" box_name = self._create_test_box("ChamferBox1", 10, 8, 6) result = self.tool.chamfer_edges( object_name=box_name, edge_indices=[0], # First edge distance=1.5, name="TestChamfer", ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["source_object"], box_name) self.assertEqual(props["distance"], 1.5) self.assertEqual(props["edges_chamfered"], 1) def test_chamfer_multiple_edges(self): """Test chamfering multiple edges.""" cylinder_name = self._create_test_cylinder("ChamferCylinder", 6, 12) result = self.tool.chamfer_edges( object_name=cylinder_name, edge_indices=[0, 2], # Top and bottom circular edges distance=1.0, ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["edges_chamfered"], 2) self.assertEqual(props["distance"], 1.0) def test_chamfer_invalid_parameters(self): """Test chamfer with invalid parameters.""" box_name = self._create_test_box("ChamferBox2") # Zero distance result = self.tool.chamfer_edges(box_name, [0], distance=0.0) self.assertFalse(result["success"]) self.assertIn("Distance must be positive", result["error"]) # Invalid edge index result = self.tool.chamfer_edges(box_name, [-1], distance=1.0) self.assertFalse(result["success"]) self.assertIn("Invalid edge index", result["error"]) # Draft Tests def test_draft_faces_valid_parameters(self): """Test draft operation with valid parameters.""" box_name = self._create_test_box("DraftBox1", 10, 8, 6) result = self.tool.draft_faces( object_name=box_name, face_indices=[0, 1], # Two faces angle=5.0, direction=(0, 0, 1), name="TestDraft", ) # Note: Draft is simplified implementation, should succeed self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["source_object"], box_name) self.assertEqual(props["angle"], 5.0) self.assertEqual(props["faces_drafted"], 2) self.assertIn("note", props) # Should have implementation note def test_draft_invalid_parameters(self): """Test draft with invalid parameters.""" box_name = self._create_test_box("DraftBox2") # Invalid angle result = self.tool.draft_faces(box_name, [0], angle=50.0) self.assertFalse(result["success"]) self.assertIn("Angle must be between 0 and 45 degrees", result["error"]) # Empty face list result = self.tool.draft_faces(box_name, [], angle=5.0) self.assertFalse(result["success"]) self.assertIn("Face indices must be a non-empty list", result["error"]) # Zero direction vector result = self.tool.draft_faces(box_name, [0], angle=5.0, direction=(0, 0, 0)) self.assertFalse(result["success"]) self.assertIn("Direction vector cannot be zero", result["error"]) # Thickness Tests def test_thickness_without_face_removal(self): """Test thickness operation without removing faces.""" box_name = self._create_test_box("ThicknessBox1", 10, 8, 6) result = self.tool.create_thickness( object_name=box_name, thickness=2.0, name="TestThickness" ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["source_object"], box_name) self.assertEqual(props["thickness"], 2.0) self.assertEqual(props["faces_removed"], 0) self.assertFalse(props["is_shell"]) def test_thickness_with_face_removal(self): """Test thickness operation with face removal (shell creation).""" box_name = self._create_test_box("ThicknessBox2", 12, 10, 8) result = self.tool.create_thickness( object_name=box_name, thickness=1.5, face_indices=[5], # Remove top face name="TestShell", ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["thickness"], 1.5) self.assertEqual(props["faces_removed"], 1) self.assertTrue(props["is_shell"]) def test_thickness_invalid_parameters(self): """Test thickness with invalid parameters.""" box_name = self._create_test_box("ThicknessBox3") # Zero thickness result = self.tool.create_thickness(box_name, thickness=0.0) self.assertFalse(result["success"]) self.assertIn("Thickness must be positive", result["error"]) # Invalid face index result = self.tool.create_thickness(box_name, thickness=1.0, face_indices=[999]) self.assertFalse(result["success"]) self.assertIn("Invalid face index", result["error"]) # Offset Tests def test_offset_outward(self): """Test outward offset operation.""" cylinder_name = self._create_test_cylinder("OffsetCylinder1", 5, 10) result = self.tool.offset_surface( object_name=cylinder_name, distance=2.0, name="TestOffsetOut" ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["source_object"], cylinder_name) self.assertEqual(props["distance"], 2.0) self.assertEqual(props["direction"], "outward") self.assertGreater(props["volume"], 0) def test_offset_inward(self): """Test inward offset operation.""" box_name = self._create_test_box("OffsetBox", 15, 12, 10) result = self.tool.offset_surface( object_name=box_name, distance=-1.5, name="TestOffsetIn" ) self.assertTrue(result["success"]) props = result["properties"] self.assertEqual(props["distance"], -1.5) self.assertEqual(props["direction"], "inward") def test_offset_invalid_parameters(self): """Test offset with invalid parameters.""" box_name = self._create_test_box("OffsetBox2") # Zero distance result = self.tool.offset_surface(box_name, distance=0.0) self.assertFalse(result["success"]) self.assertIn("Distance cannot be zero", result["error"]) # Integration Tests def test_sequential_modifications(self): """Test applying multiple surface modifications sequentially.""" # Create initial box box_name = self._create_test_box("SequentialBox", 12, 10, 8) # Apply fillet fillet_result = self.tool.fillet_edges(box_name, [0, 1], radius=1.0) self.assertTrue(fillet_result["success"]) # Apply chamfer to the filleted object chamfer_result = self.tool.chamfer_edges( fillet_result["object_name"], [4, 5], distance=0.5 ) self.assertTrue(chamfer_result["success"]) # Apply thickness to create shell thickness_result = self.tool.create_thickness( chamfer_result["object_name"], thickness=1.0, face_indices=[5] ) self.assertTrue(thickness_result["success"]) def test_manufacturing_workflow(self): """Test a typical manufacturing workflow.""" # Create a part part_name = self._create_test_cylinder("ManufacturingPart", 8, 15) # Add fillets for stress relief fillet_result = self.tool.fillet_edges(part_name, [0, 2], radius=1.5) self.assertTrue(fillet_result["success"]) # Add chamfers for assembly chamfer_result = self.tool.chamfer_edges( fillet_result["object_name"], [1], distance=0.8 ) self.assertTrue(chamfer_result["success"]) # Create shell for weight reduction shell_result = self.tool.create_thickness( chamfer_result["object_name"], thickness=2.0, face_indices=[1] ) self.assertTrue(shell_result["success"]) # Verify final part has reasonable volume self.assertGreater(shell_result["properties"]["volume"], 0) def test_edge_case_handling(self): """Test handling of edge cases and error conditions.""" # Test with very small geometry small_box = self._create_test_box("SmallBox", 1, 1, 1) # Large fillet radius (should fail gracefully) result = self.tool.fillet_edges(small_box, [0], radius=10.0) # This might fail due to geometry constraints, which is expected if not result["success"]: self.assertIn("Fillet operation failed", result["error"]) def test_no_active_document_handling(self): """Test handling when no active document exists.""" # Close the document App.closeDocument(self.doc.Name) # Operations should fail gracefully result = self.tool.fillet_edges("TestObject", [0], radius=1.0) self.assertFalse(result["success"]) self.assertIn("No active document", result["error"]) def test_complex_geometry_modifications(self): """Test modifications on more complex geometry.""" # Create a more complex shape using primitives tool tube_result = self.primitives_tool.create_tube( outer_radius=8.0, inner_radius=5.0, height=12.0, name="ComplexTube" ) if tube_result["success"]: tube_name = tube_result["object_name"] # Apply fillet to tube edges fillet_result = self.tool.fillet_edges(tube_name, [0, 1], radius=1.0) # Complex geometry might have different edge arrangements # Test should handle both success and expected failures gracefully if fillet_result["success"]: self.assertGreater(fillet_result["properties"]["volume"], 0) class TestSurfaceModificationWithoutFreeCAD(unittest.TestCase): """Test cases that don't require FreeCAD.""" def test_import_without_freecad(self): """Test that the module can be imported even without FreeCAD.""" # This test runs regardless of FreeCAD availability self.assertTrue(True) # Placeholder test if __name__ == "__main__": # Run tests unittest.main(verbosity=2)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server