Draft: more logical placement for 3 point arcs
The placement of 3 point arcs very often was unexpected and typically did not match the placement of circles created on the same working plane. This PR updates the code to use the placement of the working plane to adjust the placement of the created arc. Additonally: * Avoided code duplication by just passing the edge from make_arc_3points to make_circle. * Removed inconsistent map_mode argument from make_arc_3points and the cryptic code related to it. The handling of the support property is something that has to be reviewed for all Draft commands at some point though.
This commit is contained in:
@@ -584,12 +584,17 @@ class Arc_3Points(gui_base.GuiCommandBase):
|
||||
# proceed with creating the final object.
|
||||
# Draw a simple `Part::Feature` if the parameter is `True`.
|
||||
Gui.addModule("Draft")
|
||||
Gui.addModule("WorkingPlane")
|
||||
_cmd = "Draft.make_arc_3points(["
|
||||
_cmd += "FreeCAD." + str(self.points[0])
|
||||
_cmd += ", FreeCAD." + str(self.points[1])
|
||||
_cmd += ", FreeCAD." + str(self.points[2])
|
||||
_cmd += "], primitive=" + str(params.get_param("UsePartPrimitives")) + ")"
|
||||
_cmd_list = ["circle = " + _cmd,
|
||||
_cmd += "]"
|
||||
_cmd += ", placement=pl"
|
||||
_cmd += ", primitive=" + str(params.get_param("UsePartPrimitives"))
|
||||
_cmd += ")"
|
||||
_cmd_list = ["pl = WorkingPlane.get_working_plane().get_placement()",
|
||||
"circle = " + _cmd,
|
||||
"Draft.autogroup(circle)"]
|
||||
if params.get_param("UsePartPrimitives"):
|
||||
_cmd_list.append("Draft.select(circle)")
|
||||
|
||||
@@ -39,10 +39,8 @@ from draftutils.translate import translate
|
||||
import draftutils.gui_utils as gui_utils
|
||||
|
||||
|
||||
def make_arc_3points(points, placement=None, face=False,
|
||||
support=None, map_mode="Deactivated",
|
||||
primitive=False):
|
||||
"""Draw a circular arc defined by three points in the circumference.
|
||||
def make_arc_3points(points, placement=None, face=False, support=None, primitive=False):
|
||||
"""Draw a circular arc defined by three points on the circumference.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -50,17 +48,9 @@ def make_arc_3points(points, placement=None, face=False,
|
||||
A list that must be three points.
|
||||
|
||||
placement: Base::Placement, optional
|
||||
It defaults to `None`.
|
||||
It is a placement, comprised of a `Base` (`Base::Vector3`),
|
||||
and a `Rotation` (`Base::Rotation`).
|
||||
If it exists it moves the center of the new object to the point
|
||||
indicated by `placement.Base`, while `placement.Rotation`
|
||||
is ignored so that the arc keeps the same orientation
|
||||
with which it was created.
|
||||
|
||||
If both `support` and `placement` are given,
|
||||
`placement.Base` is used for the `AttachmentOffset.Base`,
|
||||
and again `placement.Rotation` is ignored.
|
||||
It is adjusted to match the geometry of the created edge.
|
||||
The Z axis of the adjusted placement will be parallel to the
|
||||
(negative) edge axis. Its Base will match the edge center.
|
||||
|
||||
face: bool, optional
|
||||
It defaults to `False`.
|
||||
@@ -72,39 +62,14 @@ def make_arc_3points(points, placement=None, face=False,
|
||||
It is a list containing tuples to define the attachment
|
||||
of the new object.
|
||||
|
||||
A tuple in the list needs two elements;
|
||||
the first is an external object, and the second is another tuple
|
||||
with the names of sub-elements on that external object
|
||||
likes vertices or faces.
|
||||
::
|
||||
support = [(obj, ("Face1"))]
|
||||
support = [(obj, ("Vertex1", "Vertex5", "Vertex8"))]
|
||||
|
||||
This parameter sets the `Support` property but it only really affects
|
||||
the position of the new object when the `map_mode`
|
||||
is set to other than `'Deactivated'`.
|
||||
|
||||
map_mode: str, optional
|
||||
It defaults to `'Deactivated'`.
|
||||
It defines the type of `'MapMode'` of the new object.
|
||||
This parameter only works when a `support` is also provided.
|
||||
|
||||
Example: place the new object on a face or another object.
|
||||
::
|
||||
support = [(obj, ("Face1"))]
|
||||
map_mode = 'FlatFace'
|
||||
|
||||
Example: place the new object on a plane created by three vertices
|
||||
of an object.
|
||||
::
|
||||
support = [(obj, ("Vertex1", "Vertex5", "Vertex8"))]
|
||||
map_mode = 'ThreePointsPlane'
|
||||
This parameter sets the `Support` property but it only really
|
||||
affects the position of the new object if its `MapMode` is
|
||||
set to other than `'Deactivated'`.
|
||||
|
||||
primitive: bool, optional
|
||||
It defaults to `False`. If it is `True`, it will create a Part
|
||||
primitive instead of a Draft object.
|
||||
In this case, `placement`, `face`, `support`, and `map_mode`
|
||||
are ignored.
|
||||
In this case, `placement`, `face` and `support` are ignored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -130,14 +95,6 @@ def make_arc_3points(points, placement=None, face=False,
|
||||
_err(translate("draft","Wrong input: must be list or tuple of 3 points exactly."))
|
||||
return None
|
||||
|
||||
if placement is not None:
|
||||
try:
|
||||
utils.type_check([(placement, App.Placement)], name=_name)
|
||||
except TypeError:
|
||||
_err(translate("draft","Placement:") + " {}".format(placement))
|
||||
_err(translate("draft","Wrong input: incorrect type of placement."))
|
||||
return None
|
||||
|
||||
p1, p2, p3 = points
|
||||
|
||||
try:
|
||||
@@ -149,41 +106,16 @@ def make_arc_3points(points, placement=None, face=False,
|
||||
return None
|
||||
|
||||
try:
|
||||
_edge = Part.Arc(p1, p2, p3)
|
||||
edge = Part.Arc(p1, p2, p3).toShape()
|
||||
except Part.OCCError as error:
|
||||
_err(translate("draft","Cannot generate shape:") + " " + "{}".format(error))
|
||||
return None
|
||||
|
||||
edge = _edge.toShape()
|
||||
radius = edge.Curve.Radius
|
||||
center = edge.Curve.Center
|
||||
|
||||
if primitive:
|
||||
obj = App.ActiveDocument.addObject("Part::Feature", "Arc")
|
||||
obj.Shape = edge
|
||||
return obj
|
||||
|
||||
rot = App.Rotation(edge.Curve.XAxis,
|
||||
edge.Curve.YAxis,
|
||||
edge.Curve.Axis, "ZXY")
|
||||
_placement = App.Placement(center, rot)
|
||||
start = edge.FirstParameter
|
||||
end = math.degrees(edge.LastParameter)
|
||||
obj = Draft.make_circle(radius,
|
||||
placement=_placement, face=face,
|
||||
startangle=start, endangle=end,
|
||||
support=support)
|
||||
|
||||
original_placement = obj.Placement
|
||||
|
||||
if placement and not support:
|
||||
obj.Placement.Base = placement.Base
|
||||
if support:
|
||||
obj.MapMode = map_mode
|
||||
if placement:
|
||||
obj.AttachmentOffset.Base = placement.Base
|
||||
obj.AttachmentOffset.Rotation = original_placement.Rotation
|
||||
|
||||
return obj
|
||||
return Draft.make_circle(edge, placement=placement, face=face, support=support)
|
||||
|
||||
## @}
|
||||
|
||||
@@ -32,6 +32,7 @@ import math
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
import DraftGeomUtils
|
||||
import DraftVecUtils
|
||||
import draftutils.utils as utils
|
||||
import draftutils.gui_utils as gui_utils
|
||||
|
||||
@@ -41,37 +42,61 @@ if App.GuiUp:
|
||||
from draftviewproviders.view_base import ViewProviderDraft
|
||||
|
||||
|
||||
def _get_normal(axis, ref_rot):
|
||||
local_axis = ref_rot.inverted().multVec(axis)
|
||||
x, y, z = [abs(coord) for coord in list(local_axis)]
|
||||
# Use local X, Y or Z axis for comparison:
|
||||
if z >= x and z >= y:
|
||||
local_comp_vec = App.Vector(0, 0, 1)
|
||||
elif y >= x and y >= z:
|
||||
local_comp_vec = App.Vector(0, -1, 0) # -Y to match the Front view
|
||||
else:
|
||||
local_comp_vec = App.Vector(1, 0, 0)
|
||||
comp_vec = ref_rot.multVec(local_comp_vec)
|
||||
axis = App.Vector(axis) # create independent copy
|
||||
if axis.getAngle(comp_vec) > math.pi/2:
|
||||
axis = axis.negative()
|
||||
return axis
|
||||
|
||||
|
||||
def make_circle(radius, placement=None, face=None, startangle=None, endangle=None, support=None):
|
||||
"""make_circle(radius, [placement, face, startangle, endangle])
|
||||
or make_circle(edge,[face]):
|
||||
"""make_circle(radius, [placement], [face], [startangle], [endangle])
|
||||
or make_circle(edge, [placement], [face]):
|
||||
|
||||
Creates a circle object with given parameters.
|
||||
|
||||
If startangle and endangle are provided and not equal, the object will show
|
||||
If startangle and endangle are provided and not equal, the object will be
|
||||
an arc instead of a full circle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : the radius of the circle.
|
||||
radius: the radius of the circle or the shape of a circular edge
|
||||
If it is an edge, startangle and endangle are ignored.
|
||||
edge.Curve must be a Part.Circle.
|
||||
|
||||
placement :
|
||||
If placement is given, it is used.
|
||||
placement: optional
|
||||
If radius is an edge, placement is adjusted to match the geometry
|
||||
of the edge. The Z axis of the adjusted placement will be parallel
|
||||
to the (negative) edge axis. Its Base will match the edge center.
|
||||
|
||||
face : Bool
|
||||
face: Bool
|
||||
If face is False, the circle is shown as a wireframe,
|
||||
otherwise as a face.
|
||||
|
||||
startangle : start angle of the circle (in degrees)
|
||||
startangle: start angle of the circle (in degrees)
|
||||
Recalculated if not in the -360 to 360 range.
|
||||
|
||||
endangle : end angle of the circle (in degrees)
|
||||
endangle: end angle of the circle (in degrees)
|
||||
Recalculated if not in the -360 to 360 range.
|
||||
|
||||
edge : edge.Curve must be a 'Part.Circle'
|
||||
The circle is created from the given edge.
|
||||
support: App::PropertyLinkSubList, optional
|
||||
It defaults to `None`.
|
||||
It is a list containing tuples to define the attachment
|
||||
of the new object.
|
||||
|
||||
support :
|
||||
TODO: Describe
|
||||
This parameter sets the `Support` property but it only really
|
||||
affects the position of the new object if its `MapMode` is
|
||||
set to other than `'Deactivated'`.
|
||||
"""
|
||||
|
||||
if not App.ActiveDocument:
|
||||
@@ -81,38 +106,36 @@ def make_circle(radius, placement=None, face=None, startangle=None, endangle=Non
|
||||
if placement:
|
||||
utils.type_check([(placement,App.Placement)], "make_circle")
|
||||
|
||||
if startangle != endangle:
|
||||
_name = "Arc"
|
||||
if (isinstance(radius, Part.Edge) and len(radius.Vertexes) > 1) \
|
||||
or startangle != endangle:
|
||||
name = "Arc"
|
||||
else:
|
||||
_name = "Circle"
|
||||
name = "Circle"
|
||||
|
||||
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", _name)
|
||||
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", name)
|
||||
|
||||
Circle(obj)
|
||||
|
||||
if face is not None:
|
||||
obj.MakeFace = face
|
||||
|
||||
if isinstance(radius,Part.Edge):
|
||||
if isinstance(radius, Part.Edge) and DraftGeomUtils.geomType(radius) == "Circle":
|
||||
edge = radius
|
||||
if DraftGeomUtils.geomType(edge) == "Circle":
|
||||
obj.Radius = edge.Curve.Radius
|
||||
placement = App.Placement(edge.Placement)
|
||||
delta = edge.Curve.Center.sub(placement.Base)
|
||||
placement.move(delta)
|
||||
# Rotation of the edge
|
||||
rotOk = App.Rotation(edge.Curve.XAxis, edge.Curve.YAxis, edge.Curve.Axis, "ZXY")
|
||||
placement.Rotation = rotOk
|
||||
if len(edge.Vertexes) > 1:
|
||||
v0 = edge.Curve.XAxis
|
||||
v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center)
|
||||
v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center)
|
||||
# Angle between edge.Curve.XAxis and the vector from center to start of arc
|
||||
a0 = math.degrees(App.Vector.getAngle(v0, v1))
|
||||
# Angle between edge.Curve.XAxis and the vector from center to end of arc
|
||||
a1 = math.degrees(App.Vector.getAngle(v0, v2))
|
||||
obj.FirstAngle = a0
|
||||
obj.LastAngle = a1
|
||||
obj.Radius = edge.Curve.Radius
|
||||
axis = edge.Curve.Axis
|
||||
ref_rot = App.Rotation() if placement is None else placement.Rotation
|
||||
normal = _get_normal(axis, ref_rot)
|
||||
x_axis = ref_rot.multVec(App.Vector(1, 0, 0))
|
||||
y_axis = ref_rot.multVec(App.Vector(0, 1, 0))
|
||||
rot = App.Rotation(x_axis, y_axis, normal, "ZXY")
|
||||
placement = App.Placement(edge.Curve.Center, rot)
|
||||
if len(edge.Vertexes) > 1:
|
||||
v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center)
|
||||
v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center)
|
||||
if not axis.isEqual(normal, 1e-4):
|
||||
v1, v2 = v2, v1
|
||||
obj.FirstAngle = math.degrees(DraftVecUtils.angle(x_axis, v1, normal))
|
||||
obj.LastAngle = math.degrees(DraftVecUtils.angle(x_axis, v2, normal))
|
||||
else:
|
||||
obj.Radius = radius
|
||||
if (startangle is not None) and (endangle is not None):
|
||||
|
||||
Reference in New Issue
Block a user