It is ok for me to exchange arguments about this
when you have time, and I think it is much more important that it may seem at a first glance. A solver constraint can be changed or improved with restricted impact (if it provides the same functionality in a different way, without impact at all). The decision on how to code sketcher constraints impacts the Python interface and even storage/serialisation. Changing it afterwards may imply macros breaking (Python impact) or problems to provide backwards compatibility with legacy files (if one sketcher constraint was stored in one way and that way of storing needs to be used to code a different situation).
jnxd wrote: ↑Thu Dec 01, 2022 6:52 pm
BTW, this is not the only place where one tangent command creates two constraints of PoO and tangent. See the relevant code for three selection tangent constraint when the point is not an end point of either curve.
This case is still different. A ternary tangency creates a TangentViaPoint constraint which internally uses AngleViaPoint
solver constraint and can provide a tangency with
any curve. There is an expectation that a ternary tangency (i.e. a AngleViaPoint), when supported for a type, provides tangency between any set curves. The Tangent at knot does not use the AngleViaPoint infrastructure and can only provide tangencies with
lines.
In fact, because of the current reuse of AngleViaPoint infrastructure there appears to be a bug, that the branch allows to make an tangency between a circle and a knot, or an ellipse and a knot, while not yet supported.
jnxd wrote: ↑Thu Dec 01, 2022 6:52 pm
I think it doesn't make sense to make just tangent here when tangent-at-point exists, but I'll get back sometime once I have my hands on a computer.
I think that depiction of tangent at knot as
tangent at point may be misleading. This may also be at the bottom of view of (lack of) homogeneity of interface.
TangentViaPoint is a set of functionality originally created to provide tangencies
between (complex) curves. It avoids having to create a specific
solver constraint for each pair of curves (example ellipse-hyperbole, ellipse-parabola). In fact, it is enough that a new curve implements point on object and is able to provide its normal at parameter u, for it to work. In this type of constraint, a point is introduced at sketcher level. The point is not part of, or is associated with any of the curves (e.g. it is not an internal geometry point). It may be used to block the point of tangency at a given position (for example at an angle with respect to an axis). It does come with the issue that the tangency is a compound of one tangency and
two point on objects. This is also characteristic, the point is point on object restricted to
both curves.
At the time, we tried making this point "solver only", but this gave rise to problems with diagnostic (and at sketcher level diagnostic, i.e. the DoFs and accurate detection of conflicting/redundant constraints, is arguably the most important feature of all). The decision was to allow the suboptimal "two sketcher constraints" solution, with the understanding that it was a corner case (only for complex curves, as simple curves have satisfactory specific solutions). At the moment we did not see further. It begs the follow up question:
Can this be done in another way? Yes. This may include, for example, specific constraints for each pair, or a new tangency constraint encapsulating the three constraints (but constraints have currently limitations of how many curves/positions can store). It is not straightforward (or without impact), but I am not against discussing possible improvements to this. We also need to take care of backwards compatibility (constraints are stored independently now).
This can be a separate effort.
There is another important point before I go on with the comparison. Although it does not concern the sketcher level representation of constraints,
endpoint tangencies, as well as
endpoint to curve tangencies are all implemented with AngleViaPoint (curve to curve are not, unless it is the situation above). This means that despite having a different Python interface (which does not point at all to a TangentViaPoint), at solver level and in a way transparent to the user, many tangencies are internally implemented with the same AngleViaPoint
solver code. So, while using TangentViaPoint sets some expectations on the interface (Python interface), it is possible to implement some constraint where these expectations are not there using AngleViaPoint. This can cause confusion, and this is why I indicate it here. However, we need to separate the
intention provided by the Python interface from the actual implementation at solver level. The implementation at solver level can reasonably change transparently to the user. The Python interface cannot change in a transparent way to the user. For endpoint to endpoint, or endpoint to curve tangencies, the Python code looks like this:
App.getDocument('hang').getObject('Sketch').addConstraint(Sketcher.Constraint('Tangent',17,1,18,2))
App.getDocument('hang').getObject('Sketch').addConstraint(Sketcher.Constraint('Tangent',19,2,18))
For a TangentviaPoint, it looks like this:
>>> App.getDocument('hang').getObject('Sketch').addGeometry(Part.Point(App.Vector(-55.721522,45.582154,0)))
>>> App.getDocument('hang').getObject('Sketch').addConstraint(Sketcher.Constraint('PointOnObject',16,1,11))
>>> App.getDocument('hang').getObject('Sketch').addConstraint(Sketcher.Constraint('PointOnObject',16,1,10))
>>> App.getDocument('hang').getObject('Sketch').addConstraint(Sketcher.Constraint('TangentViaPoint',11,10,16,1))
Internally, at
solver level all the tangencies above are AngleViaPoint constraints (with the angle being fixed at solver level so to be tangent). In the former, the end-point-to curve also adds a
solver level PointOnObject as part of the tangency constraint. This is done fully transparently for the user. In the latter, the pointOnObject constraints are
sketcher level.
Now, coming back to the second part of the original discussion. A knot is an internal alignment geometry. For a given B-Spline, the knots are at fixed positions (i.e. changing the positions of the knots do change the B-Spline, leading to a different B-Spline). These are the first two differences (above the point is "free" before tangency is applied, above the point defines the point of tangency over
two curves from a continuum of possible tangency positions). A tangency at a knot position is perfectly defined by the knot position itself. This also leads to a further difference, while above there are
two sketcher level point on object constraints, in the current implementation tangent at knot position, there is only
one sketcher level point on object involved.
These differences make it possible for a tangent at knot, to be implement the PointonObject
solver level constraint transparently to the user, as in the case of the curve-to-endpoint indicated above.
In addition, the tangent at knot does not make use of the generic AngleViaPoint infrastructure, but uses a specific constraint at solver level (which is valid exclusively for lines). Therefore, it should be separated from the code adding an AngleViaPoint (the same as the "simple tangencies" between curves are separated from the ones using AngleViaPoint).
In fact, it may be possible for you or for another developer in the future, to write the tangency function for B-Splines used by AngleViaPoint. Together with two point on object (support needs to be there as well), it could provide using AngleViaPoint all the tangencies at any position of a B-Spline with another curve (at the cost of a sketcher level point, two sketcher level PoO and one tangency constraint in the current infrastructure, or else if we develop the infrastructure further as discussed above to remove such limitation). For this, I think that keeping the TangentViaPoint Python interface for this specific case of tangency is more sound.
Please, give it a thought and come back as necessary.