Some scripting considerations

Discussions about the development of the TechDraw workbench
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Some scripting considerations

Post by onekk »

Hello, I have done some experiment in scripting TechDraw, here some results:
  1. Overall impression good
  2. It is difficult to approach all the fine details about recomputation order, as code examples are not organized. I have had many crash in the proceedings, but now I have found a decent way to make things.
  3. Some helpers methods are needed to write come concise code, if not it will take a zillions lines to make simple things
  4. There are some need of organic examples, maybe next month I could have time to make something to comment and maybe use to start some discussions.
  5. Some thing are not directly accessible by Python, like circle centers and similar, this is a bad things, as it would be a great thing to have them accessible, or a quick way to place centers.
A little experiment, as I have the need to make some model view and ask the customer for proper dimensions, as some of them are missing.

anno1.png
anno1.png (17.64 KiB) Viewed 2211 times

This is some code used to annotate:

Code: Select all

def TDaddMlab(d_doc, page, mlab, mtyp, mref2d, mnote, mpos):
    """Add a measure to a page.

    Args:
        d_doc: FreeCAD.Document
        page: TechDraw::DrawPage instance
        mlab: Obj Label
        mtyp: Obj Type
        mref2d: Obj reference2d
        mnote: string
        mpos: tuple containing position X, Y relative to 'reference2d'
    """
    dim = d_doc.addObject('TechDraw::DrawViewDimension')
    dim.Label = mlab
    dim.Type = mtyp
    dim.References2D = mref2d

    if mnote != "":
        dim.FormatSpec = mnote
        dim.Arbitrary = True

    dim.X = mpos[0]
    dim.Y = mpos[1]
    dim.recompute()
    page.addView(dim)

# A little example of use


TDaddMlab(d_doc, page, "dim6", "Radius", [(view, 'Edge13')], "Hole Radius", (+19.0, +42.0))
            
refrence2d are passed using proper reference to the view, so no need to pass the view to the method.

Tested on:

Code: Select all

OS: Artix Linux (openbox)
Word size of FreeCAD: 64-bit
Version: 0.20.2.29603 (Git) AppImage
Build type: Release
Branch: (HEAD detached at 0.20.2)
Hash: 930dd9a76203a3260b1e6256c70c1c3cad8c5cb8
Python 3.10.8, Qt 5.15.4, Coin 4.0.0, Vtk 9.1.0, OCC 7.6.3
Locale: Italian/Italy (it_IT)
Installed mods: 
  * CurvedShapes 1.0.4
  * ScriptWB-v1
  * QuickMeasure 2022.10.28
  * freecad.gears 1.0.0
  * Assembly4 0.50.1
  * fasteners 0.4.56
  * toSketch 1.0.1
  * Curves 0.6.8
  * Help 1.0.3


Not too bad, so many thanks to developers.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
wandererfan
Veteran
Posts: 6268
Joined: Tue Nov 06, 2012 5:42 pm
Contact:

Re: Some scripting considerations

Post by wandererfan »

onekk wrote: Wed May 24, 2023 4:12 pm
Let us know what helpers/new functions are needed. Exposing existing c++ functions to python isn't a big deal (mostly).
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Some scripting considerations

Post by onekk »

wandererfan wrote: Wed May 24, 2023 5:59 pm ...
Yes thanks.

A way to have the center or the "cosmetic lines" for centers to ease the insertions of distances like center to center or center to an edge/point.

Another thing that it would be a great addition will be a way to specify a name (that will be printed under or over the dimension).

Not much more for this limited experiment, at least for now.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
wandererfan
Veteran
Posts: 6268
Joined: Tue Nov 06, 2012 5:42 pm
Contact:

Re: Some scripting considerations

Post by wandererfan »

onekk wrote: Wed May 24, 2023 7:32 pm A way to have the center or the "cosmetic lines" for centers to ease the insertions of distances like center to center or center to an edge/point.

Code: Select all

obj = doc.getObject("View")
e = obj.getEdgeBySelection("Edge9")
e.Curve.Center
Vector (2.8033008588991053, -6.685586535436919, 0.0)
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Some scripting considerations

Post by onekk »

wandererfan wrote: Fri May 26, 2023 1:10 pm ...

Code: Select all

obj = doc.getObject("View")
e = obj.getEdgeBySelection("Edge9")
e.Curve.Center
Vector (2.8033008588991053, -6.685586535436919, 0.0)
Thanks, and then, there is some code to place a point and refer to it in an automated way, something like.

Code: Select all

pnt = view.addPoint(e.Curve.Center)

- retrieve the vertex number
- create a dims (I know how to do this)


Sorry but I'm not near a computer, so only pseudo code.

TIA and regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
wandererfan
Veteran
Posts: 6268
Joined: Tue Nov 06, 2012 5:42 pm
Contact:

Re: Some scripting considerations

Post by wandererfan »

onekk wrote: Fri May 26, 2023 2:41 pm there is some code to place a point and refer to it in an automated way, something like.
This is still pretty accurate, but maybe incomplete: https://wiki.freecad.org/TechDraw_API

Code: Select all

vSel = Gui.Selection.getSelectionEx()
vSelObj = vSel[0]
vObj = vSelObj.Object
vSub = vSelObj.SubElementNames[0]

>>> vSub
'Vertex3'
>>> vObj.Name
'View'

vert = vObj.getVertexBySelection("Vertex3")
vpoint = vert.Point + App.Vector(10, 10, 10)

newVertTag = vObj.makeCosmeticVertex(vpoint)

>>> newVertTag
'cf894e85-b9a5-459c-ad01-f574239bd35c'

>>> vObj.getCosmeticVertex(newVertTag)
<CosmeticVertex object>
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Some scripting considerations

Post by onekk »

wandererfan wrote: Fri May 26, 2023 5:53 pm
onekk wrote: Fri May 26, 2023 2:41 pm there is some code to place a point and refer to it in an automated way, something like.
This is still pretty accurate, but maybe incomplete: https://wiki.freecad.org/TechDraw_API

Code: Select all

vSel = Gui.Selection.getSelectionEx()
vSelObj = vSel[0]
vObj = vSelObj.Object
vSub = vSelObj.SubElementNames[0]

>>> vSub
'Vertex3'
>>> vObj.Name
'View'

vert = vObj.getVertexBySelection("Vertex3")
vpoint = vert.Point + App.Vector(10, 10, 10)

newVertTag = vObj.makeCosmeticVertex(vpoint)

>>> newVertTag
'cf894e85-b9a5-459c-ad01-f574239bd35c'

>>> vObj.getCosmeticVertex(newVertTag)
<CosmeticVertex object>
Many thanks I will have a busy WE so probably monday I will test the code.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Some scripting considerations

Post by onekk »

wandererfan wrote: Fri May 26, 2023 5:53 pm ...

Hello, here some example, maybe too verbose, but it works and show probably the point:

Code: Select all

"""TechDraw Example.

file: 20230612-TechDraw_example.py

Copyright 2023 Carlo Dormeletti
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
"""

import importlib  # noqa
import math  # noqa
import os  # noqa
import sys  # noqa
from datetime import datetime

from math import asin, atan, cos, degrees,  pi, radians, sin, sqrt  # noqa

import FreeCAD  # noqa
import FreeCADGui  # noqa
import Part
#from ProfileLib import RegularPolygon
import Sketcher

# needed only if you want to do Techdraw page export
import TechDrawGui  # noqa

from FreeCAD import Placement, Rotation  # noqa

import PySide
from PySide import QtGui, QtCore

ver_nm = "1.0c"
today = datetime.today().strftime('%Y%m%d')

# USER MODIFIABLE
DOC_Name = f"TD_example{today}"

# Path management
MODULE_PATH = os.path.dirname(__file__)
HOME_DIR = os.path.expanduser('~')
RES_DIR = FreeCAD.getResourceDir()

DBG_LOAD = False

if MODULE_PATH not in sys.path:
    if DBG_LOAD is True:
        print("no module path")
    sys.path.insert(-1, MODULE_PATH)
else:
    if DBG_LOAD is True:
        print("module path is present")

# Assign shortest objects name
V3d = FreeCAD.Vector
V2d = FreeCAD.Base.Vector2d

# START Shortcuts
EPS = 0.01
EPS_C = EPS * -0.5

VEC0 = V3d(0, 0, 0)
ROT0 = Rotation(0, 0, 0)

DVZ = V3d(0, 0, 1)
DVX = V3d(1, 0, 0)
DVY = V3d(0, 1, 0)

# END Shortcuts

# SERVICE METHODS


def clear_doc(doc_name):
    """Clear the document deleting all the objects."""
    doc = FreeCAD.getDocument(doc_name)
    try:
        while len(doc.Objects) > 0:
            doc.removeObject(doc.Objects[0].Name)
    except Exception as e:
        print(f'Exception:  {e}')


def ensure_document(doc_name, action=0):
    """Ensure existence of document with doc_name and clear the doc."""
    doc_root = FreeCAD.listDocuments()
    doc_exist = False

    for d_name, doc in doc_root.items():
        # debug info do not delete
        print(f"ED: doc name = {d_name}")

        if d_name == doc_name:
            print("Match name")
            if action == 1:
                # when using clear_doc() is not possible like in Part.Design
                FreeCAD.closeDocument(doc_name)
                doc_exist = False
            elif action == 2:
                doc_exist = True
                clear_doc(doc_name)
                FreeCAD.setActiveDocument(d_name)
            else:
                doc_exist = True
                FreeCAD.setActiveDocument(d_name)

    if doc_exist is False:
        # debug info do not delete
        # print("ED: Create = {}".format(doc_name))
        FreeCAD.newDocument(doc_name)

    return FreeCAD.activeDocument().Name


def setview(doc_name, t_v=0):
    """Set viewport in 3D view."""
    FreeCAD.setActiveDocument("")
    FreeCAD.ActiveDocument = None
    FreeCADGui.ActiveDocument = None

    FreeCAD.setActiveDocument(doc_name)

    FreeCADGui.ActiveDocument = FreeCADGui.getDocument(doc_name)
    VIEW = FreeCADGui.ActiveDocument.ActiveView

    if t_v == 0:
        VIEW.viewTop()
    elif t_v == 1:
        VIEW.viewFront()
    else:
        VIEW.viewAxometric()
    VIEW.setAxisCross(True)
    VIEW.fitAll()


# Building methods


def circle_tangent(rad, ccen, p0):
    """Determine tangent points on a circle."""
    dx = p0.x - ccen.x
    dy = p0.y - ccen.y

    dxr, dyr = -dy, dx

    d = sqrt(dx**2 + dy**2)

    if d >= rad :
        rho = rad / d
        ad = rho**2
        bd = rho * sqrt(1 - rho**2)
        x1 = ccen.x + ad * dx + bd * dxr
        y1 = ccen.y + ad * dy + bd * dyr
        x2 = ccen.x + ad * dx - bd * dxr
        y2 = ccen.y + ad * dy - bd * dyr

    tp1 = V3d(x1, y1, 0)
    tp2 = V3d(x2, y2, 0)

    return tp1, tp2



# Assembly and PD modules


def get_body_plane(body, p_name="XY_Plane"):
    """Return the proper Object name for a plane."""
    for feat in body.Origin.OriginFeatures:
        if p_name in feat.Name:
            return feat
        else:
            None


def create_pd_body(doc, body_nm, lcs=False):
    """Create new Part Design Body."""
    newBody = doc.addObject("PartDesign::Body", body_nm)

    if lcs is True:
        # add an LCS at the root of the Part, and attach it to the 'Origin'
        lcs0 = newBody.newObject('PartDesign::CoordinateSystem', f'{body_nm}_lc0')
        lcs0.Support = [(newBody.Origin.OriginFeatures[0], '')]
        lcs0.MapMode = 'ObjectXY'
        lcs0.MapReversed = False

    newBody.recompute()
    doc.recompute()
    return newBody


def sketch_polyline(vertexList, closed=True, addHVConstraints=True):
    """Skecth a polyline."""
    geoList = []
    conList = []
    npts = len(vertexList)
    for i in range(npts - 1):
        geoList.append(Part.LineSegment(vertexList[i], vertexList[i + 1]))

    if closed:
        geoList.append(Part.LineSegment(vertexList[-1], vertexList[0]))

    for i in range(npts - 1):
        conList.append(Sketcher.Constraint('Coincident', i, 2, i + 1, 1))

    if closed:
        conList.append(Sketcher.Constraint('Coincident', npts - 1, 2, 0, 1))

    if addHVConstraints:
        for i, ls in enumerate(geoList):
            if abs(ls.tangent(0)[0].y) < 1e-14:
                conList.append(Sketcher.Constraint('Horizontal', i))
            elif abs(ls.tangent(0)[0].x) < 1e-14:
                conList.append(Sketcher.Constraint('Vertical', i))

    return (geoList, conList)


def sketch_rrect(sk, wid, lgt, c_rad, cent="bl", css=()):
    """Sketch a rounded rectangle.

    Inspired from FreeCAD echoed code.
    """
    xc1 = wid - c_rad
    yc1 = lgt - c_rad

    ac1 = sk.addGeometry(Part.ArcOfCircle(
        Part.Circle(V3d(c_rad, c_rad, 0), DVZ, c_rad), -pi, pi * -0.5))
    ln1 = sk.addGeometry(Part.LineSegment(V3d(0.0, c_rad, 0), V3d(0.0, yc1, 0)))
    ac2 = sk.addGeometry(Part.ArcOfCircle(
        Part.Circle(V3d(c_rad, yc1, 0), DVZ, c_rad), pi * -1.5, -pi))
    ln2 = sk.addGeometry(Part.LineSegment(V3d(c_rad, lgt, 0), V3d(xc1, lgt, 0)))
    ac3 = sk.addGeometry(Part.ArcOfCircle(
        Part.Circle(V3d(xc1, yc1, 0), DVZ, c_rad), pi * -2.0, pi * -1.5))
    ln3 = sk.addGeometry(Part.LineSegment(V3d(wid, yc1, 0), V3d(wid, c_rad, 0)))
    ac4 = sk.addGeometry(Part.ArcOfCircle(
        Part.Circle(V3d(xc1, c_rad, 0), DVZ, c_rad), pi * -2.5, pi * - 2.0))
    ln4 = sk.addGeometry(Part.LineSegment(V3d(xc1, 0.0, 0), V3d(c_rad, 0.0, 0)))

    # print(ac1, ac2, ac3, ac4)
    # print(ln1, ln2, ln3, ln4)

    conList = []
    conList.append(Sketcher.Constraint('Tangent', ac1, 1, ln1, 1))
    conList.append(Sketcher.Constraint('Tangent', ln1, 2, ac2, 2))
    conList.append(Sketcher.Constraint('Tangent', ac2, 1, ln2, 1))
    conList.append(Sketcher.Constraint('Tangent', ln2, 2, ac3, 2))
    conList.append(Sketcher.Constraint('Tangent', ac3, 1, ln3, 1))
    conList.append(Sketcher.Constraint('Tangent', ln3, 2, ac4, 2))
    conList.append(Sketcher.Constraint('Tangent', ac4, 1, ln4, 1))
    conList.append(Sketcher.Constraint('Tangent', ln4, 2, ac1, 2))
    conList.append(Sketcher.Constraint('Horizontal', ln2))
    conList.append(Sketcher.Constraint('Horizontal', ln4))
    conList.append(Sketcher.Constraint('Vertical', ln1))
    conList.append(Sketcher.Constraint('Vertical', ln3))

    sk.addConstraint(conList)

    del conList

    c_r1 = sk.addConstraint(Sketcher.Constraint('Radius', ac1, c_rad))
    cc_lst = [c_r1]

    if cent == 'cc':
        pass
    else:
        c_r2 = sk.addConstraint(Sketcher.Constraint('Radius', ac2, c_rad))
        cc_lst.append(c_r2)

    c_r3 = sk.addConstraint(Sketcher.Constraint('Radius', ac3, c_rad))
    cc_lst.append(c_r3)

    c_wid = sk.addConstraint(Sketcher.Constraint('DistanceX', ac1, 1, ln3, 2, wid))
    c_len = sk.addConstraint(Sketcher.Constraint('DistanceY', ac1, 2, ac2, 1, lgt))

    if css == ():
        pass
    else:
        sk.setExpression(f'Constraints[{c_wid}]', css[0])
        sk.setExpression(f'Constraints[{c_len}]', css[1])

        for c_num in cc_lst:
            sk.setExpression(f'Constraints[{c_num}]', css[2])

    blc = sk.addGeometry(Part.Point(V3d(0.0, 0.0, 0)), True)
    trc = sk.addGeometry(Part.Point(V3d(wid, lgt, 0)), True)

    conList = []
    conList.append(Sketcher.Constraint('PointOnObject', blc, 1, ln1))
    conList.append(Sketcher.Constraint('PointOnObject', blc, 1, ln4))
    conList.append(Sketcher.Constraint('PointOnObject', trc, 1, ln2))
    conList.append(Sketcher.Constraint('PointOnObject', trc, 1, ln3))
    sk.addConstraint(conList)
    del conList

    if cent == 'cc':
        sk.addConstraint(Sketcher.Constraint('Symmetric', ac1, 2, ac2, 1, -1))
        sk.addConstraint(Sketcher.Constraint('Symmetric', ac1, 1, ln3, 2, -2))
    elif cent == 'bl':
        sk.addConstraint(Sketcher.Constraint('Coincident', blc, 1, -1, 1))
    elif cent == 'cw':
        sk.addConstraint(Sketcher.Constraint('PointOnObject',1, 2, -2))
        sk.addConstraint(Sketcher.Constraint('Symmetric', 6, 1, 3, 2, -1))
    elif cent == 'cl':
        pass

    sk.recompute()

    return sk


def sketch_wire(sk, wire):
    """Create a correctly closed sketch from a list of edges.

    Portion of code courtesy of Roy_043
    """
    #
    geoList = []

    for w_edge in wire:
        pm1 = w_edge.ParameterRange[0]
        pm2 = w_edge.ParameterRange[1]
        e_curve = w_edge.Curve
        if hasattr(e_curve, "Axis") and vec_is_same(e_curve.Axis, V3d(0, 0, -1)):
            e_curve.reverse()
            pm1, pm2 = -pm2, pm1
        geoList.append(e_curve.trim(pm1, pm2))

    sk.addGeometry(geoList, False)

    conListEnds = []
    conListOther = []

    segPts = [(g.StartPoint, g.EndPoint) for g in geoList]
    segTans = [(g.tangent(g.FirstParameter)[0], g.tangent(g.LastParameter)[0]) for g in geoList]
    segs = list(range(len(geoList))) # List of indices.
    nxts = segs[1:] + [segs[0]]      # List of next indices.
    for seg, nxt in zip(segs, nxts):
        segGeo = geoList[seg]
        nxtGeo = geoList[nxt]
        segPtSta, segPtEnd = segPts[seg]
        nxtPtSta, nxtPtEnd = segPts[nxt]
        segTanSta, segTanEnd = segTans[seg]
        nxtTanSta, nxtTanEnd = segTans[nxt]

        conTyp = ""
        if vec_is_same(segPtEnd, nxtPtSta):
            conTyp = "Tangent" if tan_is_same(segTanEnd, nxtTanSta) else "Coincident"
            conListEnds.append(Sketcher.Constraint(conTyp, seg, 2, nxt, 1))
        elif vec_is_same(segPtEnd, nxtPtEnd):
            conTyp = "Tangent" if tan_is_same(segTanEnd, nxtTanEnd) else "Coincident"
            conListEnds.append(Sketcher.Constraint(conTyp, seg, 2, nxt, 2))
        elif vec_is_same(segPtSta, nxtPtSta):
            conTyp = "Tangent" if tan_is_same(segTanSta, nxtTanSta) else "Coincident"
            conListEnds.append(Sketcher.Constraint(conTyp, seg, 1, nxt, 1))
        elif vec_is_same(segPtSta, nxtPtEnd):
            conTyp = "Tangent" if tan_is_same(segTanSta, nxtTanEnd) else "Coincident"
            conListEnds.append(Sketcher.Constraint(conTyp, seg, 1, nxt, 2))

        if segGeo.TypeId == "Part::GeomLineSegment":
            # Avoid redundant Horizontal/Vertical constraint if the next
            # connected line is parallel:
            if conTyp == "Tangent" and nxtGeo.TypeId == "Part::GeomLineSegment":
                pass
            elif tan_is_same(segTanSta, V3d(1, 0, 0)):
                conListOther.append(Sketcher.Constraint("Horizontal", seg))
            elif tan_is_same(segTanSta, V3d(0, 1, 0)):
                conListOther.append(Sketcher.Constraint("Vertical", seg))
        elif segGeo.TypeId == "Part::GeomArcOfCircle":
            rad = segGeo.Radius
            conListOther.append(Sketcher.Constraint('Radius', seg, rad))

    sk.addConstraint(conListEnds)
    sk.addConstraint(conListOther)

    return sk


def tan_is_same(tanA, tanB):
    """Check if two tan are same, with some tolerances."""
    return tanA.isEqual(tanB, 1e-8) or tanA.isEqual(-tanB, 1e-8)


def vec_is_same(vecA, vecB):
    """Check if two vectors are equal, with some tolerance."""
    return vecA.isEqual(vecB, 1e-8)


# BUILDING CODE START HERE

# SPREADHEETS METHODS

def ss_color(d_sheet, cells, color):
    """Set cell color."""
    for c_ref in cells:
        d_sheet.setBackground(c_ref, color[0])
        d_sheet.setForeground(c_ref, color[1])


def init_doc_ss(d_doc, dbg_p=False):
    """Set spreadsheet headers."""
    #
    c_sheet = d_doc.addObject("Spreadsheet::Sheet", "ModelData")

    ss_head = (
        ('A1', 'Model'), ('D1', "Generic Data"),
         )

    ss_desc = (
        # Generic Data
        ('D2', 'GenRad'), ('D3', 'GenDepth'),
        ('A3', 'Radius 2'),
        ('A4', 'Height 1'), ('A5', 'Base Lenght'), ('A6', 'Base Width'),
        ('A7', 'Base Thickness'), ('A8', 'Displacement X'),
        ('A9', 'Bracket WaB'), ('A10', 'Bracket Top Rad'),
        ('A11', 'Bracket Height'), ('A12', 'Bracket Thickness'),
    )

    # ss_data format (cell, value, alias)
    ss_data = [
        # Generic
        ('E2', '5.0mm', 'gen_rad'),
        ('E3', '10.0mm', 'gen_dep'),
        ('B3', '89.0mm', 'mdm_r2'),
        ('B4', '19.0mm', 'mdm_h1'),
        ('B5', '110.0mm', 'mdm_blen'),
        ('B6', '130.0mm', 'mdm_bwid'),
        ('B7', '50.0mm', 'mdm_bthi'),
        ('B8', '45.0mm', 'mdm_bdpx'),
        ('B9', '100mm', 'md_bk_bw'),
        ('B10', '22.0mm', 'md_bk_tpr'),
        ('B11', '114.0mm', 'md_bk_hei'),
        ('B12', '25.0mm', 'md_bk_thi'),
    ]

    for head in ss_head:
        c_sheet.set(head[0], head[1])

    for desc in ss_desc:
        c_sheet.set(desc[0], desc[1])

   # Apply Color to Header Cells
    hd_cells = (v[0] for v in ss_head)

    hd_colors = ((0.0, 1.0, 1.0), (0.0, 0.0, 0.0))
    ss_color(c_sheet, hd_cells, hd_colors)

    # Apply Color to Descriptions Cells
    desc_cells = (v[0] for v in ss_desc)

    ds_colors = ((0.5, 1.0, 0.75), (0.0, 0.0, 0.0))
    ss_color(c_sheet, desc_cells, ds_colors)

    # Populate cells with data
    for data in ss_data:
        c_sheet.set(data[0], data[1])
        if len(data) > 2:
            c_sheet.setAlias(data[0], data[2])

    c_sheet.recompute()

    return c_sheet


# ANNOTATION SYSTEM

def createTD(d_doc, td_pnm):
    """Create a TechDraw page."""
    tmplSpec = 'Mod/TechDraw/Templates/A4_Landscape_blank.svg'
    tmpl_file = os.path.join(RES_DIR, tmplSpec)

    page = d_doc.addObject('TechDraw::DrawPage', td_pnm)
    template = d_doc.addObject('TechDraw::DrawSVGTemplate', f'{td_pnm}_tmpl')
    template.Template = tmpl_file
    page.Template = d_doc.getObject(template.Name)
    texts = page.Template.EditableTexts

    #for key, value in texts.items():
    #    print("{0} = {1}".format(key, value))
    page.recompute()
    d_doc.recompute()


def TD_addView(d_doc, view_nm, td_pnm, obj, vdir, dscale):
    """Add a TD View to a page."""
    view = d_doc.addObject('TechDraw::DrawViewPart', view_nm)
    page = d_doc.getObject(td_pnm)
    page.addView(view)
    view.Direction = vdir
    #view.XDirection = vdir
    view.ScaleType = u"Custom"
    view.Scale = dscale
    view.Source = obj

    view.recompute()
    page.recompute()
    d_doc.recompute()

    return view


def TDaddMlab(d_doc, page, mlab, mtyp, mref2d, mnote, mpos, show_dim=False):
    """Add a measure to a page."""
    dim = d_doc.addObject('TechDraw::DrawViewDimension')
    dim.Label = mlab
    dim.Type = mtyp
    dim.References2D = mref2d

    if show_dim is False:
        dim.FormatSpec = mnote
        dim.Arbitrary = True

    dim.X = mpos[0]
    dim.Y = mpos[1]
    dim.recompute()
    page.addView(dim)


# BUILDING METHODS


def main_body(d_doc, bd_nm, c_sheet, note=0):
    """Make main body."""
    # get some data from spreadsheet
    mdm_bl = c_sheet.get('mdm_blen').Value
    mdm_bw = c_sheet.get('mdm_bwid').Value
    md_bk_th = c_sheet.get('md_bk_thi').Value

    body = create_pd_body(d_doc, bd_nm)

    sk = body.newObject('Sketcher::SketchObject')
    sk.Label = f'{bd_nm}_base'
    sk.MapMode = 'FlatFace'
    sk.Support = (get_body_plane(body, 'XY_Plane'), [''])

    sk.addGeometry(Part.Circle(VEC0, DVZ, 10), False)
    rc_1 = sk.addConstraint(
        Sketcher.Constraint('Radius', 0, 10))
    sk.setExpression(f'Constraints[{rc_1}]', u'<<ModelData>>.mdm_r2')
    sk.addConstraint(Sketcher.Constraint('Coincident', 0, 3, -1, 1))

    sk.Visibility = False

    # First pad
    pa1 = body.newObject('PartDesign::Pad')
    pa1.Label = f'{bd_nm}_pad'
    pa1.Profile = sk
    pa1.Reversed = False
    pa1.Midplane = False
    pa1.Type = u"Length"
    pa1.setExpression('Length', u'<<ModelData>>.mdm_h1')
    pa1.UseCustomVector = False
    pa1.Direction = (0, 0, 1)
    pa1.ReferenceAxis = (sk, ['N_Axis'])
    pa1.AlongSketchNormal = 1
    pa1.UpToFace = None
    pa1.TaperAngle = 0.0
    pa1.Offset = 0.0

    pa1.recompute()

    sk.recompute()

    # Base block
    sk1 = body.newObject('Sketcher::SketchObject')
    sk1.Label = f'{bd_nm}_BB_sk1'
    sk1.MapMode = 'FlatFace'
    sk1.Support = (get_body_plane(body, 'XY_Plane'), [''])

    geoList, conList = sketch_polyline(
        [VEC0, V3d(mdm_bl, 0, 0),
         V3d(mdm_bl, mdm_bw, 0),
         V3d(0, mdm_bw, 0)])
    (line0, line1, line2, line3) = sk1.addGeometry(geoList, False)
    sk1.addConstraint(conList)
    sk1.addConstraint(Sketcher.Constraint('Symmetric', line0, 2, line1, 2, -1))

    # delete the automatically added vertical constraint
    sk1.delConstraint(5)

    bs_dx = sk1.addConstraint(Sketcher.Constraint('DistanceX', -1, 1, line0, 1, -2.0))
    sk1.setExpression(
        f'Constraints[{bs_dx}]',
        '<<ModelData>>.mdm_r2 * -1 + <<ModelData>>.mdm_bdpx')

    ywc = sk1.addConstraint(
        Sketcher.Constraint('DistanceX', line0, 1, line1, 2, mdm_bl))
    sk1.setExpression(f'Constraints[{ywc}]', '<<ModelData>>.mdm_blen')
    sk1.renameConstraint(ywc, u'ywid')

    xlc = sk1.addConstraint(
        Sketcher.Constraint('DistanceY', line3, 2, line3, 1, mdm_bw))
    sk1.setExpression(f'Constraints[{xlc}]', '<<ModelData>>.mdm_bwid')
    sk1.renameConstraint(xlc, u'xlen')

    sk1.setExpression('.AttachmentOffset.Base.z', f'{pa1.Name}.Length')

    sk1.recompute()
    sk1.Visibility = False

    # Base pad
    pa2 = body.newObject('PartDesign::Pad')
    pa2.Label = f'{bd_nm}_BB_pad'
    pa2.Profile = sk1
    pa2.Reversed = False
    pa2.Midplane = False
    pa2.Type = u"Length"
    pa2.setExpression('Length', u'<<ModelData>>.mdm_bthi')
    pa2.UseCustomVector = False
    pa2.Direction = (0, 0, 1)
    pa2.ReferenceAxis = (sk1, ['N_Axis'])
    pa2.AlongSketchNormal = True
    pa2.UpToFace = None
    pa2.TaperAngle = 0.0
    pa2.Offset = 0.0

    pa2.recompute()

    body.Tip = pa2

    # Bracket
    sk2 = body.newObject('Sketcher::SketchObject')
    sk2.Label = f'{bd_nm}_BKT_sk'
    sk2.MapMode = 'FlatFace'
    sk2.Support = (get_body_plane(body, 'XZ_Plane'), [''])

    sketch_wire(sk2, md_bck(c_sheet))
    bll = sk2.addConstraint(Sketcher.Constraint('DistanceX', 3, 1, 3, 2, 15.0))
    sk2.setExpression(f'Constraints[{bll}]', u'<<ModelData>>.md_bk_bw')
    sk2.renameConstraint(bll, 'bl_len')

    bkh = sk2.addConstraint(Sketcher.Constraint('DistanceY', 0, 1, 1, 3, 10))
    sk2.setExpression(f'Constraints[{bkh}]', u'<<ModelData>>.md_bk_hei')
    # Equal constraint two slanted lines
    sk2.addConstraint(Sketcher.Constraint('Equal', 2, 0))

    # middle point of line1
    mpbl = sk2.addGeometry(Part.Point(VEC0))
    sk2.addConstraint(Sketcher.Constraint('PointOnObject', mpbl, 1, 3))
    mpbl_c = sk2.addConstraint(Sketcher.Constraint('DistanceX', 3, 1, mpbl, 1, 10))
    sk2.setExpression(f'Constraints[{mpbl_c}]', '.Constraints.bl_len * 0.5')

    # pivot hole
    pv_ho = sk2.addGeometry(Part.Circle(VEC0, DVZ, 1.0))
    sk2.addConstraint(Sketcher.Constraint('Coincident', pv_ho, 3, 1, 3))
    phr = sk2.addConstraint(Sketcher.Constraint('Radius', pv_ho, 5.0))
    sk2.setExpression(f'Constraints[{phr}]', u'<<ModelData>>.gen_rad')

    # external reference to position sketch on top of base block
    sk2.addExternal(f'{sk1.Name}',"Edge3")  # id -3
    sk2.addConstraint(Sketcher.Constraint('Symmetric', -3, 1, -3, 2, mpbl, 1))

    sk2.Visibility = False
    sk2.recompute()

    ssb1 = body.newObject('PartDesign::SubShapeBinder')
    ssb1.Label = f'{bd_nm}_BKT_bind1'
    ssb1.Support = sk2
    ssb1.setExpression('.Placement.Base.z', f'{pa2.Name}.Length')
    ssb1.setExpression('.Placement.Base.y', f'{sk1.Name}.Constraints.xlen * -0.5')
    ssb1.Visibility = False
    ssb1.recompute()

    # Pad to proper thickness
    pa3 = body.newObject('PartDesign::Pad')
    pa3.Label = f'{bd_nm}_BKT_r'
    pa3.Profile = ssb1
    pa3.Reversed = False
    pa3.Midplane = False
    pa3.Type = u"Length"
    pa3.setExpression('Length', u'<<ModelData>>.md_bk_thi')
    pa3.UseCustomVector = False
    pa3.Direction = (0, 0, 1)
    pa3.ReferenceAxis = None
    pa3.AlongSketchNormal = True
    pa3.UpToFace = None
    pa3.TaperAngle = 0.0
    pa3.Offset = 0.0

    pa3.recompute()

    body.Tip = pa3

    ssb2 = body.newObject('PartDesign::SubShapeBinder')
    ssb2.Label = f'{bd_nm}_BKT_bind2'
    ssb2.Support = sk2
    ssb2.setExpression('.Placement.Base.z', f'{pa2.Name}.Length')
    ssb2.setExpression('.Placement.Base.y', f'{sk1.Name}.Constraints.xlen * 0.5')
    ssb2.Visibility = False
    ssb2.recompute()

    pa4 = body.newObject('PartDesign::Pad')
    pa4.Label = f'{bd_nm}_BKT_l'
    pa4.Profile = ssb2
    pa4.Reversed = True
    pa4.Midplane = False
    pa4.Type = u"Length"
    pa4.setExpression('Length', u'<<ModelData>>.md_bk_thi')
    pa4.UseCustomVector = False
    pa4.Direction = (0, 0, 1)
    pa4.ReferenceAxis = None
    pa4.AlongSketchNormal = True
    pa4.UpToFace = None
    pa4.TaperAngle = 0.0
    pa4.Offset = 0.0

    pa4.recompute()

    body.Tip = pa4

    body.recompute()


    # Create TechDraw View
    # Note: we could be bitten by TNP so reference have to be checked
    # if geometry change
    if note > 0:
        page_nm = f'{bd_nm}_Page'
        createTD(dest_doc, page_nm)
        page = d_doc.getObject(page_nm)
        view = TD_addView(d_doc, f'{bd_nm}_View', page_nm, body, V3d(0,1,0), 0.50)

        TDaddMlab(
            d_doc, page, "dim1", "DistanceX", [(view, 'Edge8')],
                  "Bracket WaB", (-14.0, +6.0))

        TDaddMlab(
            d_doc, page, "dim2", "DistanceX",
            [(view, 'Vertex6'), (view, 'Vertex8')],
            "Base width", (-13.0, -27.0))


        TDaddMlab(
            d_doc, page, "dim3", "DistanceY",
            [(view, 'Vertex1'), (view, 'Vertex4')],
            "Height 1", (+64.0, -26.0))

        TDaddMlab(
            d_doc, page, "dim4", "DistanceX",
            [(view, 'Vertex0'), (view, 'Vertex1')],
            "Radius 2", (0.0, -61.0))

        TDaddMlab(
            d_doc, page, "dim6", "Radius",
            [(view, 'Edge13')],
            "Hole Radius", (+19.0, +42.0))

        TDaddMlab(
            d_doc, page, "dim6", "Radius",
            [(view, 'Edge11')],
            "Bracket Top Radius", (-54.0, +61.0))

        edg = view.getEdgeBySelection("Edge13")
        edg_cen = edg.Curve.Center
        cvtx = view.makeCosmeticVertex(edg_cen)
        vtx_id = view.getCosmeticVertex(cvtx)

        TDaddMlab(
            d_doc, page, "dim5", "DistanceY",
            [(view, 'Vertex15'), (view, 'Edge8')],
            #[vtx_id, (view, 'Edge8')],
            "Bracket Height", (-57.0, 16.0))

        
        view.X = 84
        view.Y = 138
        view.recompute()

        page.recompute()

        d_doc.recompute()

    else:
        d_doc.recompute()

    return body


def md_bck(c_sheet):
    """Make a bracket."""
    md_bk_bw = c_sheet.get('md_bk_bw').Value
    md_bk_hei = c_sheet.get('md_bk_hei').Value
    md_bk_tpr = c_sheet.get('md_bk_tpr').Value

    l1 = md_bk_bw * 0.50
    c_cen = V3d(0.0, md_bk_hei, 0.0)

    p1 = V3d(l1, 0.0, 0.0)
    p2 = V3d(-l1, 0.0, 0.0)

    tp1, tp2 = circle_tangent(md_bk_tpr, c_cen, p1)

    d1 = p1.x - tp1.x
    d2 = p1.x - tp2.x

    if d1 < d2:
        pt1 = tp1
    else:
        pt1 = tp2

    l1 = Part.LineSegment(p1, pt1)

    tp3, tp4 = circle_tangent(md_bk_tpr, c_cen, p2)

    d3 = p2.x - tp3.x
    d4 = p2.x - tp4.x

    if d3 > d4:
        pt2 = tp3
    else:
        pt2 = tp4

    l2 = Part.LineSegment(p2, pt2)

    cir = Part.Circle(c_cen, DVZ, md_bk_tpr)
    par1 = cir.parameter(pt1)
    par2 = cir.parameter(pt2)

    arc = cir.toShape(par1, par2)

    l3 = Part.LineSegment(p2, p1).toShape()

    edges = [l1.toShape(), arc, l2.toShape(), l3]

    return edges


if __name__ == "__main__":
    # use 1 as suffix of ensure_document as 2 will crash the application.
    doc_rname = ensure_document(DOC_Name, 1)
    dest_doc = FreeCAD.activeDocument()

    dest_doc.recompute()

    sheet = init_doc_ss(dest_doc, True)

    # model building

    md_plat = main_body(dest_doc, "md_plat", sheet, 1)

    dest_doc.recompute()

    setview(doc_rname, 2)

Just in case it it too verbose the example above, here the file.
20230612_TechDraw_example.py
(24.37 KiB) Downloaded 28 times

Actual problem, I have developed an helper methods that pass the istance of view and "object identifier" like 'Vertex15' in the example case, to make MeasureLabels (in my use case they will serve to show the customer what exact measure I need to have.)

Code: Select all

        TDaddMlab(
            d_doc, page, "dim5", "DistanceY",
            [(view, 'Vertex15'), (view, 'Edge8')],
            #[vtx_id, (view, 'Edge8')],
            "Bracket Height", (-57.0, 16.0))

Now I can get using the GUI that "object identifier" in my case wil be the center of Edge13 as derived from:

Code: Select all

        edg = view.getEdgeBySelection("Edge13")
        edg_cen = edg.Curve.Center
        cvtx = view.makeCosmeticVertex(edg_cen)
        vtx_id = view.getCosmeticVertex(cvtx)
the question is, how to pass a proper (view, 'Vertex16') in other word how to pass from vtx_id or eventually cvtx to the string "Vertex16"?

or there is a way to pass directly vtx_id or cvtx as reference in my TDaddMlab (I have taylored them to be part of a list):

Code: Select all

def TDaddMlab(d_doc, page, mlab, mtyp, mref2d, mnote, mpos, show_dim=False):
    """Add a measure to a page."""
    dim = d_doc.addObject('TechDraw::DrawViewDimension')
    dim.Label = mlab
    dim.Type = mtyp
    dim.References2D = mref2d

    if show_dim is False:
        dim.FormatSpec = mnote
        dim.Arbitrary = True

    dim.X = mpos[0]
    dim.Y = mpos[1]
    dim.recompute()
    page.addView(dim)

TIA and Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
wandererfan
Veteran
Posts: 6268
Joined: Tue Nov 06, 2012 5:42 pm
Contact:

Re: Some scripting considerations

Post by wandererfan »

onekk wrote: Sun Jun 11, 2023 2:46 pm the question is, how to pass a proper (view, 'Vertex16') in other word how to pass from vtx_id or eventually cvtx to the string "Vertex16"?
A cosmetic vertex knows its index, but that attribute isn't exposed to Python. Easy to add.
or there is a way to pass directly vtx_id or cvtx as reference in my TDaddMlab (I have taylored them to be part of a list):
Dimension references don't know about cosmetic items or their unique tags. We could create something like dim.setRefererence2d(cvTag).
User avatar
onekk
Veteran
Posts: 6146
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: Some scripting considerations

Post by onekk »

wandererfan wrote: Mon Jun 12, 2023 1:24 pm
A cosmetic vertex knows its index, but that attribute isn't exposed to Python. Easy to add.
...

Dimension references don't know about cosmetic items or their unique tags. We could create something like dim.setRefererence2d(cvTag).

Thanks for answering.

Your proposition are good, when you have time, I will be pleased to help if you want on testing things, with scripting.


One more advancement, just an idea it will be to make mnote in code below:

Code: Select all

        dim.FormatSpec = mnote
        dim.Arbitrary = True
accept even something similar to:

Code: Select all

mnote = f'Description Label \n ·{meas_value}mm'
This will permit to taylor the label, assignin the real measure and a proper Label, with great flexibility as you could as example use a drawing in inches and expose dimensions in mm or the other way around.

Now the result is assigned to a predefined "bounding box" resulting in the text truncated on top and bottom.

I think that two lines will suffice, or maybe adding a proper "Label" where you could put "Description Label" as in the example above and maybe a formatting properties like "tc" (top center), "bc" (bottom center), or "ol" (one line) or similar predefined "label positions", that will permit to assign the label and set his relative position in respect to the "dimension"

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
Post Reply