FreeCAD python type definitions, code documentation

Need help, or want to share a macro? Post here!
variable
Posts: 2
Joined: Mon Sep 02, 2019 6:12 pm

FreeCAD python type definitions, code documentation

Postby variable » Mon Sep 02, 2019 6:21 pm

I've recently started writing my objects in Python in VSCode then importing as a macro into a running FreeCAD instance (which is the only method I've found so far).

I have imports and whatnot working, and everything _works_ but there are no handy type definitions or module, function, etc code suggestions. Nor inline documentation. Am I importing something wrong, or are type stubs present elsewhere besides /lib ? Just wanted to check first before I went to the effort of writing stub files.
Thanks
User avatar
Kunda1
Posts: 5937
Joined: Thu Jan 05, 2017 9:03 pm

Re: FreeCAD python type definitions, code documentation

Postby Kunda1 » Mon Sep 02, 2019 9:05 pm

What are stub files ?
Want to contribute back to FC? Checkout:
#lowhangingfruit | Use the Source, Luke. | How to Help FreeCAD | How to report FC bugs and features
User avatar
wandererfan
Posts: 3155
Joined: Tue Nov 06, 2012 5:42 pm

Re: FreeCAD python type definitions, code documentation

Postby wandererfan » Tue Sep 03, 2019 12:56 am

Kunda1 wrote:
Mon Sep 02, 2019 9:05 pm
What are stub files ?
https://en.wikipedia.org/wiki/Test_stub

Stubs for when you haven't written the lower level functions yet, drivers for when you haven't written the upper level functions yet.
vocx
Posts: 1898
Joined: Thu Oct 18, 2018 9:18 pm

Re: FreeCAD python type definitions, code documentation

Postby vocx » Thu Sep 05, 2019 3:03 pm

wandererfan wrote:
Tue Sep 03, 2019 12:56 am
...

Stubs for when you haven't written the lower level functions yet, drivers for when you haven't written the upper level functions yet.
Are you sure?

According to this PEP 484, https://www.python.org/dev/peps/pep-0484/#stub-files, stub files are files that provide type hinting information, which presumably can be displayed by IDEs like VSCode. I haven't seen any such file in FreeCAD's source tree. Personally, I'm also a bit against adding too much type information, Python being dynamically typed and all that. Better programming documentation is for sure needed, but adding fixed types to functions isn't a priority, particularly because FreeCAD's Python code is still subject to change.
User avatar
wandererfan
Posts: 3155
Joined: Tue Nov 06, 2012 5:42 pm

Re: FreeCAD python type definitions, code documentation

Postby wandererfan » Thu Sep 05, 2019 6:47 pm

vocx wrote:
Thu Sep 05, 2019 3:03 pm
Are you sure?
Well, in the broad context of software development, yes, I'm sure of what drivers and stubs are.

In the context of Python type hinting, I haven't a clue.
variable
Posts: 2
Joined: Mon Sep 02, 2019 6:12 pm

Re: FreeCAD python type definitions, code documentation

Postby variable » Sat Sep 07, 2019 11:01 pm

Stub files in the context of Python are type hints and code comments written outside of the source.

Example that I've been working on for the `Drawing` workbench:

Code: Select all

""" FreeCAD Draft Workbench API documentation

https://www.freecadweb.org/wiki/Draft_API

"""

import FreeCAD
from typing import Optional
from typing import NewType
from typing import overload

degree = NewType('degree', float)


def cut(obj1: FreeCAD.Object, obj2: FreeCAD.Object) -> FreeCAD.Object :
    """Returns a cut object made from the difference of the 2 given objects. The original objects get hidden.
    Eric
    Returns: The newly created object"""
    ...


def extrude(obj: FreeCAD.Object, vector: Vector) -> FreeCAD.Object :
    """Extrudes the given object in the direction given by the vector. The original object gets hidden.
    Returns: The newly created object"""


def formatObject(obj1: FreeCAD.Object, obj2: Optional[FreeCAD.Object]) -> none:
    """This function applies to the given target object the current properties set on the Draft toolbar (line color and line width), or copies the properties of a second object if provided. It also places the object in construction group if the construction button is pressed.
    Returns: Nothing"""


def fuse(obj: FreeCAD.Object, obj2: FreeCAD.Object) -> FreeCAD.Object :
    """Returns an object made from the union of the 2 given objects. If the objects are coplanar, a special Draft Wire is used, otherwise the final object is a standard Part fuse.
    Returns: The newly created object"""


def getDraftPath(submodule: Optional[str]) -> path:
    """Returns the user or system path where the Draft module is running from. If a subpath or a filename is supplied, the full path to the subpath inside the Draft module is returned.
    Returns: A file path"""


def getGroupContents(scan_list: List) -> List[FreeCAD.Object] :
    """Scans recursively the given list for groups. If groups are encountered, their contents are appended to the list.
    Returns: A list of FreeCAD Objects"""


def getRealName(object_name: str) -> str:
    """Strips the trailing numbers from an object name.
    Returns: The stripped object name"""


def getSelection( ) -> FreeCAD.Object :
    """Returns the current FreeCAD selection.
    Returns: The current FreeCAD selection."""


def makeCircle(radius: float, [placement], facemode: bool, startangle: degree, endangle: degree) -> FreeCAD.Object :
    """Creates a circle object with given radius. If a placement is given, it is used. If facemode is False, the circle is shown as a wireframe, otherwise as a face. If start angle AND end angle are given (in degrees), they are used and the object appears as an arc.
    Returns: The newly created object."""


@overload
def makeDimension(attach_obj: FreeCAD.Object, vertice1: int, vertice2: int, intersecting_vector: Optional[Vector]) -> FreeCAD.Object:
    ...
def makeDimension(vec1: Vector, vec2: Vector, intersecting_vector: Optional[Vector]) -> FreeCAD.Object :
    """Creates a Dimension object measuring distance between first and second vectors, with the dimension line passing through the third vector if provided. The current line width and color from the Draft toolbar will be used. Instead of 2 vectors, you can also pass a FreeCAD object, and two integers (and optionally a vector where the dimension line must pass). In that case, the dimension will be associated with the object, and measure two of its vertices, indicated by the two given indice numbers.
    Returns: The newly created object."""


def makeLine(vec1: Vector, vec2: Vector) -> FreeCAD.Object :
    """Creates a line between the two given vectors. The current line width and color from the Draft toolbar will be used.
    Returns: The newly created object."""


def makeRectangle(length: float, width: float, [placement], facemode: bool) -> FreeCAD.Object :
    """Creates a Rectangle object with length in X direction and height in Y direction. If a placement is given, it is used. If facemode is False, the rectangle is shown as a wireframe, otherwise as a face. The current line width and color from the Draft toolbar will be used.
    Returns: The newly created object."""


def makeText(string or list, [Vector], [screenmode]) -> FreeCAD.Object :
    """Creates a Text object, at the given point if a vector is provided, containing the string or the strings given in the list, one string by line. The current color from the Draft toolbar and the text height and font specified in preferences are used. If screenmode is True, the text always faces the view direction, otherwise it lies on the XY plane.
    Returns: The newly created object."""


def makeWire(source: Union[List[Vector],Part.Wire], closed: Optional[bool], [placement], facemode: Optional[bool]) -> Draft.DWire:
    """Creates a DWire object from the given list of vectors or from the given Wire. If closed is True or if first and last points are identical, the wire is closed. If facemode is True (and wire is closed), the wire will appear filled. The current line width and color from the Draft toolbar will be used.
    Returns: A new Draft DWire (not a Part Wire)."""


def move(obj: FreeCAD.Object or list, Vector, copymode: Optional[bool]) -> List[FreeCAD.Object] :
    """Moves the given object or the objects contained in the given list in the direction and distance indicated by the given vector. If copymode is True, the actual objects are not moved, but copies are created instead.
    Returns: The object(s) (or their copies if copymode was True)."""


def precision( ) -> int:
    """Returns the precision value from Draft user settings.
    Returns: An integer."""


def rotate(obj: FreeCAD.Object or list, angle, [center], [axis] ,[copymode]) -> List[FreeCAD.Object] :
    """Rotates the given object or the objects contained in the given list with the given angle around the given center if provided, using axis as a rotation axis. If axis is omitted, the rotation will be around the vertical Z axis. If copymode is True, the actual objects are not moved, but copies are created instead.
    Returns: The objects (or their copies)."""


def scale(obj: FreeCAD.Object or list, vector, [center], [copymode]) -> List[FreeCAD.Object]:
    """Scales the given object or the objects contained in the given list with a scale factors defined by the given vector (in X, Y and Z directions) around the given center if provided. If copymode is True, the actual objects are not moved, but copies are created instead.
    Returns: The objects (or their copies)."""


def select(obj: FreeCAD.Object) -> None:
    """Deselects everything and selects only the passed object
    Returns: Nothing."""


def shapify(obj: FreeCAD.Object) -> FreeCAD.Object :
    """Transforms a parametric shape object into non-parametric.
    Returns: The new object."""


def draftify(obj: Union[FreeCAD.Object, List[FreeCAD.Obkect]) -> None:
    """Turns the given object or each object of the given list into Draft parametric wires.
    Returns: Nothing."""


def getSVG(obj: FreeCAD.Object, [linemodifier], [textmodifier], [(u,v)])
    """Creates a SVG representation of the given object. The linemodifier attribute is a scale factor (in percents) for line width, and textmodifier for text size. You can also optionally provide a tuple of vectors to define a projection plane, otherwise the geometry will be projected on the XY plane.
    Returns: a string containing a SVG representation of the given object."""
It helps a ton to have when you are writing code.
vocx
Posts: 1898
Joined: Thu Oct 18, 2018 9:18 pm

Re: FreeCAD python type definitions, code documentation

Postby vocx » Sun Sep 08, 2019 4:18 am

variable wrote:
Sat Sep 07, 2019 11:01 pm
Stub files in the context of Python are type hints and code comments written outside of the source.

Example that I've been working on for the `Drawing` workbench:

It helps a ton to have when you are writing code.
The Drawing Workbench is obsolete. If you are going to contribute documentation, add such stub files to Part, Mesh, PartDesign, and similar workbenches developed in C++, or directly in the sources for Workbenches made in Python, like Draft, Arch, BIM, Render, etc.
Bayesian
Posts: 57
Joined: Thu Aug 08, 2019 1:49 pm

Re: FreeCAD python type definitions, code documentation

Postby Bayesian » Sun Sep 08, 2019 10:14 am

Even though I am only starting to use FreeCAD and its API, please permit me to opine that I think the Python API could use a bit of cleanup in general.

I find it difficult to locate Classes for instantiating or runtime type checks. Often such class names are Strings in terms of C++ namespaces. For example the App:Property* stuff could be enums like this:

Code: Select all

from enum import Enum
# app.py
class props(Enum):
	Length = "App::PropertyLength"
	Angle = "App::PropertyAngle"
	...
And then you can refer to those with props.Length and so on. I've begun to add stuff like this to my macros and notebooks.

This would allow checking for mistakes and enable code completion in Jupyter or other IDEs.

I know that there are property types outside the App namespace, but handling that would depend on how the Enum is supposed to be imported and used in the first place.

Importing of modules is also a mess, which mostly results from defining the "roots" of the Python extensions instead of through Python or Cython, linking in the C++ bindings and then presenting them in a cleaner way. For example it would be nice to have the standard workbenches under the FreeCAD module, like FreeCAD.Part etc.

The conda environment requires its root to be added to PYTHONPATH in order to import FreeCAD. The Appimage and PPA also have quite a big list of PYTHONPATH entries. The problem there is that files in each of those directories can override imports, leading to crashes or other incompatibilities.
looo
Posts: 2963
Joined: Mon Nov 11, 2013 5:29 pm

Re: FreeCAD python type definitions, code documentation

Postby looo » Sun Sep 08, 2019 11:01 am

Bayesian wrote:
Sun Sep 08, 2019 10:14 am
Importing of modules is also a mess, which mostly results from defining the "roots" of the Python extensions instead of through Python or Cython, linking in the C++ bindings and then presenting them in a cleaner way. For example it would be nice to have the standard workbenches under the FreeCAD module, like FreeCAD.Part etc.
At least this point should be solve with the possibility to use namespace-packages for freecad-modules. The basic idea are namespace packages which extend the freecad namespace. We discussed about moving internal modules to this new structure (theoretically it should be possible to do so) but it will change the imports and therefor a lot of existing macros have to be updated... So we are currently have two ways of python-modules initialization. For more information please have a look at some example workbenches which are installable with pip:
https://github.com/FreeCAD/Workbench-Starterkit
The conda environment requires its root to be added to PYTHONPATH in order to import FreeCAD.
I think it's not too difficult to solve this for conda-environments. But the real issue here is that people want to run different freecad versions in one environment. (At least this is possible on some distributions). This is something that is not possible in the python world (and it makes totally sense to not have multiple versions of the same package in one environment, but distros like ubuntu allow this. Python it self is a example for this).
The Appimage and PPA also have quite a big list of PYTHONPATH entries. The problem there is that files in each of those directories can override imports, leading to crashes or other incompatibilities.
I guess this is something that can be solved by modifying the python-path on startup. If this is an issue for you it's best to report it here:
https://github.com/FreeCAD/FreeCAD-AppImage

ps.: this was only an answer to the post above and might be a bit OT.
please help with my conda-packaging efforts: https://liberapay.com/looooo/
Bayesian
Posts: 57
Joined: Thu Aug 08, 2019 1:49 pm

Re: FreeCAD python type definitions, code documentation

Postby Bayesian » Mon Sep 09, 2019 5:20 am

@loo Thank you for your reply. I really don't want to offend anyone, FreeCAD is great software, I just want to share some of my experiences with getting to know it. I have been using Python for about 17 years, so you could say I've developed somewhat of a delicate palate when it comes to APIs and libraries :D

I also think the Property stuff in general could/should be more Pythonic or at least more consistent. I may create a post or github issue on that topic once my thoughts on that have stabilized somewhat.