2
# ***************************************************************************
3
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
4
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
5
# * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
7
# * This file is part of the FreeCAD CAx development system. *
9
# * This program is free software; you can redistribute it and/or modify *
10
# * it under the terms of the GNU Lesser General Public License (LGPL) *
11
# * as published by the Free Software Foundation; either version 2 of *
12
# * the License, or (at your option) any later version. *
13
# * for detail see the LICENCE text file. *
15
# * FreeCAD is distributed in the hope that it will be useful, *
16
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18
# * GNU Library General Public License for more details. *
20
# * You should have received a copy of the GNU Library General Public *
21
# * License along with FreeCAD; if not, write to the Free Software *
22
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
25
# ***************************************************************************
26
"""Provides functions to create LinearDimension or AngularDinemsion objects.
28
This includes linear dimensions, radial dimensions, and angular dimensions.
30
## @package make_dimension
32
# \brief Provides functions to create Linear or AngularDimension objects.
34
## \addtogroup draftmake
41
from draftutils import gui_utils
42
from draftutils import utils
43
from draftutils.messages import _wrn, _err
44
from draftutils.translate import translate
46
from draftobjects.dimension import LinearDimension, AngularDimension
49
from draftviewproviders.view_dimension \
50
import (ViewProviderLinearDimension,
51
ViewProviderAngularDimension)
54
def make_dimension(p1, p2, p3=None, p4=None):
55
"""Create one of three types of dimension objects.
57
In all dimensions the p3 parameter defines a point through which
58
the dimension line will go through.
60
The current line width and color will be used.
64
- (p1, p2, p3): a simple linear dimension from p1 to p2
66
- (object, i1, i2, p3): creates a linked dimension to the provided
67
object (edge), measuring the distance between its vertices
72
- (arc, i1, mode, p3): creates a linked dimension to the given arc
73
object, i1 is the index of the arc edge that will be measured;
74
mode is either "radius" or "diameter".
76
if not App.ActiveDocument:
77
_err("No active document. Aborting")
80
new_obj = App.ActiveDocument.addObject("App::FeaturePython",
82
LinearDimension(new_obj)
85
ViewProviderLinearDimension(new_obj.ViewObject)
87
if isinstance(p1, App.Vector) and isinstance(p2, App.Vector):
88
# Measure a straight distance between p1 and p2
96
elif isinstance(p2, int) and isinstance(p3, int):
97
# p1 is an object, and measure the distance between vertices p2 and p3
101
linked.append((p1, "Vertex" + str(p2 + 1)))
102
linked.append((p1, "Vertex" + str(p3 + 1)))
103
new_obj.LinkedGeometry = linked
106
# p4, and now p3, is the point through which the dimension line
110
# When used from the GUI command, this will never run
111
# because p4 will always be assigned to a vector,
112
# so p3 will never be `None`.
113
# Moreover, `new_obj.Base` doesn't exist, and certainly `Shape`
114
# doesn't exist, so if this ever runs it will be an error.
115
v1 = new_obj.Base.Shape.Vertexes[idx[0]].Point
116
v2 = new_obj.Base.Shape.Vertexes[idx[1]].Point
121
elif isinstance(p3, str):
122
# If the original p3 is a string, we are measuring a circular arc
123
# p2 should be an integer number starting from 0
125
linked.append((p1, "Edge" + str(p2 + 1)))
128
# linked.append((p1, "Center"))
130
new_obj.ViewObject.Override = "R $dim"
131
new_obj.Diameter = False
132
elif p3 == "diameter":
133
# linked.append((p1, "Diameter"))
135
new_obj.ViewObject.Override = "Ø $dim"
136
new_obj.Diameter = True
137
new_obj.LinkedGeometry = linked
140
# p4, and now p3, is the point through which the dimension line
144
p3 = p1.Shape.Edges[p2].Curve.Center.add(App.Vector(1, 0, 0))
146
# This p3 is the point through which the dimension line will pass,
147
# but this may not be the original p3, it could have been p4
148
# depending on the first three parameter values
151
normal = WorkingPlane.get_working_plane(update=False).axis
154
# invert the normal if we are viewing it from the back
155
vnorm = gui_utils.get3DView().getViewDirection()
157
if vnorm.getAngle(normal) < math.pi/2:
158
normal = normal.negative()
160
new_obj.Normal = normal
163
gui_utils.format_object(new_obj)
164
gui_utils.select(new_obj)
169
def makeDimension(p1, p2, p3=None, p4=None):
170
"""Create a dimension. DEPRECATED. Use 'make_dimension'."""
171
_wrn(translate("draft","This function is deprecated. Do not use this function directly."))
172
_wrn(translate("draft","Use one of 'make_linear_dimension', or 'make_linear_dimension_obj'."))
174
return make_dimension(p1, p2, p3, p4)
177
def make_linear_dimension(p1, p2, dim_line=None):
178
"""Create a free linear dimension from two main points.
183
First point of the measurement.
186
Second point of the measurement.
188
dim_line: Base::Vector3, optional
189
It defaults to `None`.
190
This is a point through which the extension of the dimension line
192
This point controls how close or how far the dimension line is
193
positioned from the measured segment that goes from `p1` to `p2`.
195
If it is `None`, this point will be calculated from the intermediate
196
distance between `p1` and `p2`.
201
A scripted object of type `'LinearDimension'`.
202
This object does not have a `Shape` attribute, as the text and lines
203
are created on screen by Coin (pivy).
206
If there is a problem it will return `None`.
208
_name = "make_linear_dimension"
210
found, doc = utils.find_doc(App.activeDocument())
212
_err(translate("draft","No active document. Aborting."))
216
utils.type_check([(p1, App.Vector)], name=_name)
218
_err(translate("draft","Wrong input: must be a vector."))
222
utils.type_check([(p2, App.Vector)], name=_name)
224
_err(translate("draft","Wrong input: must be a vector."))
229
utils.type_check([(dim_line, App.Vector)], name=_name)
231
_err(translate("draft","Wrong input: must be a vector."))
236
dim_line = p1.add(diff)
238
new_obj = make_dimension(p1, p2, dim_line)
243
def make_linear_dimension_obj(edge_object, i1=1, i2=2, dim_line=None):
244
"""Create a linear dimension from an object.
248
edge_object: Part::Feature
249
The object which has an edge which will be measured.
250
It must have a `Part::TopoShape`, and at least one element
251
in `Shape.Vertexes`, to be able to measure a distance.
255
It is the index of the first vertex in `edge_object` from which
256
the measurement will be taken.
257
The minimum value should be `1`, which will be interpreted
260
If the value is below `1`, it will be set to `1`.
263
It defaults to `2`, which will be converted to `'Vertex2'`.
264
It is the index of the second vertex in `edge_object` that determines
265
the endpoint of the measurement.
267
If it is the same value as `i1`, the resulting measurement will be
268
made from the origin `(0, 0, 0)` to the vertex indicated by `i1`.
270
If the value is below `1`, it will be set to the last vertex
273
Then to measure the first and last, this could be used
275
make_linear_dimension_obj(edge_object, i1=1, i2=-1)
277
dim_line: Base::Vector3
278
It defaults to `None`.
279
This is a point through which the extension of the dimension line
281
This point controls how close or how far the dimension line is
282
positioned from the measured segment in `edge_object`.
284
If it is `None`, this point will be calculated from the intermediate
285
distance between the vertices defined by `i1` and `i2`.
290
A scripted object of type `'LinearDimension'`.
291
This object does not have a `Shape` attribute, as the text and lines
292
are created on screen by Coin (pivy).
295
If there is a problem it will return `None`.
297
_name = "make_linear_dimension_obj"
299
found, doc = utils.find_doc(App.activeDocument())
301
_err(translate("draft","No active document. Aborting."))
304
if isinstance(edge_object, (list, tuple)):
305
_err(translate("draft","Wrong input: edge_object must not be a list or tuple."))
308
found, edge_object = utils.find_object(edge_object, doc)
310
_err(translate("draft","Wrong input: edge_object not in document."))
313
if not hasattr(edge_object, "Shape"):
314
_err(translate("draft","Wrong input: object doesn't have a 'Shape' to measure."))
316
if (not hasattr(edge_object.Shape, "Vertexes")
317
or len(edge_object.Shape.Vertexes) < 1):
318
_err(translate("draft","Wrong input: object doesn't have at least one element in 'Vertexes' to use for measuring."))
322
utils.type_check([(i1, int)], name=_name)
324
_err(translate("draft","Wrong input: must be an integer."))
329
_wrn(translate("draft","i1: values below 1 are not allowed; will be set to 1."))
331
vx1 = edge_object.getSubObject("Vertex" + str(i1))
333
_err(translate("draft","Wrong input: vertex not in object."))
337
utils.type_check([(i2, int)], name=_name)
339
_err(translate("draft","Wrong input: must be a vector."))
343
i2 = len(edge_object.Shape.Vertexes)
344
_wrn(translate("draft","i2: values below 1 are not allowed; will be set to the last vertex in the object."))
346
vx2 = edge_object.getSubObject("Vertex" + str(i2))
348
_err(translate("draft","Wrong input: vertex not in object."))
353
utils.type_check([(dim_line, App.Vector)], name=_name)
355
_err(translate("draft","Wrong input: must be a vector."))
358
diff = vx2.Point.sub(vx1.Point)
360
dim_line = vx1.Point.add(diff)
362
# TODO: the internal function expects an index starting with 0
363
# so we need to decrease the value here.
364
# This should be changed in the future in the internal function.
368
new_obj = make_dimension(edge_object, i1, i2, dim_line)
373
def make_radial_dimension_obj(edge_object, index=1, mode="radius",
375
"""Create a radial or diameter dimension from an arc object.
379
edge_object: Part::Feature
380
The object which has a circular edge which will be measured.
381
It must have a `Part::TopoShape`, and at least one element
382
must be a circular edge in `Shape.Edges` to be able to measure
387
It is the index of the edge in `edge_object` which is going to
389
The minimum value should be `1`, which will be interpreted
390
as `'Edge1'`. If the value is below `1`, it will be set to `1`.
393
It defaults to `'radius'`; the other option is `'diameter'`.
394
It determines whether the dimension will be shown as a radius
397
dim_line: Base::Vector3, optional
398
It defaults to `None`.
399
This is a point through which the extension of the dimension line
400
will pass. The dimension line will be a radius or diameter
401
of the measured arc, extending from the center to the arc itself.
403
If it is `None`, this point will be set to one unit to the right
404
of the center of the arc, which will create a dimension line that is
405
horizontal, that is, parallel to the +X axis.
410
A scripted object of type `'LinearDimension'`.
411
This object does not have a `Shape` attribute, as the text and lines
412
are created on screen by Coin (pivy).
415
If there is a problem it will return `None`.
417
_name = "make_radial_dimension_obj"
419
found, doc = utils.find_doc(App.activeDocument())
421
_err(translate("draft","No active document. Aborting."))
424
found, edge_object = utils.find_object(edge_object, doc)
426
_err(translate("draft","Wrong input: edge_object not in document."))
429
if not hasattr(edge_object, "Shape"):
430
_err(translate("draft","Wrong input: object doesn't have a 'Shape' to measure."))
432
if (not hasattr(edge_object.Shape, "Edges")
433
or len(edge_object.Shape.Edges) < 1):
434
_err(translate("draft","Wrong input: object doesn't have at least one element in 'Edges' to use for measuring."))
438
utils.type_check([(index, int)], name=_name)
440
_err(translate("draft","Wrong input: must be an integer."))
445
_wrn(translate("draft","index: values below 1 are not allowed; will be set to 1."))
447
edge = edge_object.getSubObject("Edge" + str(index))
449
_err(translate("draft","Wrong input: index doesn't correspond to an edge in the object."))
452
if not hasattr(edge, "Curve") or edge.Curve.TypeId != 'Part::GeomCircle':
453
_err(translate("draft","Wrong input: index doesn't correspond to a circular edge."))
457
utils.type_check([(mode, str)], name=_name)
459
_err(translate("draft","Wrong input: must be a string, 'radius' or 'diameter'."))
462
if mode not in ("radius", "diameter"):
463
_err(translate("draft","Wrong input: must be a string, 'radius' or 'diameter'."))
468
utils.type_check([(dim_line, App.Vector)], name=_name)
470
_err(translate("draft","Wrong input: must be a vector."))
473
center = edge_object.Shape.Edges[index - 1].Curve.Center
474
dim_line = center + App.Vector(1, 0, 0)
476
# TODO: the internal function expects an index starting with 0
477
# so we need to decrease the value here.
478
# This should be changed in the future in the internal function.
481
new_obj = make_dimension(edge_object, index, mode, dim_line)
486
def make_angular_dimension(center=App.Vector(0, 0, 0),
487
angles=None, # If None, set to [0,90]
488
dim_line=App.Vector(10, 10, 0), normal=None):
489
"""Create an angular dimension from the given center and angles.
493
center: Base::Vector3, optional
494
It defaults to the origin `Vector(0, 0, 0)`.
495
Center of the dimension line, which is a circular arc.
497
angles: list of two floats, optional
498
It defaults to `[0, 90]`.
499
It is a list of two angles, given in degrees, that determine
500
the aperture of the dimension line, that is, of the circular arc.
501
It is drawn counter-clockwise.
504
angles = [330 60] # the arc crosses the X axis
505
angles = [-30 60] # same angle
507
dim_line: Base::Vector3, optional
508
It defaults to `Vector(10, 10, 0)`.
509
This is a point through which the extension of the dimension line
510
will pass. This defines the radius of the dimension line,
513
normal: Base::Vector3, optional
514
It defaults to `None`, in which case the axis of the current working
520
A scripted object of type `'AngularDimension'`.
521
This object does not have a `Shape` attribute, as the text and lines
522
are created on screen by Coin (pivy).
525
If there is a problem it will return `None`.
527
_name = "make_angular_dimension"
529
# Prevent later modification of a default parameter by using a placeholder
533
found, doc = utils.find_doc(App.activeDocument())
535
_err(translate("draft","No active document. Aborting."))
539
utils.type_check([(center, App.Vector)], name=_name)
541
_err(translate("draft","Wrong input: must be a vector."))
545
utils.type_check([(angles, (tuple, list))], name=_name)
548
_err(translate("draft","Wrong input: must be a list with two angles."))
552
utils.type_check([(ang1, (int, float)),
553
(ang2, (int, float))], name=_name)
555
_err(translate("draft","Wrong input: must be a list with two angles."))
558
# If the angle is larger than 360 degrees, make sure
559
# it is smaller than 360
560
for n in range(len(angles)):
562
angles[n] = angles[n] - 360
565
utils.type_check([(dim_line, App.Vector)], name=_name)
567
_err(translate("draft","Wrong input: must be a vector."))
572
utils.type_check([(dim_line, App.Vector)], name=_name)
574
_err(translate("draft","Wrong input: must be a vector."))
578
normal = WorkingPlane.get_working_plane(update=False).axis
580
new_obj = App.ActiveDocument.addObject("App::FeaturePython",
582
AngularDimension(new_obj)
584
new_obj.Center = center
585
new_obj.FirstAngle = angles[0]
586
new_obj.LastAngle = angles[1]
587
new_obj.Dimline = dim_line
590
ViewProviderAngularDimension(new_obj.ViewObject)
592
# Invert the normal if we are viewing it from the back.
593
# This is determined by the angle between the current
594
# 3D view and the provided normal being below 90 degrees
595
vnorm = gui_utils.get3DView().getViewDirection()
596
if vnorm.getAngle(normal) < math.pi/2:
597
normal = normal.negative()
599
new_obj.Normal = normal
602
gui_utils.format_object(new_obj)
603
gui_utils.select(new_obj)
608
def makeAngularDimension(center, angles, p3, normal=None):
609
"""Create an angle dimension. DEPRECATED. Use 'make_angular_dimension'."""
610
utils.use_instead("make_angular_dimension")
613
angles = [math.degrees(ang2), math.degrees(ang1)]
615
return make_angular_dimension(center=center, angles=angles,
616
dim_line=p3, normal=normal)