FreeCAD

Форк
0
/
make_dimension.py 
618 строк · 21.4 Кб
1
# -*- coding: utf-8 -*-
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> *
6
# *                                                                         *
7
# *   This file is part of the FreeCAD CAx development system.              *
8
# *                                                                         *
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.                                 *
14
# *                                                                         *
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.                  *
19
# *                                                                         *
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  *
23
# *   USA                                                                   *
24
# *                                                                         *
25
# ***************************************************************************
26
"""Provides functions to create LinearDimension or AngularDinemsion objects.
27

28
This includes linear dimensions, radial dimensions, and angular dimensions.
29
"""
30
## @package make_dimension
31
# \ingroup draftmake
32
# \brief Provides functions to create Linear or AngularDimension objects.
33

34
## \addtogroup draftmake
35
# @{
36
import math
37

38
import FreeCAD as App
39
import WorkingPlane
40

41
from draftutils import gui_utils
42
from draftutils import utils
43
from draftutils.messages import _wrn, _err
44
from draftutils.translate import translate
45

46
from draftobjects.dimension import LinearDimension, AngularDimension
47

48
if App.GuiUp:
49
    from draftviewproviders.view_dimension \
50
        import (ViewProviderLinearDimension,
51
                ViewProviderAngularDimension)
52

53

54
def make_dimension(p1, p2, p3=None, p4=None):
55
    """Create one of three types of dimension objects.
56

57
    In all dimensions the p3 parameter defines a point through which
58
    the dimension line will go through.
59

60
    The current line width and color will be used.
61

62
    Linear dimension
63
    ----------------
64
    - (p1, p2, p3): a simple linear dimension from p1 to p2
65

66
    - (object, i1, i2, p3): creates a linked dimension to the provided
67
      object (edge), measuring the distance between its vertices
68
      indexed i1 and i2
69

70
    Circular dimension
71
    ------------------
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".
75
    """
76
    if not App.ActiveDocument:
77
        _err("No active document. Aborting")
78
        return None
79

80
    new_obj = App.ActiveDocument.addObject("App::FeaturePython",
81
                                           "Dimension")
82
    LinearDimension(new_obj)
83

84
    if App.GuiUp:
85
        ViewProviderLinearDimension(new_obj.ViewObject)
86

87
    if isinstance(p1, App.Vector) and isinstance(p2, App.Vector):
88
        # Measure a straight distance between p1 and p2
89
        new_obj.Start = p1
90
        new_obj.End = p2
91
        if not p3:
92
            p3 = p2.sub(p1)
93
            p3.multiply(0.5)
94
            p3 = p1.add(p3)
95

96
    elif isinstance(p2, int) and isinstance(p3, int):
97
        # p1 is an object, and measure the distance between vertices p2 and p3
98
        # of this object
99
        linked = []
100
        idx = (p2, p3)
101
        linked.append((p1, "Vertex" + str(p2 + 1)))
102
        linked.append((p1, "Vertex" + str(p3 + 1)))
103
        new_obj.LinkedGeometry = linked
104
        new_obj.Support = p1
105

106
        # p4, and now p3, is the point through which the dimension line
107
        # will go through
108
        p3 = p4
109
        if not p3:
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
117
            p3 = v2.sub(v1)
118
            p3.multiply(0.5)
119
            p3 = v1.add(p3)
120

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
124
        linked = []
125
        linked.append((p1, "Edge" + str(p2 + 1)))
126

127
        if p3 == "radius":
128
            # linked.append((p1, "Center"))
129
            if App.GuiUp:
130
                new_obj.ViewObject.Override = "R $dim"
131
            new_obj.Diameter = False
132
        elif p3 == "diameter":
133
            # linked.append((p1, "Diameter"))
134
            if App.GuiUp:
135
                new_obj.ViewObject.Override = "Ø $dim"
136
            new_obj.Diameter = True
137
        new_obj.LinkedGeometry = linked
138
        new_obj.Support = p1
139

140
        # p4, and now p3, is the point through which the dimension line
141
        # will go through
142
        p3 = p4
143
        if not p3:
144
            p3 = p1.Shape.Edges[p2].Curve.Center.add(App.Vector(1, 0, 0))
145

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
149
    new_obj.Dimline = p3
150

151
    normal = WorkingPlane.get_working_plane(update=False).axis
152

153
    if App.GuiUp:
154
        # invert the normal if we are viewing it from the back
155
        vnorm = gui_utils.get3DView().getViewDirection()
156

157
        if vnorm.getAngle(normal) < math.pi/2:
158
            normal = normal.negative()
159

160
    new_obj.Normal = normal
161

162
    if App.GuiUp:
163
        gui_utils.format_object(new_obj)
164
        gui_utils.select(new_obj)
165

166
    return new_obj
167

168

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'."))
173

174
    return make_dimension(p1, p2, p3, p4)
175

176

177
def make_linear_dimension(p1, p2, dim_line=None):
178
    """Create a free linear dimension from two main points.
179

180
    Parameters
181
    ----------
182
    p1: Base::Vector3
183
        First point of the measurement.
184

185
    p2: Base::Vector3
186
        Second point of the measurement.
187

188
    dim_line: Base::Vector3, optional
189
        It defaults to `None`.
190
        This is a point through which the extension of the dimension line
191
        will pass.
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`.
194

195
        If it is `None`, this point will be calculated from the intermediate
196
        distance between `p1` and `p2`.
197

198
    Returns
199
    -------
200
    App::FeaturePython
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).
204

205
    None
206
        If there is a problem it will return `None`.
207
    """
208
    _name = "make_linear_dimension"
209

210
    found, doc = utils.find_doc(App.activeDocument())
211
    if not found:
212
        _err(translate("draft","No active document. Aborting."))
213
        return None
214

215
    try:
216
        utils.type_check([(p1, App.Vector)], name=_name)
217
    except TypeError:
218
        _err(translate("draft","Wrong input: must be a vector."))
219
        return None
220

221
    try:
222
        utils.type_check([(p2, App.Vector)], name=_name)
223
    except TypeError:
224
        _err(translate("draft","Wrong input: must be a vector."))
225
        return None
226

227
    if dim_line:
228
        try:
229
            utils.type_check([(dim_line, App.Vector)], name=_name)
230
        except TypeError:
231
            _err(translate("draft","Wrong input: must be a vector."))
232
            return None
233
    else:
234
        diff = p2.sub(p1)
235
        diff.multiply(0.5)
236
        dim_line = p1.add(diff)
237

238
    new_obj = make_dimension(p1, p2, dim_line)
239

240
    return new_obj
241

242

243
def make_linear_dimension_obj(edge_object, i1=1, i2=2, dim_line=None):
244
    """Create a linear dimension from an object.
245

246
    Parameters
247
    ----------
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.
252

253
    i1: int, optional
254
        It defaults to `1`.
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
258
        as `'Vertex1'`.
259

260
        If the value is below `1`, it will be set to `1`.
261

262
    i2: int, optional
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.
266

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`.
269

270
        If the value is below `1`, it will be set to the last vertex
271
        in `edge_object`.
272

273
        Then to measure the first and last, this could be used
274
        ::
275
            make_linear_dimension_obj(edge_object, i1=1, i2=-1)
276

277
    dim_line: Base::Vector3
278
        It defaults to `None`.
279
        This is a point through which the extension of the dimension line
280
        will pass.
281
        This point controls how close or how far the dimension line is
282
        positioned from the measured segment in `edge_object`.
283

284
        If it is `None`, this point will be calculated from the intermediate
285
        distance between the vertices defined by `i1` and `i2`.
286

287
    Returns
288
    -------
289
    App::FeaturePython
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).
293

294
    None
295
        If there is a problem it will return `None`.
296
    """
297
    _name = "make_linear_dimension_obj"
298

299
    found, doc = utils.find_doc(App.activeDocument())
300
    if not found:
301
        _err(translate("draft","No active document. Aborting."))
302
        return None
303

304
    if isinstance(edge_object, (list, tuple)):
305
        _err(translate("draft","Wrong input: edge_object must not be a list or tuple."))
306
        return None
307

308
    found, edge_object = utils.find_object(edge_object, doc)
309
    if not found:
310
        _err(translate("draft","Wrong input: edge_object not in document."))
311
        return None
312

313
    if not hasattr(edge_object, "Shape"):
314
        _err(translate("draft","Wrong input: object doesn't have a 'Shape' to measure."))
315
        return None
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."))
319
        return None
320

321
    try:
322
        utils.type_check([(i1, int)], name=_name)
323
    except TypeError:
324
        _err(translate("draft","Wrong input: must be an integer."))
325
        return None
326

327
    if i1 < 1:
328
        i1 = 1
329
        _wrn(translate("draft","i1: values below 1 are not allowed; will be set to 1."))
330

331
    vx1 = edge_object.getSubObject("Vertex" + str(i1))
332
    if not vx1:
333
        _err(translate("draft","Wrong input: vertex not in object."))
334
        return None
335

336
    try:
337
        utils.type_check([(i2, int)], name=_name)
338
    except TypeError:
339
        _err(translate("draft","Wrong input: must be a vector."))
340
        return None
341

342
    if i2 < 1:
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."))
345

346
    vx2 = edge_object.getSubObject("Vertex" + str(i2))
347
    if not vx2:
348
        _err(translate("draft","Wrong input: vertex not in object."))
349
        return None
350

351
    if dim_line:
352
        try:
353
            utils.type_check([(dim_line, App.Vector)], name=_name)
354
        except TypeError:
355
            _err(translate("draft","Wrong input: must be a vector."))
356
            return None
357
    else:
358
        diff = vx2.Point.sub(vx1.Point)
359
        diff.multiply(0.5)
360
        dim_line = vx1.Point.add(diff)
361

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.
365
    i1 -= 1
366
    i2 -= 1
367

368
    new_obj = make_dimension(edge_object, i1, i2, dim_line)
369

370
    return new_obj
371

372

373
def make_radial_dimension_obj(edge_object, index=1, mode="radius",
374
                              dim_line=None):
375
    """Create a radial or diameter dimension from an arc object.
376

377
    Parameters
378
    ----------
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
383
        its radius.
384

385
    index: int, optional
386
        It defaults to `1`.
387
        It is the index of the edge in `edge_object` which is going to
388
        be measured.
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`.
391

392
    mode: str, optional
393
        It defaults to `'radius'`; the other option is `'diameter'`.
394
        It determines whether the dimension will be shown as a radius
395
        or as a diameter.
396

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.
402

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.
406

407
    Returns
408
    -------
409
    App::FeaturePython
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).
413

414
    None
415
        If there is a problem it will return `None`.
416
    """
417
    _name = "make_radial_dimension_obj"
418

419
    found, doc = utils.find_doc(App.activeDocument())
420
    if not found:
421
        _err(translate("draft","No active document. Aborting."))
422
        return None
423

424
    found, edge_object = utils.find_object(edge_object, doc)
425
    if not found:
426
        _err(translate("draft","Wrong input: edge_object not in document."))
427
        return None
428

429
    if not hasattr(edge_object, "Shape"):
430
        _err(translate("draft","Wrong input: object doesn't have a 'Shape' to measure."))
431
        return None
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."))
435
        return None
436

437
    try:
438
        utils.type_check([(index, int)], name=_name)
439
    except TypeError:
440
        _err(translate("draft","Wrong input: must be an integer."))
441
        return None
442

443
    if index < 1:
444
        index = 1
445
        _wrn(translate("draft","index: values below 1 are not allowed; will be set to 1."))
446

447
    edge = edge_object.getSubObject("Edge" + str(index))
448
    if not edge:
449
        _err(translate("draft","Wrong input: index doesn't correspond to an edge in the object."))
450
        return None
451

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."))
454
        return None
455

456
    try:
457
        utils.type_check([(mode, str)], name=_name)
458
    except TypeError:
459
        _err(translate("draft","Wrong input: must be a string, 'radius' or 'diameter'."))
460
        return None
461

462
    if mode not in ("radius", "diameter"):
463
        _err(translate("draft","Wrong input: must be a string, 'radius' or 'diameter'."))
464
        return None
465

466
    if dim_line:
467
        try:
468
            utils.type_check([(dim_line, App.Vector)], name=_name)
469
        except TypeError:
470
            _err(translate("draft","Wrong input: must be a vector."))
471
            return None
472
    else:
473
        center = edge_object.Shape.Edges[index - 1].Curve.Center
474
        dim_line = center + App.Vector(1, 0, 0)
475

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.
479
    index -= 1
480

481
    new_obj = make_dimension(edge_object, index, mode, dim_line)
482

483
    return new_obj
484

485

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.
490

491
    Parameters
492
    ----------
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.
496

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.
502
        ::
503
            angles = [0 90]
504
            angles = [330 60]  # the arc crosses the X axis
505
            angles = [-30 60]  # same angle
506

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,
511
        the circular arc.
512

513
    normal: Base::Vector3, optional
514
        It defaults to `None`, in which case the axis of the current working
515
        plane is used.
516

517
    Returns
518
    -------
519
    App::FeaturePython
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).
523

524
    None
525
        If there is a problem it will return `None`.
526
    """
527
    _name = "make_angular_dimension"
528

529
    # Prevent later modification of a default parameter by using a placeholder
530
    if angles is None:
531
        angles = [0, 90]
532

533
    found, doc = utils.find_doc(App.activeDocument())
534
    if not found:
535
        _err(translate("draft","No active document. Aborting."))
536
        return None
537

538
    try:
539
        utils.type_check([(center, App.Vector)], name=_name)
540
    except TypeError:
541
        _err(translate("draft","Wrong input: must be a vector."))
542
        return None
543

544
    try:
545
        utils.type_check([(angles, (tuple, list))], name=_name)
546

547
        if len(angles) != 2:
548
            _err(translate("draft","Wrong input: must be a list with two angles."))
549
            return None
550

551
        ang1, ang2 = angles
552
        utils.type_check([(ang1, (int, float)),
553
                          (ang2, (int, float))], name=_name)
554
    except TypeError:
555
        _err(translate("draft","Wrong input: must be a list with two angles."))
556
        return None
557

558
    # If the angle is larger than 360 degrees, make sure
559
    # it is smaller than 360
560
    for n in range(len(angles)):
561
        if angles[n] > 360:
562
            angles[n] = angles[n] - 360
563

564
    try:
565
        utils.type_check([(dim_line, App.Vector)], name=_name)
566
    except TypeError:
567
        _err(translate("draft","Wrong input: must be a vector."))
568
        return None
569

570
    if normal:
571
        try:
572
            utils.type_check([(dim_line, App.Vector)], name=_name)
573
        except TypeError:
574
            _err(translate("draft","Wrong input: must be a vector."))
575
            return None
576

577
    if not normal:
578
        normal = WorkingPlane.get_working_plane(update=False).axis
579

580
    new_obj = App.ActiveDocument.addObject("App::FeaturePython",
581
                                           "Dimension")
582
    AngularDimension(new_obj)
583

584
    new_obj.Center = center
585
    new_obj.FirstAngle = angles[0]
586
    new_obj.LastAngle = angles[1]
587
    new_obj.Dimline = dim_line
588

589
    if App.GuiUp:
590
        ViewProviderAngularDimension(new_obj.ViewObject)
591

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()
598

599
    new_obj.Normal = normal
600

601
    if App.GuiUp:
602
        gui_utils.format_object(new_obj)
603
        gui_utils.select(new_obj)
604

605
    return new_obj
606

607

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")
611

612
    ang1, ang2 = angles
613
    angles = [math.degrees(ang2), math.degrees(ang1)]
614

615
    return make_angular_dimension(center=center, angles=angles,
616
                                  dim_line=p3, normal=normal)
617

618
## @}
619

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.