Skip to content

Feature/add chamfer fillet #62572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

lbartoletti
Copy link
Member

Description

This PR introduces core functionality for chamfer and fillet operations on geometric objects, commonly used in CAD applications for corner modification.

There are two implementation level:

Low-level 2D operations (QgsGeometryUtilsBase):

  • createChamfer() - Creates chamfer between two segments (x/y coordinates) with specified distances
  • createFillet() - Creates circular fillet between two segments (x/y coordinates) with specified radius

High-level geometry operations (QgsGeometry):

  • Vertex-based methods: chamfer(vertexIndex, ...) and fillet(vertexIndex, ...)
  • Segment-based methods: chamfer(seg1, seg2, ...) and fillet(seg1, seg2, ...)
  • Full Z/M coordinate interpolation support
  • Handles LineString and CompoundCurve

The algorithms support both touching and non-touching segments.

Some visual examples:

Original:
original

Symetric chamfer:
chamfer

Asymetric Chamfer:
chamfer_asym

Fillet (preserving arc here):
fillet

Future Work

The map tools will be added in a separate PR
Maybe a processing too

Funded by: Frankurt.

…tilsBase

Add createChamfer() and createFillet() methods for 2D geometric operations.
These low-level functions handle segment intersection, angle calculation,
and tangent point computation for CAD-style corner modifications.
Add vertex-based and segment-based chamfer() and fillet() methods with
Z/M coordinate interpolation support. Handles both LineString modification
and standalone segment operations for CAD-style corner editing.
…operations

Handle vertex-based operations on CompoundCurve geometries by preserving
existing CircularString segments and properly inserting new chamfer/fillet
geometry without segmentation.
@github-actions github-actions bot added this to the 3.46.0 milestone Jul 10, 2025
Copy link
Contributor

github-actions bot commented Jul 10, 2025

🪟 Windows builds

Download Windows builds of this PR for testing.
Debug symbols for this build are available here.
(Built from commit 78972f6)

🍎 MacOS Qt6 builds

Download MacOS Qt6 builds of this PR for testing.
This installer is not signed, control+click > open the app to avoid the warning
(Built from commit 78972f6)

🪟 Windows Qt6 builds

Download Windows Qt6 builds of this PR for testing.
(Built from commit 78972f6)

@lbartoletti lbartoletti force-pushed the feature/add_chamfer_fillet branch from 481c502 to 78972f6 Compare July 11, 2025 12:55
* \param epsilon tolerance for numerical comparisons and intersection detection
* \returns TRUE if fillet was successfully created
*
* \note The caller must ensure that filletPointsX and filletPointsY arrays are
Copy link
Collaborator

Choose a reason for hiding this comment

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

how is that working with the Python bindings? Might be safer to sip_skip this one...

Comment on lines +599 to +602
double *trim1StartX = nullptr, double *trim1StartY = nullptr,
double *trim1EndX = nullptr, double *trim1EndY = nullptr,
double *trim2StartX = nullptr, double *trim2StartY = nullptr,
double *trim2EndX = nullptr, double *trim2EndY = nullptr,
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't these all be SIP_OUT too?

* \param y y-coordinate of the point to interpolate
* \param segStart start point of the segment
* \param segEnd end point of the segment
* \param distanceFromStart distance from segment start to the interpolated point
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't this be automatically calculated based on x/y ?

* Interpolates a point on a segment with proper Z and M value interpolation.
* \param x x-coordinate of the point to interpolate
* \param y y-coordinate of the point to interpolate
* \param segStart start point of the segment
Copy link
Collaborator

Choose a reason for hiding this comment

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


/**
* Creates a fillet (rounded corner) between two line segments using QgsPoint.
* Returns the three fillet arc points via output parameters.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I assume this is a circular arc? I think it should be explicitly mentioned in the docs

distance2 = distance1;

// Validate input parameters
if ( distance1 < 0 || distance2 < 0 )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if ( distance1 < 0 || distance2 < 0 )
if ( distance1 <= 0 || distance2 <= 0 )

if ( !createChamfer( seg1Start, seg1End, seg2Start, seg2End, distance1, distance2, chamferStart, chamferEnd ) )
return nullptr;

QgsLineString *completeLine = new QgsLineString();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use unique_ptr. Also use constructor for QgsLineString which takes a list of points instead of calling addVertex multiple times

if ( segments <= 0 )
{
// Return CompoundCurve with circular arc
QgsCompoundCurve *completeCurve = new QgsCompoundCurve();
Copy link
Collaborator

Choose a reason for hiding this comment

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

unique_ptr (elsewhere too)

QgsCompoundCurve *completeCurve = new QgsCompoundCurve();

// First linear segment
QgsLineString *firstSegment = new QgsLineString();
Copy link
Collaborator

Choose a reason for hiding this comment

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

as above, use constructor instead of multiple addVertex calls (elsewhere too)


QgsGeometry QgsGeometry::chamfer( int vertexIndex, double distance1, double distance2 ) const
{
const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( d->geometry.get() );
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want to be more tolerant and also accept multicurves with a single geometry:

Suggested change
const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( d->geometry.get() );
if ( isNull() )
return QgsGeometry();
const QgsCurve *curve = qgsgeometry_cast<const QgsCurve *>( d->geometry->simplifiedTypeRef() );

elsewhere too

@nyalldawson
Copy link
Collaborator

@lbartoletti nice work! Above review is about the api and high-level considerations only... I haven't reviewed the actual maths behind the chamfer/fillet geometry creation. I trust you on those! 😆

@nyalldawson
Copy link
Collaborator

The map tools will be added in a separate PR

Can you elaborate on the plans for exposing these? My personal preference is that they'd be implemented via QgsAdvancedDigitizingToolsRegistry and exposed only in the CAD dock tools panel, as that's the approach with the least ui complexity cost.

@nyalldawson nyalldawson added the API API improvement only, no visible user interface changes label Jul 14, 2025
Comment on lines +3233 to +3235
QgsGeometry fillet( const QgsPoint &seg1Start, const QgsPoint &seg1End,
const QgsPoint &seg2Start, const QgsPoint &seg2End,
double radius, int segments = 8 ) const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain why those variants using the segment points are needed?
To me they look weird, as it super easy to call with points that do not match segments or even do not lie on the geometry!

@lbartoletti
Copy link
Member Author

@lbartoletti nice work! Above review is about the api and high-level considerations only... I haven't reviewed the actual maths behind the chamfer/fillet geometry creation. I trust you on those! 😆

Thank you!
I'll be AFK for 15 days, but I'll take your comments into consideration when I return.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API API improvement only, no visible user interface changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants