pymadcad

Simple yet powerful CAD (Computer Aided Design) library, written with Python.

Installation

From PyPI

pip install pymadcad

Optionnal dependencies

There is some optionnal dependencies you can choose to install by yourself to enable some features of pymadcad.

From source

You will need an archive of the source directory <https://github.com/jimy-byerley/pymadcad>. Extract it somewhere then type the following commands in the extracted directory.

Make sure you installed the dependencies:

pip install moderngl pyglm cython pillow numpy scipy

You still need the PyQt5 library. As there is many possible sources, you have to install it manually so you can use the version/source you prefer. Choose one of the following:

  • Qt from PyPI

    pip install PyQt5
    
  • Qt from the linux repositories

    sudo apt install python3-pyqt5 python3-pyqt5.qtopengl
    

Compile the module:

python setup.py build_ext --inplace

The source directory can now be used as is.

For Debian/Ubuntu

Warning

There is no download page yet.

Download the .deb file, then

sudo dpkg -i python3-pymadcad.deb
sudo apt -f install

For Windows

the pip command comes with most of the python distributions, so refers to the PyPI section below.

Main concepts of madcad

Comparison with existing CAD softwares

Current CAD softwares, are based on a full GUI oriented approach, with parametric features bases on dynamic relationship between the scene objects. This can leads to many problems:

  • Objects relationships are never visible, thus tricky to understand and to manage. On most of these softwares it’s in fact impossible to manage it.

  • The software has to be very complex to handle all possible behaviors in parameters change in order to update the model.

  • The primitives is always limited to a small set fixed by design.

  • The GUI has to be very complex to allow the user to deal with every aspect of the objects (thus many tools, many object menus, sub-menus, context-dependent behaviors). The softwares are very hard to maintain, and often buggy

  • When a CAD file grow in complexity, we must avoid to modify it deeply or the entire build-tree fails and it’s no more possible to repair.

  • The build tree is rarely human-readable.

The current library differs in its approach by the following points:

  • MADCAD is a script based CAD

    all the functions of madcad are available in the current API. Nothing is hidden. The python script is the build tree, it is as readable as the user makes it. We strive to keep all the functions simple and intuitive to allow a good readability and a fast scripting.

  • MADCAD is not a parametric software

    a madcad script is just a regular Python script. What makes the madcad files parametric is the ability to change variables and reexecute the script.

    So madcad is generative, for implicit geometry definitions, the user has to use a solver before constructing the objects.

  • MADCAD is extensible

    As it’s a regular script, it’s possible to use from simple to complex programming concepts (if/else, loops, tables, functions, classes, module imports, generators, …). Giving it infinite more possibilities, and in an easier way than with other softwares.

Design and concepts

The last section describe the guidelines. Here is what these considerations brought us.

Scripting

The build tree is replaced by a script, thus a programming language was needed. For the best readability it had to have a very clean syntax. But for the highest flexibility it has to be very dynamic and powerful. Object orientation is necessary for transparent data manipulation, as well as dynamic typing. Functionnal programming and generators are welcome.

Choice is Python

CAD Data

The object oriented system helps to get each variable an identity depending on the true meaning of the contained data. We choose to get the classes the closest to the mathematical instrinsic meaning of the data.

Choice is to split types across their geometric nature:
vector

for both points and directions

axis

for direction associated with an origin, as well as for planes

matrix

for affine and linear transformations, rotations, bases

Mesh

only for surfaces

Web

only for lines

Solid

for objects inside the same movement

etc

Math library

For efficient and simple linear algebra operations (vectors, matrices), We needed a library. It has to get very short type names (as often repeated in scripts and library). Few but universal operations. Compiled runtime and memory.

It appeared that the GPU shader languages already fixed that for us, with a sort of universal API for 2-4 dimension types.

Choice is PyGLM

Mesh Data

It’s always a difficult way to stop a decision on the way to represent 3D objects. In most cases we represent it by its exterior surface, but the problem of the polygons remains: ngons, quads, triangles, bezier, … ? And the way to put it into memory also: simple unit elements, dynamically typed polygons, halfedges, quadedges, … Or even the way to access it: arrays, chained lists, octree, hashmaps.

Choice was not easy but is in favor of simplicity:
array storage

list for now

simple unit elements

vec3 for points and tuple for edges and trangles

surface is only made of triangles

(a,b,c)

This structure is very simple and common in the 3D computing, and it makes easy to write most of the operations. Therefore the storage is exposed to the user.

Display library

As 3D manipulation can be complex, it’s mandatory to visualize any kind of objects. A part of the library is dedicated to display the objects in an OpenGL scene. But to allow user interactions and allow to embed the scene widget, we had to focus on one only panelling library.

We prefered to bet on the most - advanced and feature-complete - free library despite its differences with the python environment.

Choice is Qt but with moderngl for the render pipeline

Note

Qt5 is not very pythonic (it’s designed for c++, no functional programming, huge inheritance instead of python protocols, string typing, enums, camelcase, …). But it may evolve in the future, according to the Qt6 guidelines

References

Python guide for newcomers

Quick introduction to the Python language for beginners, in particular case of madcad usage. Skip this if you already heard of Python or if you already used an object oriented language.

TODO

Guide

Here are the most used functions and classes of madcad.

Features

  • surface generation (3D sketch primitives, extrusion, revolution, …)

  • fast boolean operations

  • common mesh file format import/export

  • kinematic manipulation

  • indirect geometry definition through the constraint/solver system

  • blending and envelope completion functions

  • objects display with high-quality graphics

data types

The most common object types of MADCAD are the following:

math types:

vec3

a 3D vector (with fast operations)

mat3

linear transformation, used for rotations and scaling

mat4

affine transformation, used for poses, rotations, translations, scaling

quat

a quaternion, used for rotation (faster and more convenient than matrices for non-repeated operations)

details in module mathutils

mesh data:

Mesh

used to represent 3D surfaces.

Web

used to represent 3D lines.

Wire

used to represent only contiguous 3D lines.

details in module mesh

kinematic data:

Solid

each instance constitutes a kinematic undeformable solid

Kinematic

holds joints and solids for a kinematic structure, with some common mathematical operations

details in module kinematic

most of the remaining classes are definition elements for kinematics or meshes, see primitives , constraints , and joints modules.

Most common operations

Math operations

>>> a = vec3(1,2,3)
>>> O = vec3(0)
>>> X = vec3(1,0,0)
>>> normalize(a)
vec3( 0.267261. 0.534522. 0.801784 )
>>> 5*X
vec3(5,0,0)
>>> cross(a, X)         # cross product  a ^ X
vec3(0,3,2)
>>> dot(cross(a, X), X)         # X is orthogonal to its cross product with an other vector
0
>>> quat(vec3(0,0,2)) * X               # rotation of the X vector by 2 rad around the Z axis
vec3( -0.416147. 0.909297. 0 )

Geometry primitives

# define points
O = vec3(0)
A = vec3(2,0,0)
B = vec3(1,2,0)
C = vec3(0,2,0)
# create a list of primitives
line = [
        Segment(O, A),          # segment from 0 to A (the direction is important for the surface generation)
        ArcThrough(A, B, C), # arc from A to C, with waypoint B
        Segment(C,O),           # segment from C to O
        ]
>>> web(line)   # convert the list of primitives into a Web object, ready for extrusion and so on
Web( ... )
>>> show([line])
_images/primitives-unsolved.png

Solver

Suppose that you want to set the Arc tangent to the A and B segments, and fix its radius. It is not easy to guess the precise coordinates for A, B and C for this. You can then specify the constraints to the solver. He will fix that for you.

csts = [
        Tangent(line[0], line[1], A),   # segment and arc are tangent in A
        Tangent(line[1], line[2], C),   # arc and segment are tangent in C
        Radius(line[1], 1.5),           # radius of arc must be equal to 1.5
        ]
solve(csts, fixed=[0])          # solve the constraints, O is fixed and therefore will not move during the process

That’s it ! The primitive list can now be converted to Wire or Web with the good shape.

>>> A, B, C    # points have been modified inplace
(vec3(...), vec3(...), vec3(...))
_images/primitives-solved.png

Kinematic

Prior part design (or after for assembly), we may want to see how what we are making should behave. We use then a Kinematic, using the current engineering conventions. In the same spirit as for the primitives, the solvekin function solves the joints constraints.

# we define the solids, they intrinsically have nothing particular
base = Solid()
s1 = Solid()
s2 = Solid()
s3 = Solid()
s4 = Solid()
s5 = Solid()
wrist = Solid(name='wrist')     # give it a fancy name

# the joints defines the kinematic.
# this is a 6 DoF (degrees of freedom) robot arm
csts = [
        Pivot(base,s1, (O,Z)),                   # pivot using axis (O,Z) both in solid base and solid 1
        Pivot(s1,s2, (vec3(0,0,1), X), (O,X)),   # pivot using different axis coordinates in each solid
        Pivot(s2,s3, (vec3(0,0,2), X), (O,X)),
        Pivot(s3,s4, (vec3(0,0,1), Z), (vec3(0,0,-1), Z)),
        Pivot(s4,s5, (O,X)),
        Pivot(s5,wrist, (vec3(0,0,0.5), Z), (O,Z)),
        ]

# the kinematic is created with some fixed solids (they interact but they don't move)
kin = Kinematic(csts, fixed=[base])

# solve the current position (not necessary if just nned a display)
solvekin(csts)

show([kin])

Kinematics are displayable as interactive objects the user can move. Thay also are usefull to compute force repartitions during the movmeents or movement trajectories or kinematic cycles …

_images/kinematic-robot-arm.png

Generation

Most of the common surfaces are generated from an outline (closed is often not mendatory). An outline can be a Web or a Wire, depending on the algorithm behind. Those can be created by hand or obtained from primitives (see above).

Generaly speaking, generation functions are all functions that can produce a mesh from simple parameters by knowing by advance where each point will be.

Note

Most generation functions produce a surface. To represent a volume we use a closed surface so you have to pay attention to if your input outline is well closed too.

The most common functions are

  • extrusion

  • revolution

  • thicken

  • tube

  • saddle

  • flatsurface

Suppose we want a torus, let’s make a simple revolution around an axis, the extruded outline have not even to be in a plane:

revolution(
    radians(180),       # 180 degrees converted into radiaus
    (O,Z),              # revolution axis, origin=0, direction=Z
    web(Circle((A,Y), 0.5)),    # primitive converted into Web
    )
_images/revolution-circle.png

Join arbitrary outlines in nicely blended surfaces.

interfaces = [
        Circle((vec3(0,0,3),vec3(0,0,1)), 1),
        Circle((vec3(-1,-1,-1),normalize(vec3(-1,-1,-1))), 1),
        Circle((vec3(1,-1,-1),normalize(vec3(1,-1,-1))), 1),
        Circle((vec3(0,1,0),normalize(vec3(0,1,-1))), 1),
        ]

m = junction(
                interface[0],
                interface[1],
                interface[2],
                (interface[3], 'normal'),
                tangents='tangent',
                )
for c in interface:
        m += extrusion(c.axis[1]*3, web(c))
_images/junction-circles-post.png

details in module generation

Reworking

For some geometries it is much faster to rework the already generated mesh to add complex geometries. Putting a hole in a surface for instance. Thus you won’t need to generate all the intersection surfaces by hand.

# obtain two different shapes that has noting to to with each other
m1 = brick(width=vec3(2))
m2 = m1.transform(vec3(0.5, 0.3, 0.4)) .transform(quat(0.7*vec3(1,1,0)))

# remove the volue of the second to the first
difference(m1, m2)
_images/boolean-cube.png

An other usual rework operation is cut edges with chamfers or roundings. Because round is already a math function, we use the term bevel

# obtain a mesh
cube = brick(width=vec3(2))
# cut some edges
bevel(cube,
        [(0,1),(1,2),(2,3),(0,3),(1,5),(0,4)],          # edges to smooth
        ('width', 0.3),         # cutting description, known as 'cutter'
        )
_images/bevel-cube.png

Reference

Warning

The module is still in alpha version and the API may change a lot before release.

submodules

Tip

Most of the submodule functions and classes are present in the madcad root module, so unless you write a library you won’t need to import them explicitly.

mathutils - all the basic math types and functions

Most of the names here are coming from the glm module. But the goal is to harvest at one point all the basic math functions and objects that are used all around madcad.

Tip

All the present functions and types are present in the root madcad module.

Most common glm types

all implements the common operators + - * / <> ==

vec3

alias of glm.dvec3

mat3

alias of glm.dmat3x3

mat4

alias of glm.dmat4x4

quat

alias of glm.dquat

all glm types exists with several element types and in several precision:

prefix

precision

d

f64 aka double precisin floating point

f

f32 aka for simple precision floating point

i

i32 aka integer

i16

16 bits integer

i8

8 bits integer aka byte

u

unsigned integer (also declines in u16 and u32)

b

bit aka boolean

precision specification is put as prefix: dvec3, fvec3, imat4. Notation without prefix refers to the madcad implementation precision: float64 (prefix ‘d’).

In this documentation, when we refer to a ‘vector’ without explicit type, we obviously mean a vec3 aka. dvec3.

Note

The default glm precision type is float32 (prefix ‘f’). For convenience, these are overriden in madcad to use a better precision.

Common vector operations
dot(x: float, y: float)float

Returns the dot product of x and y, i.e., result = x * y. dot(x: vecN, y: vecN) -> float

Returns the dot product of x and y, i.e., result = x[0] * y[0] + x[1] * y[1] + ...

dot(x: quat, y: quat) -> float

Returns dot product of x and y, i.e., x[0] * y[0] + x[1] * y[1] + ...

cross(x: vec3, y: vec3)vec3

Returns the cross product of x and y. cross(x: quat, y: quat) -> quat

Compute a cross product.

mix(x: number, y: number, a: float)number
Returns x * (1.0 - a) + y * a, i.e., the linear blend of x and y using the floating-point

value a. The value for a is not restricted to the range [0, 1].

mix(x: number, y: number, a: bool) -> number

Returns y if a is True and x otherwise.

mix(x: vecN, y: vecN, a: fvecN) -> vecN

Returns x * (1.0 - a) + y * a, i.e., the linear blend of x and y using the floating-point value a. The value for a is not restricted to the range [0, 1].

mix(x: vecN, y: vecN, a: bvecN) -> vecN

For each component index i: Returns y[i] if a[i] is True and x[i] otherwise.

mix(x: matNxM, y: matNxM, a: fmatNxM) -> matNxM

Returns x * (1.0 - a) + y * a, i.e., the linear blend of x and y using the floating-point value a for each component. The value for a is not restricted to the range [0, 1].

mix(x: matNxM, y: matNxM, a: float) -> matNxM

Returns x * (1.0 - a) + y * a, i.e., the linear blend of x and y using the floating-point value a for each component. The value for a is not restricted to the range [0, 1].

mix(x: quat, y: quat, a: float) -> quat

Spherical linear interpolation of two quaternions. The interpolation is oriented and the rotation is performed at constant speed. For short path spherical linear interpolation, use the slerp function.

length(x: float)float

Returns the length of x, i.e., abs(x). length(x: vecN) -> float

Returns the length of x, i.e., sqrt(x * x).

length(x: quat) -> float

Returns the norm of a quaternion.

distance(p0: float, p1: float)float

Returns the distance between p0 and p1, i.e., length(p0 - p1). distance(p0: vecN, p1: vecN) -> float

Returns the distance between p0 and p1, i.e., length(p0 - p1).

normalize(x: vecN)vecN

Returns a vector in the same direction as x but with length of 1. normalize(x: quat) -> quat

Returns the normalized quaternion.

anglebt(x, y)float

angle between two vectors

the result is not sensitive to the lengths of x and y

arclength(p1, p2, n1, n2)

length of an arc between p1 and p2, normal to the given vectors in respective points

project(vec, dir)glm.dvec3

component of vec along dir, equivalent to dot(vec,dir) / dot(dir,dir) * dir

the result is not sensitive to the length of dir

noproject(vec, dir)glm.dvec3

components of vec not along dir, equivalent to vec - project(vec,dir)

the result is not sensitive to the length of dir

unproject(vec, dir)glm.dvec3

return the vector in the given direction as if vec was its projection on it, equivalent to dot(vec,vec) / dot(vec,dir) * dir

the result is not sensitive to the length of dir

perp(v: glm.dvec2)glm.dvec2

perpendicular vector to the given vector

norm1(x)float

norm L1 ie. abs(x) + abs(y) + abs(z)

Alias to glm.l1Norm

norm2(x)float

norm L2 ie. sqrt(x**2 + y**2 + z**2) the usual distance also known as manhattan distance

Alias to glm.l2Norm alias glm.length

norminf(x)float

norm L infinite ie. max(abs(x), abs(y), abs(z))

Alias to glm.lxNorm

See the glm complete reference

Transformations
transform(*args)glm.dmat4x4

create an affine transformation matrix.

supported inputs:
mat4

obviously returns it unmodified

float

scale using the given ratio

vec3

translation only

quat, mat3, mat4

rotation only

(vec3,vec3), (vec3,mat3), (vec3,quat)

(o,T) translation and rotation

(vec3,vec3,vec3)

(x,y,z) base of vectors for rotation

(vec3,vec3,vec3,vec3)

(o,x,y,z) translation and base of vectors for rotation

dirbase(dir, align=dvec3(1, 0, 0))

returns a base using the given direction as z axis (and the nearer vector to align as x)

scaledir(dir, factor=None)glm.dmat3x3

return a mat3 scaling in the given direction, with the given factor (1 means original scale) if factor is None, the length of dir is used, but it can leads to precision loss on direction when too small.

rotatearound(angle, *args)glm.dmat4x4

return a transformation matrix for a rotation around an axis

rotatearound(angle, axis) rotatearound(angle, origin, dir)

transpose(x: matNxM)matMxN

Returns the transposed matrix of x.

inverse(m: matSxS)matSxS

Return the inverse of a squared matrix. inverse(q: quat) -> quat

Return the inverse of a quaternion.

affineInverse(m: matSxS)matSxS

Fast matrix inverse for affine matrix.

angle(x: quat)float

Returns the quaternion rotation angle.

axis(x: quat)vec3

Returns the q rotation axis.

angleAxis(angle: float, axis: vec3)quat

Build a quaternion from an angle and a normalized axis.

lerp(x: quat, y: quat, a: float)quat

Linear interpolation of two quaternions. The interpolation is oriented.

Scalar functions
interpol1(a, b, x)

1st order polynomial interpolation

interpol2(a, b, x)

3rd order polynomial interpolation a and b are iterable of successive derivatives of a[0] and b[0]

intri_smooth(pts, ptangents, a, b)

cubic interpolation over a triangle, edges are guaranteed to fit an interpol2 curve using the edge tangents

Note

if the tangents lengths are set to the edge lenghts, that version gives a result that only blends between the curved edges, a less bulky result than intri_sphere

intri_sphere(pts, ptangents, a, b, etangents=None)

cubic interpolation over a triangle (2 dimension space), edges are guaranteed to fit an interpol2 curve using the edge tangents

Note

if the tangents lengths are set to the edge lenghts, that version gives a result close to a sphere surface

intri_parabolic(pts, ptangents, a, b, etangents=None)

quadratic interpolation over a triangle, edges are NOT fitting an interpol2 curve

Distances
distance_pa(pt, axis)

point - axis distance

distance_pe(pt, edge)

point - edge distance

distance_aa(a1, a2)

axis - axis distance

distance_ae(axis, edge)

axis - edge distance

Constants
NUMPREC

Numeric precision of a unit float (using the default precision)

COMPREC

unit complement of NUMPREC for convenience: 1 - NUMPREC

mesh - meshes and discretised objects

This module defines triangular meshes and edges webs.

containers

The classes defined here are ‘points containers’ it means that they are storing a list of points (as a member point), a list of groups (member group) and index points and groups for other purpose (faces and edges). All of these follow this line:

  • storages are using basic types, no object inter-referencing, making it easy to copy it

  • storages (points, groups, faces, edges, …) are used as shared ressources

    python objects are ref-counted, allowing multiple Mesh instance to have the same point buffer. The user is responsible to ensure that there will be non conflict.

    To avoid conflicts, operations that are changing each point (or the most of them) reallocate a new list. Then operations like mergeclose or stripgroups won’t affect other Meshes that shared the same buffers initially.

  • as build from shared ressources, these classes can be build from existing parts at a nearly zero cost (few verifications, no computation)

  • the user is allowed to hack into the internal data, ensure that the Mesh is still consistent after.

classes
class Mesh(points=None, faces=None, tracks=None, groups=None, options=None)

set of triangles, used to represent volumes or surfaces. As volumes are represented by their exterior surface, there is no difference between representation of volumes and faces, juste the way we interpret it.

points

list of vec3 for points

faces

list of triplets for faces, the triplet is (a,b,c) such that cross(b-a, c-a) is the normal oriented to the exterior.

tracks

integer giving the group each face belong to

groups

custom information for each group

options

custom informations for the entire mesh

Point container methods

Support for +, += Mesh

transform(trans)

apply the transform to the points of the mesh, returning the new transformed mesh

flip()

flip all faces, getting the normals opposite

splitgroups(edges=None)

split the mesh groups into connectivity separated groups. the points shared by multiple groups will be duplicated if edges is provided, only the given edges at group frontier will be splitted

return a list of tracks for points

mergepoints(merges)

merge points with the merge dictionnary {src index: dst index} merged points are not removed from the buffer.

mergeclose(limit=None, start=0)

merge points below the specified distance, or below the precision return a dictionnary of points remapping {src index: dst index}

finish()

finish and clean the mesh note that this operation can cost as much as other transformation operation job done

  • mergeclose

  • strippoints

  • stripgroups

strippoints(used=None)

remove points that are used by no faces, return the reindex list. if used is provided, these points will be removed without usage verification

return a table of the reindex made

stripgroups()

remove groups that are used by no faces, return the reindex list

check methods

check()

raise if the internal data is inconsistent

isvalid()

return true if the internal data is consistent (all indices referes to actual points and groups)

issurface()
isenvelope()

return true if the surfaces are a closed envelope

scan methods

pointat(point, neigh=1e-13)

return the index of the first point at the given location, or None

usepointat(point, neigh=1e-13)

Return the index of the first point in the mesh at the location. If none is found, insert it and return the index

pointnear(point)

return the nearest point the the given location

groupnear(point)

return the id of the group for the nearest surface to the given point

extraction methods

facenormal(f)

normal for a face

facenormals()

list normals for each face

edgenormals()

dict of normals for each UNORIENTED edge

vertexnormals()

list of normals for each point

edges()

set of UNORIENTED edges present in the mesh

edges_oriented()

iterator of ORIENTED edges, directly retreived of each face

group(groups)

return a new mesh linked with this one, containing only the faces belonging to the given groups

outlines_oriented()

return a set of the ORIENTED edges delimiting the surfaces of the mesh

outlines_unoriented()

return a set of the UNORIENTED edges delimiting the surfaces of the mesh this method is robust to face orientation aberations

outlines()

return a Web of ORIENTED edges

groupoutlines()

return a dict of ORIENTED edges indexing groups.

On a frontier between multiple groups, there is as many edges as groups, each associated to a group.

frontiers(*args)

return a Web of UNORIENTED edges that split the given groups appart.

if groups is None, then return the frontiers between any groups

tangents()

tangents to outline points

islands(conn=None)[Mesh]

return the unconnected parts of the mesh as several meshes

precision(propag=3)

numeric coordinate precision of operations on this mesh, allowed by the floating point precision

box()

return the extreme coordinates of the mesh (vec3, vec3)

surface()

total surface of triangles

barycenter()

surface barycenter of the mesh

class Web(points=None, edges=None, tracks=None, groups=None, options=None)

set of bipoint edges, used to represent wires this definition is very close to the definition of Mesh, but with edges instead of triangles

points

list of vec3 for points

edges

list of couples for edges, the couple is oriented (meanings of this depends on the usage)

tracks

integer giving the group each line belong to

groups

custom information for each group

options

custom informations for the entire web

Point container methods

Support for +, += Web

transform(trans)

apply the transform to the points of the mesh, returning the new transformed mesh

flip()

reverse direction of all edges

segmented(group=None)

return a copy of the mesh with a group each edge

if group is specified, it will be the new definition put in each groups

mergepoints(merges)

merge points with the merge dictionnary {src index: dst index} remaining points are not removed

mergeclose(limit=None, start=0)

merge points below the specified distance, or below the precision return a dictionnary of points remapping {src index: dst index}

finish()

finish and clean the mesh note that this operation can cost as much as other transformation operation job done

  • mergeclose

  • strippoints

  • stripgroups

strippoints(used=None)

remove points that are used by no edges, return the reindex list. if used is provided, these points will be removed without usage verification

return a table of the reindex made

stripgroups()

remove groups that are used by no faces, return the reindex list

check methods

check()

check that the internal data references are good (indices and list lengths)

isvalid()

return true if the internal data is consistent (all indices referes to actual points and groups)

isline()

true if each point is used at most 2 times by edges

isloop()

true if the wire form a loop

scan methods

pointat(point, neigh=1e-13)

return the index of the first point at the given location, or None

usepointat(point, neigh=1e-13)

Return the index of the first point in the mesh at the location. If none is found, insert it and return the index

pointnear(point)

return the nearest point the the given location

extraction methods

extremities()

return the points that are used once only (so at wire terminations) 1D equivalent of Mesh.outlines()

groupextremities()

return the extremities of each group. 1D equivalent of Mesh.groupoutlines()

On a frontier between multiple groups, there is as many points as groups, each associated to a group.

arcs()

return the contiguous portions of this web

box()

return the extreme coordinates of the mesh (vec3, vec3)

precision(propag=3)

numeric coordinate precision of operations on this mesh, allowed by the floating point precision

length()

total length of edges

barycenter()

curve barycenter of the mesh

class Wire(points=None, indices=None, tracks=None, groups=None, options=None)

Line as continuous suite of points Used to borrow reference of points from a mesh by keeping their original indices

points

points buffer

indices

indices of the line’s points in the buffer

tracks

group index for each point in indices it can be used to associate groups to points or to edges (if to edges, then take care to still have as many track as indices)

groups

data associated to each point (or edge)

Support for item access to underlying points:

__getitem__(i)

return the ith point of the wire, useful to use the wire in a same way as list of points

equivalent to self.points[self.indices[i]]

Point container methods

Support for +, += Wire

transform(trans)

apply the transform to the points of the mesh, returning the new transformed mesh

flip()
segmented(group=None)

return a copy of the mesh with a group each edge

if group is specified, it will be the new definition put in each groups

close()
mergeclose(limit=None)

merge close points ONLY WHEN they are already linked by an edge. the meaning of this method is different than Web.mergeclose()

strippoints()

remove points that are used by no edge, return the reindex list. if used is provided, these points will be removed without usage verification

return a table of the reindex made

check methods

check()

raise if the internal data are not consistent

isvalid()

return True if the internal data are consistent

extraction methods

edges()

list of successive edges of the wire

edge(i)

ith edge of the wire

vertexnormals(loop=False)

return the opposed direction to the curvature in each point this is called normal because it would be the normal to a surface whose section would be that wire

tangents(loop=False)

return approximated tangents to the curve as if it was a surface section. if this is not a loop the result is undefined.

normal()

return an approximated normal to the curve as if it was the outline of a flat surface. if this is not a loop the result is undefined.

length()

curviform length of the wire (sum of all edges length)

barycenter()

curve barycenter

conversion functions
web(*arg)

Build a web object from supported objects:

Web

return it with no copy

Wire

reference points and generate edge couples

Primitive

call its .mesh method and convert the result to web

Iterable

convert each element to web and join them

List of vec3

reference it and generate trivial indices

Iterable of vec3

get points and generate trivial indices

wire(*arg)

Build a Wire object from the other compatible types. Supported types are:

Wire

return it with no copy

Web

find the edges to joint, keep the same point buffer

Primitive

call its .mesh method and convert the result to wire

Iterable

convert each element to Wire and joint them

List of vec3

reference it and put trivial indices

Iterable of vec3

create internal point list from it, and put trivial indices

mesh internals manipulation
edgekey(a, b)

return a key for a non-directional edge

facekeyo(a, b, c)

return a key for an oriented face

connpp(ngons)

point to point connectivity input is a list of ngons (tuple of 2 to n indices)

connef(faces)

connectivity dictionnary, from oriented edge to face

lineedges(line, closed=False)

yield the successive couples in line

striplist(list, used)

remove all elements of list that match a False in used, return a reindexation list

line_simplification(web, prec=None)

return a dictionnary of merges to simplify edges when there is points aligned.

This function sort the points to remove on the height of the triangle with adjacent points. The returned dictionnary is guaranteed without cycles

suites(lines, oriented=True, cut=True, loop=False)

return a list of the suites that can be formed with lines. lines is an iterable of edges

Parameters
  • oriented – specifies that (a,b) and (c,b) will not be assembled

  • cut – cut suites when they are crossing each others

return a list of the sequences that can be formed

mesh_distance(m0, m1)

minimal distance between elements of meshes

The result is a tuple (distance, primitive from m0, primitive from m1). primitive can be:

int

index of the closest point

(int,int)

indices of the closest edge

(int,int,int)

indices of the closest triangle

Exceptions defined here
exception MeshError

kinematic - Kinematic solver/constraint system

This module defines the types and functions for kinematic manimulation and computation.

A Kinematic is a conceptual approach of mechanisms. It sort parts in several groups with the same movement (so in a solid, the solids are all bound together), and it links the defined solids by joints corresponding to the constraints each solid put to the other solids in the joint. That way no matter what are the parts, and what are their shape, even whan surfaces links the solids - the solid always have the same movements when there is the same joints between them.

So to analyse a mechanisme we look at its kinematic. And that can be done prior or after the part design as it is independant.

A kinematic in itself is a set of solids, observing movement relations. Those are modeled across the following classes: Solid and Kinematic.

Solids are considered to be undeformable, this allows the to use the Screw theory to represent the force and movement variables (see https://en.wikipedia.org/wiki/Screw_theory). In this module, screws are called Screw.

Tip

In case of undeformable solids, torsors makes possible to represent both the translative and rotative part of each movement aspect, independently from the point in the solid.

class Screw(resulting=None, momentum=None, position=None)
a 3D torsor aka Screw aka Wrench aka Twist - is a mathematical object defined as follow:
  • a resulting vector R

  • a momentum vector field M

the momentum is a function of space, satisfying the relationship:

M(A) = M(B) + cross(R, A-B)

therefore it is possible to represent a localized torsor such as:
  • R = resulting

  • M = momentum vector at position P

  • P = position at which M takes the current value

torsor are usefull for generalized solid mechanics to handle multiple variables of the same nature:
  • force torsor:

    Screw(force, torque, pos)

  • velocity (aka kinematic) torsor:

    Screw(rotation, velocity, pos)

  • kinetic (inertia) torsor:

    Screw(linear movement quantity, rotational movement quantity, pos)

all these torsors makes it possible to represent all these values independently from expression location

resulting
Type

vec3

momentum
Type

vec3

position
Type

vec3

Of course, as any vector variables, Screw implements + - with other Torsor, and * / with float

locate(pt)madcad.kinematic.Screw

gets the same torsor, but expressed for an other location

transform(mat)madcad.kinematic.Screw

changes the torsor from coordinate system

class Solid(pose=None, **objs)

Solid for kinematic definition, used as variable by the kinematic solver

orientation

rotation from local to world space

Type

quat

position

displacement from local to world

Type

vec3

visuals

of objects to display using the solid’s pose

Type

list

name

optional name to display on the scheme

Type

str

property pose: glm.dmat4x4

transformation from local to global space, therefore containing the translation and rotation from the global origin

transform(mat)

displace the solid by the transformation

class Kinematic(joints, fixed=(), solids=None)

Holds a kinematic definition, and methods to use it The solid objects used are considered as variables and are modified inplace by methods, and can be modified at any time by outer functions The joints are not modified in any case (and must not be modified while a Kinematic is using it)

joints

the joints constraints

solids

all the solids the joint applys on, and eventually more

fixed

the root solids that is considered to be fixed to the ground

solvekin(joints, fixed=(), precision=0.0001, maxiter=None, damping=1)

solver for kinematic joint constraints.

Unlike solve, the present solver is dedicated to kinematic usage (and far more efficient and precise). It doesn’t rely on variables as defined by solve, but instead use Solids as constraints.

See the joints module for joints definitions.

makescheme(joints, color=None)

create kinematic schemes and add them as visual elements to the solids the joints applies on

joints - Kinematic Joints definition

Simple joints
class Pivot(s1, s2, a1, a2=None, position=None)

Junction for rotation only around an axis

classical definition: Pivot (axis) no additional information is required by the initial state this class holds an axis for each side

class Gliding(s1, s2, a1, a2=None, position=None)

Joint for rotation and translation around an axis

classical definition: Gliding pivot (axis) the initial state doesn’t require more data this class holds an axis for each side

class Track(s1, s2, b1, b2=None, position=None)

Joint for translation only in a direction

classical definition: Track (direction vector) the initial state requires more parameters: the relative placements of the solids this class holds a base for each of the solids, bases are (0,X,Y) where X,Y are constrained to keep the same direction across bases, and the bases origins lays on their common Z axis

class Punctiform(s1, s2, a1, p2=None)

Joint for rotation all around a point belonging to a plane.

classical definition: Punctiform/Sphere-Plane (axis) the initial state does’t require more data the class holds a normal axis for the plane side, and a point for the sphere side (that defaults to the axis’origin)

class Planar(s1, s2, a1, a2=None, position=None)

Joint for translation in 2 directions and rotation around the third direction

classical definition: Planar (direction vector) the initial state requires an additional distance between the solids this class holds an axis for each side, the axis origins are constrained to share the same projections on the normal

Complex joints
class Gear(s1, s2, ratio, a1, a2=None, position=None)

Gear interaction between two solids.

ratio is the factor from the rotation of s1 to the rotation of s2 The two pinions are considered circular, but no assumption is made on their radius or relative inclination. The interaction is symetric.

Note

As solids don’t hold more data than only their pose (position and orientation), there is no way to distinguish the gear in a pose, and in the same pose rotated by \(2 \pi\).

Therefore rotation jumps can occurs when several gears are tied.

class Helicoid(s0, s1, step, b0, b1=None, position=None)

Screw a solid into an other.

step is the translation distance needed for that one solid turn by 2*pi around the other The interaction is symetric.

Note

As solids orientation is in \([0;~ 2 \pi]\), when the gap in the helicoid is bigger than step then the joints solids only rotates by gap/step * 2*pi % 2*pi

If the helicoid is involved in any gearings or reductors, the result might be not what was expected.

primitives - 3D Primitives for Wire generation

Primitives are parametrized objects, that can be baked into a mesh/web/wire object. A primitive object must have the following signature:

class SomePrimitive:
        # method baking the primitive in some general-purpose 3D object
        # resolution is optional and defaults to the primitive settings that defaults to the current settings at call time
        def mesh(self, resolution=None) -> Mesh/Web/Wire:
                ...

        # for the solver
        # primitive attributes the solver has to consider as variables or variable container
        slvvars = 'fields', 'for', 'solver', 'variables'
        # otpional method constraining the primitive parameters (to keep points on a circle for instance)
        def fit(self) -> err**2 as float:
                ...
isprimitive(obj)

return True if obj match the signature for primitives

Curve resolution

Some primitive types are curves, the discretisation is important for visual as well as for result quality (remember that even if something looks like a perfect curves, it’s still polygons). The resolution (subdivision) of curve is done following the following cirterions present in the settings module

specification priority order:

  1. optional argument resolution passed to primitive.mesh() or to web() or wire()

  2. optional attribute resolution of the primitive object

  3. value of settings.primitives['curve_resolution'] at bake time.

specification format:

('fixed', 16)   # fixed amount of 16 subdivisions
('rad', 0.6)    # max polygon angle is 0.6 rad
('radm', 0.6)
('radm2', 0.6)
primitives types
class Vector

Alias to vec3

class Point

Alias to vec3

class Axis(origin, direction, interval=None)

Mimic the behavior of a tuple, but with the primitive signature.

__getitem__(i)
class Segment(a, b)

segment from a to b

property direction
class ArcCentered(axis, a, b, resolution=None)

arc from a to b, centered around the origin of the axis.

An axis is requested instead of a point (that would be more intuitive), to solve the problem when a,b, center are aligned

property center
property radius
property axis
tangent(pt)

tangent to the closest point of the curve to pt

class ArcThrough(a, b, c, resolution=None)

arc from a to c, passing through b

property center
property radius
property axis
tangent(pt)

tangent to the closest point of the curve to pt

class Circle(axis, radius, alignment=dvec3(1, 0, 0), resolution=None)

circle centered around the axis origin, with the given radius, in an orthogonal plane to the axis direction

property center
property radius
property axis
tangent(pt)

tangent to the closest point of the curve to pt

class ArcTangent(a, b, c, resolution=None)

An arc always tangent to Segment(a,b) and Segment(c,b). The solution is unique.

property center
property radius
property axis
tangent(pt)

tangent to the closest point of the curve to pt

class TangentEllipsis(a, b, c, resolution=None)

An quater of ellipsis always tangent to Segment(a,b) and Segment(c,b). The solution is unique.

property center
property axis
tangent(pt)

tangent to the closest point of the curve to pt

class Interpolated(points, weights=None, resolution=None)

interpolated curve passing through the given points (3rd degree bezier spline)

the tangent in each point is determined by the direction between adjacent points the point weights is how flattened is the curve close to the point tangents

_images/primitives-spline-interpolated.png
class Softened(points, weights=None, resolution=None)

interpolated curve tangent to each segment midpoint (3rd degree bezier curve)

the points weights is the weight in the determination of each midpoint

_images/primitives-spline-softened.png

constraints - geometric constraints on primitive

This modules defines the constraints definitions and the solver tools

Constraints can be any object referencing variables and implementing the following signature to guide the solver in the resolution:

class SomeConstraint:

        # name the attributes referencing the solver variables to change
        slvvars = 'some_primitive', 'some_point'
        # function returning the squared error in the constraint

        #  for a coincident constraint for instance it's the squared distance
        #  the error must be a contiguous function of the parameters, and it's squared for numeric stability reasons.
        #  the solver internally use an iterative approach to optimize the sum of all fit functions.
        def fit((self):
                ...
isconstraint(obj)

return True if obj match the constraint signature

solver
solve(constraints, fixed=(), *args, **kwargs)

short hand to use the class Problem

class Problem(constraints, fixed=())

class to holds data for a problem solving process. it is intended to be instantiated for each different probleme solving, an instance is used multiple times only when we want to solve on top of the previous results, using exactly the same probleme definition (constraints and variables)

therefore the solver protocol is the follownig:
  • constraints define the probleme

  • each constraint refers to variables it applies on

    constraints have the method fit() and a member ‘slvvars’ that can be

    1. an iterable of names of variable members in the constraint object

    2. a function returning an iterable of the actual variables objects (that therefore must be referenced refs and not primitive types)

  • each variable object can redirect to other variable objects if they implements such a member ‘slvvars’

  • primitives can also be constraints on their variables, thus they must have a method fit() (but no member ‘primitives’ here)

  • primitives can implement the optional solver methods for some constraints, such as ‘slv_tangent’

solving method

Internally, this class uses scipy.optimize.minimize. Therefore the scipy minimization methods using only the gradient are all available. The mose usefull may be:

BFGS fast even for complex problems (few variables)

https://en.wikipedia.org/wiki/Broyden%E2%80%93Fletcher%E2%80%93Goldfarb%E2%80%93Shanno_algorithm

CG for problems with tons of variables or for nearby solutions

https://en.wikipedia.org/wiki/Conjugate_gradient_method

Powell for simple non-continuous problems with tons of variables

https://en.wikipedia.org/wiki/Powell%27s_method

default is method='BFGS'

register(obj)

register a constraint or a variable object

unregister(obj)

unregister all variables from a constraint or a variable object

constraints definitions
class Distance(p1, p2)

Makes two points distant of the fixed given distance

class Radius(arc, radius)

Gets the given Arc with the given fixed radius

Note: Only ArcCentered are supported yet.

class Angle(s1, s2)

Gets two segments with the given fixed angle between them

class Tangent(c1, c2, p)

Makes to curves tangent in the given point The point moves as well as curves.

The curves primitives must have a member slv_tangent(p) returning the tangent vector at the nearest position to p

class OnPlane(axis, pts: list)

Puts the given points on the fixed plane given by its normal axis

generation - functions to generate mesh surfaces from lines or meshes

based on extrusion/transformation of a Web
extrans(section, transformations, links)madcad.mesh.Mesh

create a surface by extruding and transforming the given outline.

Transformations

iterable of mat4, one each section

Link

iterable of tuples (a,b,t) with: (a,b) the sections to link (indices of values returned by transformation). t the group number of that link, to combine with the section groups

extrusion(trans, line, alignment=0)

create a surface by extruding the given outline by a transformation

:param : line: a line (Web or Wire) or a surface (Mesh) to extrude :param : trans: any transformation object accepted by mathutils.transform :param : alignment: when > 0 the line is extruded both sides (the transformation is linearly interpoled)

revolution(angle, axis, profile, resolution=None)

create a revolution surface by extruding the given outline steps is the number of steps between the start and the end of the extrusion

saddle(web1, web2)

create a surface by extruding outine1 translating each instance to the next point of outline2

tube(outline, path, end=True, section=True)

create a tube surface by extrusing the outline along the path if section is True, there is a correction of the segments to keep the section undeformed by the curve

generation of common meshes
square(axis, width: float)madcad.mesh.Mesh

return a simple square with the given normal axis and square width. Useful to quickly create a cutplane

brick(*args, **kwargs)madcad.mesh.Mesh

a simple brick with rectangular sides

constructors

  • brick(Box)

  • brick(min, max)

  • brick(center=vec3(0), width=vec3(-inf))

icosahedron(center, radius)madcad.mesh.Mesh

a simple icosahedron (see https://en.wikipedia.org/wiki/Icosahedron)

icosphere(center, radius, resolution=None)madcad.mesh.Mesh

a simple icosphere with an arbitrary resolution (see https://en.wikipedia.org/wiki/Geodesic_polyhedron).

Points are obtained from a subdivided icosahedron and reprojected on the desired radius.

uvsphere(center, radius, alignment=dvec3(0, 0, 1), resolution=None)madcad.mesh.Mesh

a simple uvsphere (simple sphere obtained with a revolution of an arc)

regon(axis, radius, n, alignment=None)madcad.mesh.Wire

create a regular n-gon Wire, the same way we create a Circle

offseting
inflateoffsets(surf, distance, method='face')[vec3]

displacements vectors for points of a surface we want to inflate.

Method

determines if the distance is from the old to the new faces, edges or points possible values: 'face', 'edge', 'point'

inflate(surf, distance, method='face')madcad.mesh.Mesh

move all points of the surface to make a new one at a certain distance of the last one

Method

determines if the distance is from the old to the new faces, edges or points

thicken(surf, thickness, alignment=0, method='face')madcad.mesh.Mesh

thicken a surface by extruding it, points displacements are made along normal.

Thickness

determines the distance between the two surfaces (can be negative to go the opposite direction to the normal).

Alignment

specifies which side is the given surface: 0 is for the first, 1 for the second side, 0.5 thicken all apart the given surface.

Method

determines if the thickness is from the old to the new faces, edges or points

others
flatsurface(outline, normal=None)madcad.mesh.Mesh

generates a surface for a flat outline using the prefered triangulation method .

if normal is specified, it must be the normal vector to the plane, and will be used to orient the face.

icosurface(pts, ptangents, resolution=None)madcad.mesh.Mesh

generate a surface ICO (a subdivided triangle) with its points interpolated using interpol2tri.

  • If normals are given instead of point tangents (for ptangents), the surface will fit a sphere.

  • Else ptangents must be a list of couples (2 edge tangents each point).

subdivide(mesh, div=1)madcad.mesh.Mesh

subdivide all faces by the number of cuts

repeat(pattern, n, trans)

create a mesh duplicating n times the given pattern, each time applying the given transform.

Pattern

can either be a Mesh, Web or Wire the return type will depend on the input type

N

the number of repetitions

Trans

is the transformation between each duplicate

blending - creation of surface between outlines

This module focuses on automated envelope generations, based on interface outlines.

the user has only to define the interface outlines or surfaces to join, and the algorithm makes the surface. No more pain to imagine some fancy geometries.

formal definitions
interface

a surface or an outline (a loop) with associated exterior normals.

node

a group of interfaces meant to be attached together by a blended surface.

In order to generate envelopes, this module asks for cutting all the surfaces to join into ‘nodes’. The algorithm decides how to join shortly all the outlines in a node. Once splited in nodes, you only need to generate node junctions for each, and concatenate the resulting meshes.

details

The blended surfaces are created between interfaces, linked as the points of a convex polyedron of the interfaces directions to the node center.

Example

>>> x,y,z = vec3(1,0,0), vec3(0,1,0), vec3(0,0,1)
>>> m = junction(
                # can pass a surface: the surface outlines and normals will be used as the generated surface tangents
                extrusion(2*z, web(Circle((vec3(0),z), 1))),
                # can pass wire or primitives: the wire loops are used and the approximate normal to the  wire plane
                Circle((2*x,x), 0.5),
                # more parameters when building directly the interfqce
                (Circle((2*y,y), 0.5), 'tangent', 2.),

                generate='normal',
                )

to come in a next version

# create junction for each iterable of interface, if some are not interfaces, they are used as placeholder objects for auto-determined interfaces
>> multijunction(
                (surf1, surf2, 42, surf5),
                (42, surf3, surf4),
                generate='straight',
                )
general mesh generation
junction(*args, center=None, tangents='normal', weight=1.0, match='length', resolution=None)

join several outlines with a blended surface

tangents:

‘straight’ no interpolation, straight lines ‘normal’ interpolated surface starts normal to the interfaces ‘tangent’ interpolated surface starts tangent to the interfaces

weight:

factor applied on the tangents before passing to interpol2 or intri_smooth the resulting tangent is computed in point a as weight * distance(a,b) * normalize(tangent[a])

match:

‘length’ share the outline between 2 matched points to assign the same length to both sides ‘corner’ split the curve to share around a corner

center:

position of the center of the junction node used to determine connexion between interfaces can be usefull for particularly weird and ambiguous interfaces

Note

match method ‘corner’ is not yet implemented

_images/junction-circles-prep.png _images/junction-circles.png
blendloop(interface, center=None, tangents='tangent', weight=1.0, resolution=None)madcad.mesh.Mesh

blend inside a loop interface

see junction for the parameters.

_images/blendloop.png
blendpair(*interfaces, match='length', tangents='tangent', weight=1.0, resolution=None)madcad.mesh.Mesh

blend between a pair of interfaces

match:

‘length’, ‘closest’ refer to match_* in this module

see junction for the other parameters.

_images/blendpair.png
blenditer(parameters, div, interpol)madcad.mesh.Mesh

create a blended surface using the matching parameters and the given interpolation parameters is an iterable of tuples of arguments for the interpolation function interpol receive the elements iterated and the interpolation position at the end

misc tools
match_length(line1, line2)[int, int]

yield couples of point indices where the curved absciss are the closest

match_closest(line1, line2)[int, int]

yield couples of points by cutting each line at the curvilign absciss of the points of the other

small utilities
join(mesh, line1, line2)

simple straight surface created from matching couples of line1 and line2 using mesh indices for lines

trijoin(pts, ptgts, div)

simple straight surface created between 3 points, interpolation is only on the sides

cut - functions for cutting meshes at edges or points, like chamfer

This module provides the chamfer and bevel functions, and the associated tools.

multicut, chamfer, and bevel are built in the same cutting algorithm. It cuts the mesh faces by propagation from the given edges. The cutting planes are determined by an offset vector from the original primitive (point or edge).

Most of the time you don’t need to set the offset yourself. It can be automatically calculated by several methods, depending on the shape you want to get. Those methods are called cutters and are executed using planeoffsets.

end-user functions
chamfer(mesh, indices, cutter)
chamfer(mesh: madcad.mesh.Mesh, edges, cutter)
chamfer(web: madcad.mesh.Web, points, cutter)
chamfer(wire: madcad.mesh.Wire, points, cutter)

chamfer a Mesh/Web/Wire around the given edges/points, using the given cutter

This is a chamfer on edges around a cube corner

_images/chamfer.png
bevel(mesh, indices, cutter)
bevel(mesh: madcad.mesh.Mesh, edges, cutter, resolution=None)
bevel(obj: madcad.mesh.Web, points, cutter, resolution=None)
bevel(wire: madcad.mesh.Wire, points, cutter, resolution=None)

bevel a Mesh/Web/Wire around the given edges/points, using the given cutter

This is a bevel on edges around a cube corner

_images/bevel.png
multicut(mesh, indices, cutter)
multicut(mesh: madcad.mesh.Mesh, edges, cutter, conn=None, prec=None, removal=None)
multicut(web: madcad.mesh.Web, points, cutter, conn=None, prec=None, removal=None)
multicut(wire: madcad.mesh.Wire, points, cutter)

cut a Mesh/Web/Wire around the given edges/points, using the given cutter

This is the result on edges around a cube corner

_images/multicut.png
cutters (cut methods)
_images/cutter.svg
cutter_width(width, fn1, fn2)

plane offset for a cut based on the width of the bevel

cutter_distance(depth, fn1, fn2)

plane offset for a cut based on the distance along the side faces

Warning

this cut method can be unstable at the moment

cutter_depth(dist, fn1, fn2)

plane offset for a cut based on the distance to the cutted edge

cutter_radius(depth, fn1, fn2)

plane offset for a cut based on the angle between faces

helpers
mesh_cut(mesh, start, cutplane, stops, conn, prec, removal, cutghost=True)

propagation cut for an edge

Start

the edge or point to start propagation from

Cutplane

the plane cutting the faces. Its normal must be oriented toward the propagation area.

Stops

the planes stopping the propagation. Their normal must be oriented toward the propagation area.

Removal

the set in wich the function will put the indices of faces inside

Cutghost

whether the function should propagate on faces already marked for removal (previously or during the propagation)

web_cut(web, start, cutplane, conn, prec, removal)

propagation cut for a point

Start

the edge or point to start propagation from

Cutplane

the plane cutting the faces. Its normal must be oriented toward the propagation area.

planeoffsets(mesh, edges, cutter)

compute the offsets for cutting planes using the given method cutter is a tuple or a function

  • function(fn1,fn2) -> offset

    fn1, fn2 are the adjacents face normals offset is the distance from segment to plane times the normal to the plane

  • (‘method’, distance)

    the method is the string name of the method (a function named ‘cutter’+method existing in this module) distance depends on the method and is the numeric parameter of the method

tangentjunction(points, match, normals, div)

create a surface joining the given couples of points, tangent to the two sides normals is a dict {point: normal}

tangentcorner(pts, lp, normals, div)

create a rounded surface tangent to the loop given normals is a dict {point: normal}

tangentend(points, edge, normals, div)

join a tangent surface resulting of tangentcorner or tangentjunction to a straight edge e normals is the same dict as for tangentcorner and tangentjunction

boolean - boolean cut/intersect/stitch meshes

Defines boolean operations for triangular meshes. Strictly speaking, boolean operations applies on sets, but considering the volumes delimited by their mesh envelopes, we can perform boolean operations on those volumes by manipulating only surface meshes.

This relies on a new intersection algorithm named syandana. It finds candidates for intersections using a spacial hashing of triangles over a voxel (see madcad.hashing). This is solving the problem of putting triangles in an octree. Also to avoid the increasing complexity of the operation with flat planes divided in multiple parallel triangles, the algorithm is implemented with a detection of ngons.

The syandana algorithm achieves intersections of meshes in nearly O(n) where usual methods are O(n**2)

After intersection, the selection of surface sides to keep or not is done through a propagation.

most common
pierce(m1, m2, selector=False)madcad.mesh.Mesh

cut the faces of m1 at their intersection with faces of m2, and remove the faces inside

selector decides which part of each mesh to keep

  • False keep the exterior part (part exclusive to the other mesh)

  • True keep the common part

boolean(m1, m2, selector=(False, True), prec=None)madcad.mesh.Mesh

execute boolean operation on volumes

selector decides which part of each mesh to keep

  • False keep the exterior part (part exclusive to the other mesh)

  • True keep the common part

union(a, b)madcad.mesh.Mesh

return a mesh for the union of the volumes. It is a boolean with selector (False,False)

_images/boolean-union.png
difference(a, b)madcad.mesh.Mesh

return a mesh for the volume of a less the common volume with b It is a boolean with selector (False, True)

_images/boolean-difference.png
intersection(a, b)madcad.mesh.Mesh

return a mesh for the common volume. It is a boolean with selector (True, True)

_images/boolean-intersection.png
more advanced
intersectwith(m1, m2, prec=None)madcad.mesh.Web

Cut m1 faces at their intersections with m2. Returning the intersection edges in m1 and associated m2 faces.

m1 faces and tracks are replaced thus the underlying buffers stays untouched.

The algorithm is using ngon intersections and retriangulation, in order to avoid infinite loops and intermediate triangles.

booleanwith(m1, m2, side, prec=None)set

execute the boolean operation inplace and only on m1

io - read/write mesh or data files

read(name: str, type=None, **opts)madcad.mesh.Mesh

load a mesh from a file, guessing its file type

write(mesh: madcad.mesh.Mesh, name: str, type=None, **opts)

write a mesh to a file, guessing its file type

cache(filename: str, create: Optional[callable] = None, name=None, storage=None, **opts)madcad.mesh.Mesh

Small cachefile system, it allows to dump objects to files and to get them when needed.

It’s particularly usefull when working with other processes. The cached files are reloaded only when the cache files are newer than the memory cache data

If specified, create() is called to provide the data, in case it doesn’t exist in memory neighter as file if specified, name is the cache name used to index the file it defaults to the filename if specified, storage is the dictionnary used to storage cache data, defaults to io.caches

caches

dict containing the data objects, associated to their filename.

{'filename': (read_time, data_loaded)}
class FileFormatError

settings - global and default settings

The settings module holds dictionnaries each aspect of the madcad library.

dictionnaries:
primitives

default settings for mesh and primitive operations

display

visual settings to display the 3D objects

scene

for what and how to display in the 3D scene

controls

preferences for the controls of the Scene widget

All the settings presents here are loaded at start or on demand from ~/.config/madcad/pymadcad.json

load(file=None)

load the settings directly in this module, from the specified file or the default one

dump(file=None)

load the current settings into the specified file or to the default one

install()

create and fill the config directory if not already existing

clean()

delete the default configuration file

use_qt_colors()

set the color settings to fit the current system colors

curve_resolution(length, angle, param=None)

return the subdivision number for a curve, using the given or setting specification

Length

is the curvilign length of the curve

Angle

is the integral of the absolute curvature (total rotation angle)

hashing - fast access to space associated data

This modules provide tools for accessing data using its space location (not for final user).

The complexity and therefore the cost of those operations are most of the time close to the hashmap complexity O(1). It means data is found in time independently of the actual size of the mesh or whatever storage it is.

class PositionMap(cellsize, iterable=None)

Holds objects assoiated with their location every object can be bound to multiple locations, and each location can hold multiple objects cellsize defines the box size for location hashing (the smaller it is, the bigger the memory footprint will be for non-point primitives)

Attributes defined here:
cellsize

the boxing parameter (DON’T CHANGE IT IF NON-EMPTY)

dict

the hashmap from box to objects lists

add(space, obj)

add an object associated with a primitive

display(scene)
get(space)

get the objects associated with the given primitive

keysfor(space)

rasterize the primitive, yielding the successive position keys currently allowed primitives are

points

vec3

segments

(vec3,vec3)

triangles

(vec3,vec3,vec3)

update(other)

add the elements from an other PositionMap or from an iteravble

meshcellsize(mesh)

returns a good cell size to index primitives of a mesh with a PositionMap

See implementation.

class PointSet(cellsize, iterable=None, manage=None)

Holds a list of points and hash them. the points are holds using indices, that allows to get the point buffer at any time, or to retreive only a point index cellsize defines the box size in which two points are considered to be the same

methods are inspired from the builtin type set

Attributes defined here:
points

the point buffer (READ-ONLY PURPOSE)

cellsize

the boxing parameter (DON’T CHANGE IT IF NON-EMPTY). mendatory and is the distance at which you want to distinguish points

dict

the hashmap from box to point indices

Build parameters:
iterable

use it to build the set by inserting elements

manage

pass a list for inplace use it, only indexing will be built

__add__(iterable)
__getitem__(pt)
__sub__(iterable)
add(pt)

add a point

difference_update(iterable)

remove the points from an iteravble

discard(pt)

remove the point at given location if any

keyfor(pt)

hash key for a point

remove(pt)

remove a point

update(iterable)

add the points from an iterable

rendering - 3D interface

This module provides a render pipeline system centered around class ‘Scene’ and a Qt widget ‘View’ for window integration and user interaction. ‘Scene’ is only to manage the objects to render (almost every madcad object). Most of the time you won’t need to deal with it directly. The widget is what actually displays it on the screen. The objects displayed can be of any type but must implement the display protocol

display protocol

a displayable is an object that implements the signatue of Display:

class display:
        box (Box)                      # delimiting the display, can be an empty or invalid box
        world (fmat4)                   # local transformation

        stack(scene)                   # rendering routines (can be methods, or any callable)
        duplicate(src,dst)             # copy the display object for an other scene if possible
        upgrade(scene,displayable)     # upgrade the current display to represent the given displayable
        control(...)                   # handle events

        __getitem__                    # access to subdisplays if there is

For more details, see class Display below

Note

As the GPU native precision is f4 (float 32 bits), all the vector stuff regarding rendering is made using simple precision types: fvec3, fvec4, fmat3, fmat4, ... instead of the usual double precision vec3

Note

There is some restrictions using the widget. This is due to some Qt limitations (and design choices), that Qt is using separated opengl contexts for each independent widgets or window.

  • a View should not be reparented once displayed

  • a View can’t share a scene with Views from an other window

  • to share a Scene between Views, you must activate

    QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts, True)
    
Rendering system
show(objs, options=None, interest=None)

shortcut to create a QApplication showing only one view with the given objects inside. the functions returns when the window has been closed and all GUI destroyed

overrides

dictionnary of callables used by Scene.display to override the classes .display(scene) method

global_context

shared open gl context, None if not yet initialized

opengl_version

minimum opengl required version

class Display

Blanket implementation for displays. This class signature is exactly the display protocol specification

world

matrix from local space to parent space

Type

fmat4

box

boudingbox of the display in local space

Type

Box

These attributes are variable members by default but can be overriden as properties if needed.
  • Mendatory part for the scene

    display(scene)self

    displays are obviously displayable as themselves

    stack(scene)[key, target, priority, callable]

    rendering functions to insert in the renderpipeline.

    the expected result can be any iterable providing tuples (key, target, priority, callable) such as:

    Key

    a tuple with the successive keys in the displays tree, most of the time implementers set it to () because it doesn’t belong to a subpart of the Display.

    Target

    the name of the render target in the view that will be rendered (see View)

    Priority

    a float that is used to insert the callable at the proper place in the rendering stack

    Callable

    a function that renders, signature is func(view)

    The view contains the uniforms, rendering targets and the scene for common ressources

    duplicate(src, dst)display/None

    duplicate the display for an other scene (other context) but keeping the same memory buffers when possible.

    return None if not possible or not implemented.

    __getitem__(key)display

    get a subdisplay by its index/key in this display (like in a scene)

    update(scene, displayable)bool

    update the current displays internal datas with the given displayable .

    if the display cannot be upgraded, it must return False to be replaced by a fresh new display created from the displayable

  • Optional part for Qt interaction

    selected (bool)

    flag set to True by left-clicking on the object

    control(view, key, sub, evt: PyQt5.QtCore.QEvent)

    handle input events occuring on the area of this display (or of one of its subdisplay). for subdisplay events, the parents control functions are called first, and the sub display controls are called only if the event is not accepted by parents

    Parameters
    • key – the key path for the current display

    • sub – the key path for the subdisplay

    • evt – the Qt event (see Qt doc)

class Scene(objs=(), options=None, ctx=None, setup=None)

rendeing pipeline for madcad displayable objects

This class is gui-agnostic, it only relys on opengl, and the context has to be created by te user.

Attributes defined here:

  • scene stuff

    ctx

    moderngl Context (must be the same for all views using this scene)

    ressources

    dictionnary of scene ressources (like textures, shaders, etc) index by name

    options

    dictionnary of options for rendering, innitialized with a copy of settings.scene

  • rendering pipeline stuff

    displays

    dictionnary of items in the scheme {'name': Display}

    stacks

    lists of callables to render each target {'target': [(key, priority, callable(view))]}

    setup

    callable for setup of each rendering target

    touched

    flag set to True if the stack must be recomputed at the next render time (there is a change in a Display or in one of its children)

When an object is added to the scene, a Display is not immediately created for it, the object is put into the queue and the Display is created at the next render. If the object is removed from the scene before the next render, it is dequeued.

add(displayable, key=None)key

add a displayable object to the scene, if key is not specified, an unused integer key is used the object is not added to the the renderpipeline yet, but queued for next rendering.

box()

computes the boundingbox of the scene, with the current object poses

dequeue()

load all pending objects to insert into the scene

display(obj)

create a display for the given object for the current scene.

this is the actual function converting objects into displays. you don’t need to call this method if you just want to add an object to the scene, use add() instead

item(key)

get the Display associated with the given key, descending the parenting tree

The parents must all make their children accessible via __getitem__

render(view)

render to the view targets.

This must be called by the view widget, once the the opengl context is set.

ressource(name, func=None)

get a ressource loaded or load it using the function func. If func is not provided, an error is raised

sync(objs: dict)

update the scene from a dictionnary of displayables, the former values that cannot be updated are discarded

touch()

shorthand for self.touched = True

update(objs: dict)

rebuild the scene from a dictionnary of displayables update former displays if possible instead of replacing it

class View(scene, projection=None, navigation=None, parent=None)

Qt widget to render and interact with displayable objects it holds a scene as renderpipeline

Attributes definied here:

scene

the Scene object displayed

projection

Perspective or Orthographic

navigation

Orbit or Turntable

tool

list of callables in priority order to receive events

  • render stuff

    targets

    render targets matching those in scene.stacks

    uniforms

    parameters for rendering, used in shaders

  • Methods to get items on the screen

    somenear(point: PyQt5.QtCore.QPoint, radius=None)PyQt5.QtCore.QPoint

    return the closest coordinate to coords, (within the given radius) for which there is an object at So if objnear is returing something, objat and ptat will return something at the returned point

    ptat(point: PyQt5.QtCore.QPoint)glm.vec3

    return the point of the rendered surfaces that match the given window coordinates

    ptfrom(point: PyQt5.QtCore.QPoint, center: glm.vec3)glm.vec3

    3D point below the cursor in the plane orthogonal to the sight, with center as origin

    itemat(point: PyQt5.QtCore.QPoint)key

    return the key path of the object at the given screen position (widget relative). If no object is at this exact location, None is returned

  • Methods to move camera

    look(position: Optional[glm.vec3] = None)

    Make the scene navigation look at the position. This is changing the camera direction, center and distance.

    adjust(box: Optional[madcad.mathutils.Box] = None)

    Make the navigation camera large enough to get the given box in . This is changing the zoom level

    center(center: Optional[glm.vec3] = None)

    Relocate the navigation to the given position . This is translating the camera.

  • Event system

    event(evt)

    Qt event handler In addition to the usual subhandlers, inputEvent is called first to handle every InputEvent.

    The usual subhandlers are used to implement the navigation through the scene (that is considered to be intrinsic to the scene widget).

    inputEvent(evt)

    Default handler for every input event (mouse move, press, release, keyboard, …) When the event is not accepted, the usual matching Qt handlers are used (mousePressEvent, KeyPressEvent, etc).

    This function can be overwritten to change the view widget behavior.

    control(key, evt)

    transmit a control event successively to all the displays matching the key path stages. At each level, if the event is not accepted, it transmits to sub items

    This function can be overwritten to change the interaction with the scene objects.

  • Rendering system

    refreshmaps()

    load the rendered frames from the GPU to the CPU

    • When a picture is used to GPU rendering it’s called ‘frame’

    • When it is dumped to the RAM we call it ‘map’ in this library

    identstep(nidents)

    updates the amount of rendered idents and return the start ident for the calling rendering pass method to call during a renderstep

view settings/interaction methods
class Turntable(center: glm.vec3 = 0, distance: float = 1, yaw: float = 0, pitch: float = 0)

navigation rotating on yaw and pitch around a center

object used as View.navigation

class Orbit(center: glm.vec3 = 0, distance: float = 1, orient: glm.vec3 = vec3(1, 0, 0))

navigation rotating on the 3 axis around a center.

object used as View.navigation

class Perspective(fov=None)

object used as View.projection

Fov

field of view (rad)

class Orthographic

object used as View.projection

helpers to trick into the pipeline
class Group(scene, objs: dict / list = None, pose=1)

a group is like a subscene

property pose

pose of the group relatively to its parents

property world

update children’s world matrix applying the current pose in addition to world

property box

computes the boundingbox of the scene, with the current object poses

__getitem__(key)

get a subdisplay by its index/key in this display (like in a scene)

dequeue(scene, objs)

update the current displays internal datas with the given displayable .

if the display cannot be upgraded, it must return False to be replaced by a fresh new display created from the displayable

stack(scene)

rendering functions to insert in the renderpipeline.

the expected result can be any iterable providing tuples (key, target, priority, callable) such as:

Key

a tuple with the successive keys in the displays tree, most of the time implementers set it to () because it doesn’t belong to a subpart of the Display.

Target

the name of the render target in the view that will be rendered (see View)

Priority

a float that is used to insert the callable at the proper place in the rendering stack

Callable

a function that renders, signature is func(view)

The view contains the uniforms, rendering targets and the scene for common ressources

update(scene, objs)

update the current displays internal datas with the given displayable .

if the display cannot be upgraded, it must return False to be replaced by a fresh new display created from the displayable

class Step(*args)

simple display holding a rendering stack step

Step(target, priority, callable)

stack(scene)

rendering functions to insert in the renderpipeline.

the expected result can be any iterable providing tuples (key, target, priority, callable) such as:

Key

a tuple with the successive keys in the displays tree, most of the time implementers set it to () because it doesn’t belong to a subpart of the Display.

Target

the name of the render target in the view that will be rendered (see View)

Priority

a float that is used to insert the callable at the proper place in the rendering stack

Callable

a function that renders, signature is func(view)

The view contains the uniforms, rendering targets and the scene for common ressources

class Displayable(build, *args, **kwargs)

simple displayable initializeing the given Display class with arguments

at the display creation time, it will simply execute build(*args, **kwargs)

displayable(obj)

return True if the given object has the matching signature to be added to a Scene

scheme - annotation functionalities

schematics system
class Scheme(vertices=None, spaces=None, primitives=None, annotation=True, **kwargs)
set(*args, **kwargs)

change the specified attributes in the current default vertex definition

add(obj, **kwargs)

add an object to the scheme if it is a mesh it’s merged in the current buffers else it is added as a component to the current space

component(obj, **kwargs)

add an object as component associated to the current space

display(sch)

display for schemes

attributes: :spaces: numpy array of matrices for each space, sent as uniform to the shader :vb_vertices: vertex buffer for vertices :vas: vertex array associated to each shader

view(view)
screen(view)
world(view)
halo_world(position)
halo_view(position)
halo_screen(position)
scale_screen(center)
scale_view(center)
annotation functions
note_leading(placement, offset=None, text='here')

place a leading note at given position

placement can be any of Mesh, Web, Wire, axis, vec3

_images/note_leading.png
note_floating(position, text)

place a floating note at given position

note_distance(a, b, offset=0, project=None, d=None, tol=None, text=None)

place a distance quotation between 2 points, the distance can be evaluated along vector project if specified

_images/note_distance.png
note_distance_planes(s0, s1, offset=None, d=None, tol=None, text=None)

place a distance quotation between 2 meshes

s0 and s1 can be any of Mesh, Web, Wire

_images/note_distance_planes.png
note_distance_set(s0, s1, offset=0, d=None, tol=None, text=None)

place a distance quotation between 2 objects. This is the distance between the closest elements of both sets

s0 and s1 can be any of Mesh, Web, Wire, vec3

_images/note_distance_set.png
note_angle(a0, a1, offset=0, d=None, tol=None, text=None, unit='deg')

place an angle quotation between 2 axis

_images/note_angle.png
note_angle_planes(s0, s1, offset=0, d=None, tol=None, text=None, unit='deg')

place an angle quotation between 2 meshes considered to be plane (surface) or straight (curve)

s0 and s1 can be any of Mesh, Web, Wire, Axis

note_angle_edge(part, edge, offset=0, d=None, tol=None, text=None, unit='deg')

place an angle quotation around a mesh edge

_images/note_angle_edge.png
note_label(placement, offset=None, text='!', style='rect')

place a text label upon an object

placement can be any of Mesh, Web, Wire, axis, vec3

_images/note_label.png

standard - common standard parametric parts

Module exposing many functions to create/visualize standard parts. Those are following the ISO (metric) specifications as often as possible.

Most parts are as easy to create as:

>>> nut(8)    # nut with nominal diameter 8mm
>>> screw(8, 16)   # screw with match diameter 8mm, and length 16mm

The parts usually have many optional parameters that default to usual recommended values. You can override this by setting the keyword arguments:

>>> screw(8, 16, head='button', drive='slot')
screw stuff
nut(d, type='hex', detail=False)madcad.mesh.Mesh

create a standard nut model using the given shape type

Parameters
  • d – nominal diameter of the matching screw

  • type – the nut shape

  • detail – if True, the thread surface will be generated, else it will only be a cylinder

If d alone is given, the other parameters default to the ISO specs: https://www.engineersedge.com/iso_flat_washer.htm

Currently only shape ‘hex’ is available.

_images/hexnut.png
screw(d, length, filet_length=None, head='SH', drive=None, detail=False)

create a standard screw using the given drive and head shapes

Parameters
  • d – nominal diameter of the screw

  • length – length from the screw head to the tip of the screw

  • filet_length – length of the filet on the screw (from the tip), defaults to length

  • head – name of the screw head shape

  • drive – name of the screw drive shape

  • detail – if True, the thread surface will be generated, else it will only be a cylinder

It’s also possible to specify head and drive at once, using their codenames:

>>> screw(5, 16, head='SHT')   # socket head and torx drive
available screw heads:
  • socket (default)

  • button

  • flat (aka. cone)

available screw drives:
  • hex

  • torx (not yet available)

  • phillips (cross) (not yet available)

  • slot

All possible shapes:
_images/screw.png
washer(d, e=None, h=None)madcad.mesh.Mesh

create a standard washer. Washers are useful to offset screws and avoid them to scratch the mount part

Parameters
  • d – the nominal interior diameter (screw or anything else), the exact washer interior is slightly bigger

  • e – exterior diameter

  • h – height/thickness

If d alone is given, the other parameters default to the ISO specs: https://www.engineersedge.com/iso_flat_washer.htm

_images/washer.png
coilsprings
coilspring_compression(length, d=None, thickness=None, solid=True)

return a Mesh model of a croilspring meant for use in compression

Parameters
  • length – the distance between its two ends

  • d – the exterior diameter (the coilspring can fit in a cylinder of that diameter)

  • thickness – the wire diameter of the spring (useless if solid is disabled)

  • solid – disable it to get only the tube path of the coil, and have a Wire as return value

_images/coilspring-compression.png
coilspring_tension(length, d=None, thickness=None, solid=True)

return a Mesh model of a croilspring meant for use in tension

Parameters
  • length – the distance between its two hooks

  • d – the exterior diameter (the coilspring can fit in a cylinder of that diameter)

  • thickness – the wire diameter of the spring (useless if solid is disabled)

  • solid – disable it to get only the tube path of the coil, and have a Wire as return value

_images/coilspring-tension.png
coilspring_torsion(arm, angle=0.7853981633974483, d=None, length=None, thickness=None, hook=None, solid=True)

return a Mesh model of a croilspring meant for use in torsion

Parameters
  • arm – the arms length from the coil axis

  • length – the coil length (and distance between its hooks)

  • d – the exterior diameter (the coilspring can fit in a cylinder of that diameter)

  • thickness – the wire diameter of the spring (useless if solid is disabled)

  • hook – the length of arm hooks (negative for hooks toward the interior)

  • solid – disable it to get only the tube path of the coil, and have a Wire as return value

_images/coilspring-torsion.png
bearings
bearing(dint, dext=None, h=None, circulating='ball', contact=0, hint=None, hext=None, sealing=False, detail=False)

Circulating bearings rely on rolling elements to avoid friction and widen the part life. Its friction depends on the rotation speed but not on the current load.

see bearing specs at https://koyo.jtekt.co.jp/en/support/bearing-knowledge/

Parameters
  • dint – interior bore diameter

  • dext – exterior ring diameter

  • h – total height of the bearing

  • hint – height of the interior ring. Only for angled roller bearings

  • hext – height of the exterior ring. Only for angled roller bearings

  • circulating

    The type of circulating element in the bearing

    • ball

    • roller

  • contact

    Contact angle (aka pressure angle). It decided what directions of force the bearing can sustain.

    • 0 for radial bearing

    • pi/2 for thrust (axial) bearings

    • 0 < contact < pi/2 for conic bearings

  • sealing – True if the bearing has a sealing side. Only for balls bearings with contact = 0

  • detail – If True, the returned model will have the circulating elements and cage, if False the returned element is just a bounding representation

examples

By default bearing() provides a simplified representation of the bearing, showing mostly its functionnal surfaces (interfaces with other parts). To show all the interior details, use the detail=True option.

# bounded representation of a ball bearing
bearing(12)
_images/bearing-bounded.png

overview

  • ball bearing

    Those are extremely flexible in the force direction, way to mount, and quite cheap because widely used.

    # detailed representation of a ball bearing
    bearing(12, detail=True)
    
    _images/bearing-ball.png
  • roller bearing

    Those can hold a much bigger force than ball bearings, but must be carefully mounted in the direction of that force to always be in pressure (requiring extra design for this)

    # roller bearing
    bearing(12, circulating='roller', contact=radians(20), detail=True)
    
    _images/bearing-roller.png
  • thrust bearing

    Those can hold a bigger force that radial ball bearings, but must be carefully mounted to always stay in pressure. They also are less precise than the two previous.

    # thrust bearing
    bearing(12, contact=radians(90), detail=True)
    
    _images/bearing-thrust.png
slidebearing(dint, h=None, thickness=None, shoulder=None, opened=False)

Slide bearings rely on gliding parts to ensure a good pivot. It’s much cheaper than circulating bearings and much more compact. But needs lubricant and has a shorter life than circulating bearings. Its friction depends on the rotation speed and on the load.

Parameters
  • dint – interior diameter

  • h – exterior height (under shoulder if there is)

  • thickness – shell thickness, can be automatically determined

  • shoulder – distance from bore to shoulder tip, put 0 to disable

  • opened – enable to have a slight breach that allows a better fitting to the placement hole

examples

  • with slight breach

    # put an openning to better fit bad bore diameter
    slidebearing(10, 12, 0.5, open=True)
    
    _images/slidebearing-opened.png
  • with shoulder

    # put a shoulder to support a slight thrust
    slidebearing(10, 12, shoulder=3)
    
    _images/slidebearing-shoulder.png

gear - generation of gears, racks, etc

This module provide functions to generate racks and gears with many different shapes.

The mainstream gears are involute gears (using involute of circles as contact curves). This is due to the standard shape of racks. And involute gears are the best choice for most designs, which is why the current implementation focusses on them.

However the tools here are modular, which means you can combine them with your own functions to create customized gears.

Examples

As a first use, you may want to generate a fully finished spur gear If you do not specify optional arguments, the function will provide good defaults for it.

>>> # fully featured gear
>>> gear(step=3, z=12, depth=4, bore_radius=1.5)

You may want to generate a more bizarre gear, with a a different hub for instance. You will then need to assemble a gear from its 3 components: exterior (tooths), structure (between hub and exterior), and hub (what holds the gear on an axis)

>>> # this assemble a gear specifying each independant sub-parts
>>> ext_radius = (3*12)/(2*pi) - 3
>>> int_radius = 4
>>> geargather(
...             gearexterior(repeat_circular(gearprofile(3, 30), 30), ext_radius, 3, 30, depth=4),
...             gearstructure('rounded', ext_radius, int_radius, 2, patterns=6),
...             my_hub_mesh,
...             )

For reuse in your custom functions, the functions used to generate the gears are exposed:

>>> # this is the raw profile of a tooth
>>> gearprofile(step=3, z=12)
tooth profiles generation

The following functions are focussing on involute gears. If you want more details on how involutes gears are defined and computed, you can take a look at the algorithm section

rackprofile(step, h=None, offset=0, alpha=0.3490658503988659, resolution=None)madcad.mesh.Wire

Generate a 1-period tooth profile for a rack

Parameters
  • step – period length over the primitive line

  • h – tooth half height

  • offset – rack reference line offset with the primitive line (as a distance) - the primitive line is the adherence line with gears - the reference line is the line half the tooth is above and half below

  • alpha – angle of the tooth sides, a.k.a pressure angle of the contact

gearprofile(step, z, h=None, offset=0, alpha=0.3490658503988659, resolution=None, **kwargs)madcad.mesh.Wire

Generate a 1-period tooth profile for a straight gear

Parameters
  • step – period length over the primitive circle

  • z – number of tooth on the gear this profile is meant for

  • h – tooth half height

  • offset – offset distance of the matching rack profile (see above)

  • alpha – pressure angle in the contact

The result is the following curve (the white one):

_images/gearprofile.png
gear generation
gear(step, z: int, depth, bore_radius=0, int_height=0, pattern='full', **kwargs)madcad.mesh.Mesh

Generate a pinion.

Any extra argument will go to functions gearprofile, gearstructure, or gearhub

Parameters
  • step (float) – tooth step over the primitive curve, same as the matching rack step the primitive perimeter is step * z and radius is step * z / 2*pi

  • z (int) – number of teeth

  • depth (float) – extrusion eight - width of the gear along its axis

  • bore_radius (float) – radius of the main bore

  • int_height (float) – if you want a pinion with a structure thinner than the value of depth, the total height will be total_height = depth - 2 * int_height

  • pattern – determine the structure between exterior (tooth) and hub This argument specifies the use a a function named 'pattern_'+pattern in this module.

  • Extra parameters for gearprofile

    offset (float): offset of tooth (as a distance) alpha (float): pressure angle in radian

  • Extra parameters for gearexterior

    helix_angle (float): helix angle to get a helical pinion in radian chamfer (float): chamfer angle - only for straight pinion

  • Extra parameters for gearstructure

    ratio (float): influence the proportion of dimensions of the structure

    Note: int_height impacts the thickness of the structure unless specified

  • Extra parameters for gearhub

    hub_height (float): height of the hub shoulder

Note

  • int_height impacts the height of the hub unless specified.

  • if hub_height is null, there will be no hub.

a simple use:

>>> gear(3, 12, 4, bore_radius=1.5)
_images/gear-minimal.png

a more advanced use:

>>> gear(3, 30, 4, bore_radius=2, pattern='rounded', patterns=6, int_height=1, chamfer=radians(20))
_images/gear-advanced.png
geargather(exterior, structure, hub)madcad.mesh.Mesh

Gather all parts: exterior, structure, and hub

You can obtain them via the provided functions, or generate them yourself.

gearexterior(profile: madcad.mesh.Web, min_radius, step, z: int, depth, helix_angle=0, chamfer=0, **kwargs)madcad.mesh.Mesh

Generate the external part of the pinion

Parameters
  • profile (Web) – profile of the pinion generated by gearprofile

  • min_radius (float) – internal radius of the external part

  • step (float) – step of chordal pitch

  • z (int) – number of teeth

  • depth (float) – extrusion eight - width of the gear along its axis

  • helix_angle (float) – helix angle for helical gears - only without bevel; bevel = False - it must be a radian angle

  • chamfer (float) – chamfer angle - only for straight pinion

_images/gearexterior.png
gearstructure(pattern, ext_radius, int_radius, depth, int_height=0, ratio=1, **kwargs)madcad.mesh.Mesh

Generate the internal part of the pinion

Parameters
  • ext_radius (float) – given by the attribut _radius of the result of repeat_circular - to avoid interference, it must be smaller than _radius (for instance 0.95 * _radius))

  • int_radius (float) – it is the same radius of the largest radius of the hub part

  • depth (float) – face width

  • pattern – any of ‘full’, ‘circle’, ‘rect’, ‘rounded’

  • int_height (float) – if you want a pinion with a structure thinner than the value of depth, the total height will be total_height = depth - 2 * int_height

_images/gearstructure.png
gearhub(bore_radius, depth, int_height=0, hub_height=None, hub_radius=None, **kwargs)madcad.mesh.Web

Generate a hub for a pinion part

Parameters
  • bore_radius (float) – radius of the central bore

  • depth (float) – face width; same parameter for gearexterior and gearstructure

  • int_height (float) – only useful for no hub case, checkout the function gearstructure for more information

  • hub_height (float) – height of the hub

  • hub_radius (float) – external radius of the hub

Note

  • if bore_radius is null, the function will return a top circle and a bottom circle used for geargather function

  • if hub_height is null, the function will return a structure with a bore and without a hub

helper tools
gearcircles(step, z, h=None, offset=0, alpha=0.5235987755982988)

return the convenient circles radius for a gear with the given parameters return is (primitive, base, bottom, top)

involute(c, t0, t)

give a point of parameter t on involute from circle or radius c, starting from t0 on the circle

t and t0 are angular positions

involuteat(c, r)

give the parameter for the involute of circle radius c to reach radius r

involuteof(c, t0, d, t)

give a point of parameter t on involute with offset, from circle or radius c, starting from t0 on the circle

t and t0 are angular positions

structure patterns

Those are the functions generating usual structures ready to used in geargather.

pattern_circle(ext_radius, int_radius, depth, int_height=0, ratio=1, patterns: int = 5, circles_radius=None, circles_place=None, **kwargs)madcad.mesh.Mesh

Generate two parts of the structure (the top and the bottom) with patterns distributed on the whole structure.

Return a tuple (Web, Web, Mesh) where the first Web is the top of the structure, the second Web is the bottom of the structure and the last element Mesh is all side surfaces.

Parameters
  • ext_radius (float) – radius of the external border of the structure

  • int_radius (float) – radius of the internal border of the structure

  • depth (float) – face width

  • int_height (float) – if you want a pinion with a structure thinner than the value of depth, the total height will be total_height = depth - 2 * int_height

  • ratio (float) – number that allows to change proportionally the radius of circles

  • patterns (int) – number of circles of the structure

  • circles_radius (float) – radius of circles

  • circles_place (float) – radius where the origins of circles are placed

Note

  • for instance, with a ratio of 1.5, the radius of circles circles_radius will be divided by 1.5

  • if circles_radius is chosen, ratio won’t impact the radius of circles circles_radius

pattern_full(ext_radius, int_radius, depth, int_height=0, **kwargs)madcad.mesh.Mesh

Generate two full parts of the structure (the top and the bottom).

Return a tuple (Web, Web, None) where the first Web is the top of the structure and the second Web is the bottom of the structure.

Parameters
  • ext_radius (float) – float (radius of the external border of the pattern

  • int_radius (float) – radius of the internal border of the pattern

  • depth (float) – face width

  • int_height (float) – if you want a pinion with a structure thinner than the value of depth, the total height will be total_height = depth - 2 * int_height

pattern_rect(ext_radius, int_radius, depth, int_height=0, ratio=1, patterns=5, ext_thickness=None, int_thickness=None, **kwargs)madcad.mesh.Mesh

Generate two parts of the structure (the top and the bottom) with patterns distributed on the whole structure. All corners are straight. Check the function pattern_rounded to get rounded corners.

Return a tuple (Web, Web, Mesh) where the first Web is the top of the structure, the second Web is the bottom of the structure and the last element Mesh is all side surfaces.

Parameters
  • ext_radius – float (radius of the external border of the structure)

  • int_radius – float (radius of the internal border of the structure)

  • depth – float (face width)

  • int_height – float (if you want a pinion with a structure thinner than the value of depth, the total height will be total_height = depth - 2 * int_height)

  • ratio – float (it is a number which is the proportional factor for an homothety of patterns)

  • patterns – int (number of patterns inside the structure)

  • ext_thickness (float) – internal radius of the pattern

  • int_thickness (float) – external radius of the pattern

Note

  • for instance, if ratio is 1.5, the area of the pattern will be divided by 1.5.

  • if r_int and r_ext are chosen, ratio won’t impact these parameters

pattern_rounded(ext_radius, int_radius, depth, int_height=0, ratio=1, patterns: int = 5, ext_thickness=None, int_thickness=None, **kwargs)madcad.mesh.Mesh

Generate two parts of the structure (the top and the bottom) with patterns distributed on the whole structure. All corners are rounded. Check the function pattern_rect to get straight corners.

Return a tuple (Web, Web, Mesh) where the first Web is the top of the structure, the second Web is the bottom of the structure and the last element Mesh is all side surfaces.

Parameters
  • ext_radius – float (radius of the external border of the structure)

  • int_radius – float (radius of the internal border of the structure)

  • depth – float (face width)

  • int_height – float (if you want a pinion with a structure thinner than the value of depth, the total height will be total_height = depth - 2 * int_height)

  • ratio – float (it is a number which is the proportional factor for an homothety of patterns)

  • patterns – int (number of patterns inside the structure)

  • ext_thickness (float) – internal radius of the pattern

  • int_thickness (float) – external radius of the pattern

Note

  • for instance, if ratio is 1.5, the area of the pattern will be divided by 1.5.

  • if r_int and r_ext are chosen, ratio won’t impact these parameters

solvers

solve(constraints, fixed=(), *args, **kwargs)

short hand to use the class Problem

solvekin(joints, fixed=(), precision=0.0001, maxiter=None, damping=1)

solver for kinematic joint constraints.

Unlike solve, the present solver is dedicated to kinematic usage (and far more efficient and precise). It doesn’t rely on variables as defined by solve, but instead use Solids as constraints.

volume boolean operators

difference(a, b)madcad.mesh.Mesh

return a mesh for the volume of a less the common volume with b It is a boolean with selector (False, True)

union(a, b)madcad.mesh.Mesh

return a mesh for the union of the volumes. It is a boolean with selector (False,False)

intersection(a, b)madcad.mesh.Mesh

return a mesh for the common volume. It is a boolean with selector (True, True)

generation functions

extrusion(trans, line, alignment=0)

create a surface by extruding the given outline by a transformation

:param : line: a line (Web or Wire) or a surface (Mesh) to extrude :param : trans: any transformation object accepted by mathutils.transform :param : alignment: when > 0 the line is extruded both sides (the transformation is linearly interpoled)

revolution(angle, axis, profile, resolution=None)

create a revolution surface by extruding the given outline steps is the number of steps between the start and the end of the extrusion

tube(outline, path, end=True, section=True)

create a tube surface by extrusing the outline along the path if section is True, there is a correction of the segments to keep the section undeformed by the curve

blending functions

junction(*args, center=None, tangents='normal', weight=1.0, match='length', resolution=None)

join several outlines with a blended surface

tangents:

‘straight’ no interpolation, straight lines ‘normal’ interpolated surface starts normal to the interfaces ‘tangent’ interpolated surface starts tangent to the interfaces

weight:

factor applied on the tangents before passing to interpol2 or intri_smooth the resulting tangent is computed in point a as weight * distance(a,b) * normalize(tangent[a])

match:

‘length’ share the outline between 2 matched points to assign the same length to both sides ‘corner’ split the curve to share around a corner

center:

position of the center of the junction node used to determine connexion between interfaces can be usefull for particularly weird and ambiguous interfaces

Note

match method ‘corner’ is not yet implemented

blendpair(*interfaces, match='length', tangents='tangent', weight=1.0, resolution=None)madcad.mesh.Mesh

blend between a pair of interfaces

match:

‘length’, ‘closest’ refer to match_* in this module

see junction for the other parameters.

blendloop(interface, center=None, tangents='tangent', weight=1.0, resolution=None)madcad.mesh.Mesh

blend inside a loop interface

see junction for the parameters.

cut functions

chamfer(mesh, indices, cutter)
chamfer(mesh: madcad.mesh.Mesh, edges, cutter)
chamfer(web: madcad.mesh.Web, points, cutter)
chamfer(wire: madcad.mesh.Wire, points, cutter)

chamfer a Mesh/Web/Wire around the given edges/points, using the given cutter

bevel(mesh, indices, cutter)
bevel(mesh: madcad.mesh.Mesh, edges, cutter, resolution=None)
bevel(obj: madcad.mesh.Web, points, cutter, resolution=None)
bevel(wire: madcad.mesh.Wire, points, cutter, resolution=None)

bevel a Mesh/Web/Wire around the given edges/points, using the given cutter

standard parts

pyadcad contains a collection of functions to generate some of the most standard parts. checkout module module standard and module gear

Algorithms explainations

We describe here the most challenging algorithms used in madcad.

Development guidelines

Some details about the architecture design, the code style, and the link of tools used.

TODO

Indices and tables