Macro TechDraw AuxiliaryView/de

Other languages:
FreeCADs Arbeitsbereich TechDraw lässt immer noch ein Werkzeug zum Erstellen von Hilfsansichten (auxiliary views) vermissen.
Diese Seite beschreibt einen Anwendungsfall und enthält ein Makro, das eine Möglichkeit darstellt, eine Hilfsansicht zu erstellen.

Hoffentlich ist dies eine Anregung für einen C++ Programmierer, um diese Konzeptbestätigung in einen integriertem TechDraw-Befehl umzuwandeln.

Generic macro icon. Create your personal icon with the same name of the macro Macro TechDraw AuxiliaryView

Description
Dieses Makro fügt einem TechDraw-Zeichnungsblatt eine Hilfsansicht (auxiliary view) hinzu

Macro version: 0.02
Last modified: 2025-06-27
Author: FBXL5
Author
FBXL5
Download
Links
Macro Version
0.02
Date last modified
2025-06-27
FreeCAD Version(s)
Default shortcut
None
See also

Einleitung

Nicht jeder, der eine Zeichnung mit seinem Modell mitliefern muss, kommt mit nur drei Hauptansichten (primären Ansichten) und Schnittansichten parallel zu diesen aus. Um ein Modell aus allen möglichen Winkeln abzubilden, sind Konstrukteure auf Hilfsansichten (auxiliary views, sekundäre Ansichten) angewiesen, die in einer bestimmten Ordnung auf einem Zeichenblatt positioniert werden, um den Weg nachvollziehen zu können, auf dem jede Ansicht in einer Kette festgelegt wurde. Unzusammenhängende Ansichten, im 3D-Raum festgelegt und beliebig auf dem Zeichenblatt positioniert, sind kein sinnvoller Ersatz für richtig angeordnete Hilfsansichten.

Bis jetzt stellt diese Seite nur ein Makro zur Verfügung, das zeigt, dass es möglich ist, eine Hilfsansicht innerhalb einer Basisansicht festzulegen, ohne den Umweg über den 3D-Raum zu nehmen.

Beschreibung

Das Macro TechDraw AuxiliaryView fügt einem TechDraw-Zeichnungsblatt eine Hilfsansicht (AuxView-Objekt) hinzu. Dies ist eine einfache Ansicht, die in einer Basisansicht angelegt wird, anstatt im 3D-Raum.

Eine Hilfsansicht, die durch die rote Linie in der Basisansicht auf der rechten Seite bestimmt wird


Beispielanwendung

Gehen wir davon aus, dass uns eine netzparallele Ansicht vorliegt, aus der wir andere Ansichten entwickeln wollen, um die wahren Formen der Formelemente darzustellen, die bemaßt werden sollen.

Die Schlüsselidee ist, eine gerade Linie in der Basisansicht auszuwählen, und senkrecht darauf eine Hilfsansicht festzulegen, die als Basisansicht für eine weitere Hilfsansicht entlang dieser geraden Linie dient. Wir können die Hilfsansichten aneinanderketten, bis wir alle drei Hauptansichten des Objekts und eine entlang der zylindrischen Teilform erstellt haben:

Die beliebig positionierte gerade Linie (rot) in der Startansicht (Start view) wird als Linie mit wahrer Länge in die Hilfsansicht X und als Punkt in die Hilfsansicht Y projiziert, wo auch die wahre Breite und die wahre Stärke der Bodenplatte erkennen. Hilfsansicht Z wird senkrecht zur Bodenplatte festgelegt und stellt die wahre form der Bodenplatte und ihrer Löcher dar. Hilfsansicht R steht senkrecht auf der längeren Kante der Bodenplatte und zeigt die wahren Winkel der Boden- und Deckelflächen der zylindrischen Teilform und wie sie an die Bodenplatte passt. Die letzte, Hilfsansicht S, wird entlang der Zylinderachse festgelegt und zeigt den Durchmesser und die Wandstärke der zylindrischen Teilform.

Das manuelle Zeichnen von Hilfsansichten erfordert eine zweite Basisansicht, um auf die Höhen über der Ebene der Basisansicht zuzugreifen, aber da FreeCAD die ganze Projektionsarbeit übernimmt, können wir uns allein auf die Richtung der Ansicht (Blickrichtung) konzentrieren.

Anwendung

  1. Sicherstellen, dass Ansichtsrahmen eingeschaltet und Knotenpunkte auswählbar sind.
  2. Zwei Knotenpunkte in derselben Ansicht auswählen.
  3. Dieses Makro ausführen.
  4. Eine neue Hilfsansicht AuxView wird erstellt, einschließlich der Benennung "AuxView X" der Ansicht (ein einfaches Textfeld).
  5. Ein Ansichtspfeil wird der Basisansicht hinzugefügt, einschließlich der Richtungsbenennung "X".
  6. Die Hilfsansicht auf die gewünschte Position ziehen.
  7. Die Benennung der Richtung bearbeiten und die Benennung der Ansicht entsprechend anpassen.

Hinweise


Ausklappen, um das Makro zu kopieren und einzufügen

Die Schaltfläche Makros... drücken und ein neues Makro erstellen. Das folgende Makro kopieren und in den Makro-Editor einzufügen.

#! python
# -*- coding: utf-8 -*-
"""
This script creates an Auxiliary View from two selectec vertices
of one base view.

What is required: an active document, a drawing page containing one
or more views with a projection of a shape.

A possible workflow:
1. Extract base view from selection (two vertices)
2. Extract work page from app objects
3. Add a new view
4. Link base view from selection to new view
5. Link base view source to new view source
6. Retrieve new view's x direction from base view's x direction
7. calculate new view's z direction from new view's x direction
    and base view's z direction
8. Set new view rotation including base view rotation
9. (Add annotations etc.)

I have tried to follow this naming rule:
 class names:    CamelCase
 function names: mixedCase
 constant names: ALL_CAPITAL + underscore
 variable names: lower_case + underscore
"""

__Name__= ""
__Comment__ = ""
__Author__ = "FBXL5"
__Version__ = "00.02"
__Date__    = "2025-06-27"
__License__ = "LGPL-2.0-or-later as FreeCAD"
__Web__ = ""
__Wiki__ = ""
__Icon__  = ""
__IconW__  = ""
__Help__ = ""
__Status__ = "Alpha"
__Requires__ = "FreeCAD >= 1.0 + Python3"
__Communication__ = "https://www.freecad.org/wiki/index.php?title=User: FBXL5"
__Files__ = ""

import math # to use some predefined conversions
from PySide.QtGui import (QMessageBox)

ARROW = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
  xmlns="http://www.w3.org/2000/svg" version="1.1"
  width ="16.8mm"
  height="2.8mm"
  viewBox=" 0 -1.4 16.8 2.8">
    <g id="ArrowHead"
      style="fill:#000;fill-opacity:1;stroke:#000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;font-size:5.0;text-anchor:middle;font-family:osifont">
        <path d="m 0.3 0.0 h 16 " />
        <path d="m 0.3 0.0 l 8.0 1.05 v -2.1 z " />
    </g>
</svg>
"""

def displayMessage(title,message):
    '''
    displayMessage('Title','Messagetext')
    '''
    message_box = QMessageBox()
    message_box.setText(message)
    message_box.setWindowTitle(title)
    message_box.exec()

def getActiveDocument():
    '''
    Returns the active document or sends a message
    '''
    ado = App.activeDocument()
    if ado is not None:
        return ado
    displayMessage("AuxView", "No active document available!")
    return False

# (borrowed from TechDraw sources)
def getSelView(nSel=0):
    '''
    view = getSelView()
    nSel=0 ... number of selected view, 0 = first selected
    Return selected view, otherwise return False
    '''
    if not Gui.Selection.getSelection():
        view = None
        displayMessage('AuxView','No view selected')
    else:
        view = Gui.Selection.getSelection()[nSel]
    return view

# (borrowed from TechDraw sources)
def getSelVertexes(nVertex=1, nSel=0):
    '''
    vertexes = getSelVertexes(nVertex)
    nVertex=1 ... min. number of selected vertexes
    nSel=0 ... number of selected view, 0 = first selected
    Return a list of selected vertexes if at least nVertex vertexes are selected, otherwise return False
    '''
    if getSelView(nSel):
        view = getSelView(nSel)
    else:
        return False
    if not Gui.Selection.getSelectionEx():
        displayMessage('AuxView','No vertex selected')
        return False
    objectList = Gui.Selection.getSelectionEx()[nSel].SubElementNames

    vertexes = []
    for objectString in objectList:
        if objectString[0:6] == 'Vertex':
            vertexes.append(view.getVertexBySelection(objectString))

    if (len(vertexes) < nVertex):
        displayMessage('AuxView','Select at least '+
                        str(nVertex)+' vertices')
        return False
    else:
        return vertexes

def getPageOfSelection(doc, b_view):
    '''Retrieves the Page that holds the selected elements'''
    #- Find an object starting with 'Page' that contains the selected object
    for each in doc.Objects:
        if each.Name.startswith("Page"):  # [0:4] == 'Page':
            for item in each.OutList: # Search items belonging to a Page object
                if item.Name.startswith("ProjGroup"): # Look into projection groups
                    for view in item.OutList: # Search views belonging to a ProjGroup object
                        if view.Name == b_view.Name:
                            return each
                else:
                    if item.Name == b_view.Name:
                        return each
    return False

def getCcwAngle(vertex1,vertex2,view_rotation):
    '''Creates 3D vectors to calculate the 2D angle towards the x direction of the
    base view which is parallel to the page view's x direction.
    The direction of the XDirection property is not parallel to the view's
    x direction if the view is rotated! This angle also has to be taken into
    account to calculate the 3D angle'''
    #- Extract position vectors from the points
    vector_start = App.Vector(vertex1.X, vertex1.Y, vertex1.Z)
    vector_end   = App.Vector(vertex2.X, vertex2.Y, vertex2.Z)
    #- Calculate the 2D Direction vector from start vertex to end vertex
    # on the XY plane of the base view/work page (z = 0)
    direction = vector_end.sub(vector_start)
    x_direction = App.Vector(1, 0, 0)
    angle_x = math.degrees(direction.getAngle(x_direction))
    # getAngle() returns positive (absolute) values only (in rad)
    # -> convert to degrees and check orientation
    if vertex1.Y > vertex2.Y:
        angle_x *= -1  # switches angle orientation
    #- Turn back the base view rotation
    # angle_x is a float value now but view_rotation is deg
    #print('a. angle_x: ', angle_x)
    #print('b. view_rotation: ', float(view_rotation))
    angle_x -= float(view_rotation)
    return angle_x

def symbolAngle(vertex1,vertex2):
    '''
    Creates 3D vectors to calculate the 2D angle towards the x direction of the
    base view
    '''
    #- Extract position vectors from the points
    vector_start = FreeCAD.Vector(vertex1.X, vertex1.Y, vertex1.Z)
    vector_end   = FreeCAD.Vector(vertex2.X, vertex2.Y, vertex2.Z)
    #- Calculate the 2D Direction vector from start vertex to end vertex
    # on the XY plane of the base view/work page (z = 0)
    direction = vector_end.sub(vector_start)
    y_direction = FreeCAD.Vector(0, -1, 0)
    angle_y = math.degrees(direction.getAngle(y_direction))
    # getAngle() returns positive (absolute) values only (in rad)
    # -> convert to degrees and check orientation
    if vertex1.X > vertex2.X:
        angle_y *= -1  # switches angle orientation
    return angle_y

def main():
    ''' The main section, no more, no less '''
    # Operations are performed in the active document of the application
    #- Retrieve the active document
    active_doc = getActiveDocument()
    if not active_doc:  # (active_doc is None/False)
        return
    #- Retrieve the selection view and selected vertices
    if getSelView() and getSelVertexes(2):
        base_view = getSelView()
        vertices = getSelVertexes(2)  # required number of vertices
    else:
        return

    #- Retrieve the page that holds the view
    work_page = getPageOfSelection(active_doc, base_view)
    if not work_page:
        # this should always be true as selected vertices are already checked
        return
    # At this point the input elements are gathered:
    #  active_doc, work_page, base_view, and vertices

    #- Create a new view
    new_view = active_doc.addObject('TechDraw::DrawViewPart', 'AuxView')
    #- Add the new view to the page
    work_page.addView(new_view)
    #- Add a BaseView property to the new view
    new_view.addProperty('App::PropertyLink','BaseView')
    #- Link the BaseView object to the BaseView property
    new_view.BaseView = active_doc.getObject(base_view.Name)
    #- Hand over the source objects
    new_view.Source = new_view.BaseView.Source

    #- 2D: Calculate the ccw angle between the x axes of base view and new view
    turn_ccw = getCcwAngle(vertices[0],vertices[1],new_view.BaseView.Rotation)
    # Returns a float value representing degrees

    # 3D: Turn base_view.XDirection around base_view.Direction to get
    #     new_view.XDirection
    #- Create a rotation, angle input in float (for degrees), stored in rad
    around_direction = App.Rotation(new_view.BaseView.Direction, turn_ccw)
    #- Apply rotation to the base_view.XDirection
    new_view.XDirection = around_direction.multVec(new_view.BaseView.XDirection)
    #- The cross-product of base view Z and new view X gives new view Z direction
    new_view.Direction = new_view.BaseView.Direction.cross(new_view.XDirection)

    # 2D: Take base_view.Rotation into account, it has to be converted
    #     to float since it is stored in deg
    #- Add the rotation of the base view to the angle between the x axes
    new_view.Rotation = turn_ccw + float(new_view.BaseView.Rotation)
    Gui.runCommand('TechDraw_RedrawPage',0)
    # At this point the Auxiliary View is complete

    #- Create an arrow symbol
    new_symbol = active_doc.addObject('TechDraw::DrawViewSymbol', 'Symbol')
    new_symbol.Symbol = ARROW
    new_symbol.Owner = base_view
    new_symbol.Rotation = symbolAngle(vertices[0],vertices[1])
    #- Add the new symbol to the page
    work_page.addView(new_symbol)
    #- Create a direction tag
    dir_tag = active_doc.addObject('TechDraw::DrawViewAnnotation', 'DirTag')
    dir_tag.Text = "X"
    dir_tag.Owner = new_symbol
    #- Add the new symbol to the page
    work_page.addView(dir_tag)

    #- Create a view tag
    view_tag = active_doc.addObject('TechDraw::DrawViewAnnotation', 'ViewTag')
    view_tag.Text = "AuxView X"
    view_tag.Owner = new_view
    #- Add the new symbol to the page
    work_page.addView(view_tag)

    FreeCADGui.runCommand("TechDraw_RedrawPage",0)
    FreeCADGui.runCommand("TechDraw_RedrawPage",0)

    return

if __name__ == "__main__":
    # This will be true only if the file is "executed"
    # but not if imported as module
    main()