Skip to content

fix: target the symmetric-extrusion property the running FreeCAD has (Pad, Revolution, Groove) - #100

Open
Merlz wants to merge 1 commit into
spkane:mainfrom
Merlz:fix/symmetric-extrude-sidetype
Open

fix: target the symmetric-extrusion property the running FreeCAD has (Pad, Revolution, Groove)#100
Merlz wants to merge 1 commit into
spkane:mainfrom
Merlz:fix/symmetric-extrude-sidetype

Conversation

@Merlz

@Merlz Merlz commented Jul 29, 2026

Copy link
Copy Markdown

Fixes #94.

Summary

pad_sketch(symmetric=...) raised AttributeError because PartDesign::Pad has no Symmetric property. Two things turned out to be true beyond the original report:

  1. revolution_sketch and groove_sketch are broken the same wayrev.Symmetric and groove.Symmetric at two further sites, neither mentioned in pad_sketch crashes on FreeCAD 1.1.1 — uses removed 'Symmetric' property (renamed 'Midplane') #94.

  2. Midplane is not the right target on 1.1 either. FreeCAD 1.1 supersedes it with SideType and warns whenever Midplane is set:

    The 'Midplane' property being set for the extrusion of Sketch is deprecated
    and has been replaced by the 'SideType' property in FeatureExtrude. Please
    update your script, this property will be removed in a future version.
    

    That warning lands in the console get_console_output returns, so it pollutes the channel callers use for debugging. And since Midplane is slated for removal, a Midplane-only fix would eventually fall through to pad.Symmetric — an attribute no current version has — turning a deprecation into a hard failure later.

Worth noting for the record: git log -S "pad.Symmetric" shows it arrived in the initial commit and was never modified, so this was never a 1.1 rename — Symmetric was simply always wrong for Pad.

Approach

The generated code probes for the property instead of assuming one, so a single build works across versions and stays quiet on 1.1+:

SideType (1.1+)  →  Midplane (legacy)  →  Symmetric (terminal fallback)

Revolution and Groove have no SideType on 1.1.3 and emit no deprecation warning, so they use the Midplane chain only. I verified that specifically rather than applying SideType uniformly.

The boolean is bound to a local in the generated code so each branch reads one consistently-evaluated value.

Verification

Against live FreeCAD 1.1.3 through the xmlrpc bridge:

  • pad_sketch(length=6, symmetric=True)SideType == "Symmetric", solid spans z = -3 .. +3, Shape.isValid() true. Previously AttributeError.
  • No deprecation warning in the console on 1.1.3, because SideType is taken first.
  • revolution_sketch / groove_sketch confirmed to have no SideType, so Midplane remains correct for them.

Unit tests: 425 passed (420 on main + 5 added). ruff check and ruff format --check clean.

Test Plan

  • uv run pytest tests/unit — 425 passed
  • ruff check src/ tests/ clean
  • ruff format --check src/ tests/ clean
  • Symmetric pad against live FreeCAD 1.1.3 produces a valid, correctly-centred solid
  • Generated code compiles (asserted in tests, since it is exec'd inside FreeCAD)
  • Someone with FreeCAD 0.21/1.0 confirms the Midplane fallback path — I only have 1.1.3, so that branch is exercised by the property probe but not by me on a real 1.0 install

Notes

The tests assert on the generated source string, which is how the existing suite works for these code-generating tools, plus a compile() call so an indentation slip in an f-string template fails in CI rather than inside FreeCAD. They also assert SideType precedes Midplane, which is the part that keeps the console quiet.

Summary by CodeRabbit

  • Bug Fixes

    • Improved compatibility for symmetric pad, revolution, and groove operations across different FreeCAD versions.
    • These operations now select the appropriate symmetry setting automatically based on the available feature properties.
  • Tests

    • Added coverage to verify symmetry behavior across supported operation types and FreeCAD property variations.

PartDesign::Pad has no `Symmetric` property on any currently supported FreeCAD
release, so `pad_sketch(symmetric=...)` raised AttributeError. FreeCAD exposes
`Midplane`, and 1.1+ supersedes that with `SideType` while emitting a
deprecation warning if `Midplane` is set:

    The 'Midplane' property being set for the extrusion of Sketch is deprecated
    and has been replaced by the 'SideType' property in FeatureExtrude.

The generated code now probes for the property instead of assuming one, so a
single build works across versions and stays quiet on 1.1+:

    SideType (1.1+)  ->  Midplane (legacy)  ->  Symmetric (terminal fallback)

`revolution_sketch` and `groove_sketch` were broken the same way. They have no
`SideType`, so they use the `Midplane` chain and are unchanged otherwise. Those
two sites were not covered by the original report.

Verified against FreeCAD 1.1.3: a symmetric pad of length 6 now yields
SideType="Symmetric" and spans z=-3..+3 on a valid solid.

Fixes spkane#94
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Updated generated FreeCAD code to select compatible symmetry properties for pads, revolutions, and grooves. Added tests covering property preference, fallbacks, single binding of the symmetric value, and generated-code compilation.

Changes

Symmetric property compatibility

Layer / File(s) Summary
Property selection and validation
src/freecad_mcp/tools/partdesign.py, tests/unit/test_tools_partdesign.py
pad_sketch, revolution_sketch, and groove_sketch select supported symmetry properties with fallbacks; tests validate generated code behavior and compilation.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: FreeCAD-compatible symmetric extrusion handling for Pad, Revolution, and Groove.
Linked Issues check ✅ Passed The PR fixes #94 by replacing hardcoded Symmetric access with runtime property detection for pad_sketch, matching the reported failure.
Out of Scope Changes check ✅ Passed The added tests validate the same compatibility fix and do not introduce unrelated behavior changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/test_tools_partdesign.py`:
- Around line 1140-1185: Rename the listed non-test helpers and fixtures to
camelCase—mockMcp, mockBridge, registerTools, toolDecorator, getBridge, and
_generated—and add type annotations for every parameter and return value in
those functions. Update all references consistently while preserving the
existing fixture setup and generated-code validation behavior.
- Around line 1196-1201: Strengthen the relevant assertions in the test covering
pad symmetry compatibility: verify every fallback branch assigns each supported
object’s Symmetric property from _symmetric, and replace the current presence
check with code.count("_symmetric =") == 1 to enforce a single binding. Preserve
the existing SideType-before-Midplane ordering assertion and apply the same
checks to the additional branch covered around lines 1215–1230.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 03334d80-b7ba-4505-9d4e-f3bc7f2dcb9c

📥 Commits

Reviewing files that changed from the base of the PR and between d9a3711 and 838e6df.

📒 Files selected for processing (2)
  • src/freecad_mcp/tools/partdesign.py
  • tests/unit/test_tools_partdesign.py

Comment on lines +1140 to +1185
@pytest.fixture
def mock_mcp(self):
mcp = MagicMock()
mcp._registered_tools = {}

def tool_decorator():
def wrapper(func):
mcp._registered_tools[func.__name__] = func
return func

return wrapper

mcp.tool = tool_decorator
return mcp

@pytest.fixture
def mock_bridge(self):
bridge = AsyncMock()
bridge.execute_python = AsyncMock(
return_value=ExecutionResult(
success=True,
result={"success": True, "name": "F", "label": "F", "type_id": "T"},
stdout="",
stderr="",
error_traceback=None,
execution_time_ms=1.0,
)
)
return bridge

@pytest.fixture
def register_tools(self, mock_mcp, mock_bridge):
from freecad_mcp.tools.partdesign import register_partdesign_tools

async def get_bridge():
return mock_bridge

register_partdesign_tools(mock_mcp, get_bridge)
return mock_mcp._registered_tools

@staticmethod
def _generated(mock_bridge):
code = mock_bridge.execute_python.call_args[0][0]
# Generated code is exec'd inside FreeCAD, so it must at least parse.
compile(code, "<generated>", "exec")
return code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Apply the required helper naming and annotations.

Rename non-test helpers/fixtures to camelCase and add parameter/return annotations; e.g. mock_mcp, mock_bridge, register_tools, tool_decorator, get_bridge, and _generated. As per coding guidelines, “Use type hints for all function parameters and return values in Python” and “Use camelCase for variable names in Python code.”

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 1183-1183: The use of compile can be insecure
Context: compile(code, "", "exec")
Note: [CWE-94] Improper Control of Generation of Code ('Code Injection').

(no-compile)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_tools_partdesign.py` around lines 1140 - 1185, Rename the
listed non-test helpers and fixtures to camelCase—mockMcp, mockBridge,
registerTools, toolDecorator, getBridge, and _generated—and add type annotations
for every parameter and return value in those functions. Update all references
consistently while preserving the existing fixture setup and generated-code
validation behavior.

Source: Coding guidelines

Comment on lines +1196 to +1201
assert 'hasattr(pad, "SideType")' in code
assert 'pad.SideType = "Symmetric" if _symmetric else "One side"' in code
assert 'elif hasattr(pad, "Midplane")' in code
assert "pad.Midplane = _symmetric" in code
# SideType must be tried first, or 1.1 emits a deprecation warning.
assert code.index("SideType") < code.index("Midplane")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert every fallback branch and the actual single binding.

These tests only verify the preferred branches and that _symmetric exists. Assert each *.Symmetric = _symmetric fallback and use code.count("_symmetric =") == 1; otherwise regressions in legacy compatibility or duplicate bindings still pass.

Also applies to: 1215-1230

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_tools_partdesign.py` around lines 1196 - 1201, Strengthen the
relevant assertions in the test covering pad symmetry compatibility: verify
every fallback branch assigns each supported object’s Symmetric property from
_symmetric, and replace the current presence check with code.count("_symmetric
=") == 1 to enforce a single binding. Preserve the existing
SideType-before-Midplane ordering assertion and apply the same checks to the
additional branch covered around lines 1215–1230.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pad_sketch crashes on FreeCAD 1.1.1 — uses removed 'Symmetric' property (renamed 'Midplane')

1 participant