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.
plyfile to read/write
.ply
filesstl-numpy to read/write
.stl
filespywavefront to read
.obj
files
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 andtuple
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.
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])

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

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 …

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
)

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

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)

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'
)

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
andy
, i.e.,result = x * y
. dot(x: vecN, y: vecN) -> floatReturns the dot product of
x
andy
, i.e.,result = x[0] * y[0] + x[1] * y[1] + ...
- dot(x: quat, y: quat) -> float
Returns dot product of
x
andy
, i.e.,x[0] * y[0] + x[1] * y[1] + ...
- cross(x: vec3, y: vec3) → vec3¶
Returns the cross product of
x
andy
. cross(x: quat, y: quat) -> quatCompute a cross product.
- mix(x: number, y: number, a: float) → number¶
- Returns
x * (1.0 - a) + y * a
, i.e., the linear blend ofx
andy
using the floating-point value
a
. The value fora
is not restricted to the range[0, 1]
.- mix(x: number, y: number, a: bool) -> number
Returns
y
ifa
isTrue
andx
otherwise.- mix(x: vecN, y: vecN, a: fvecN) -> vecN
Returns
x * (1.0 - a) + y * a
, i.e., the linear blend ofx
andy
using the floating-point valuea
. The value fora
is not restricted to the range[0, 1]
.- mix(x: vecN, y: vecN, a: bvecN) -> vecN
For each component index
i
: Returnsy[i]
ifa[i]
isTrue
andx[i]
otherwise.- mix(x: matNxM, y: matNxM, a: fmatNxM) -> matNxM
Returns
x * (1.0 - a) + y * a
, i.e., the linear blend ofx
andy
using the floating-point valuea
for each component. The value fora
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 ofx
andy
using the floating-point valuea
for each component. The value fora
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.
- Returns
- length(x: float) → float¶
Returns the length of
x
, i.e.,abs(x)
. length(x: vecN) -> floatReturns 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
andp1
, i.e.,length(p0 - p1)
. distance(p0: vecN, p1: vecN) -> floatReturns the distance between
p0
andp1
, i.e.,length(p0 - p1)
.
- normalize(x: vecN) → vecN¶
Returns a vector in the same direction as
x
but with length of1
. normalize(x: quat) -> quatReturns 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
alongdir
, equivalent todot(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 alongdir
, equivalent tovec - 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 todot(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 distanceAlias to
glm.l2Norm
aliasglm.length
- norminf(x) → float¶
norm L infinite ie.
max(abs(x), abs(y), abs(z))
Alias to
glm.lxNorm
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.
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
orstripgroups
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
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
- 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
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
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 otherTorsor
, and* /
withfloat
- 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 bygap/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:
optional argument
resolution
passed toprimitive.mesh()
or toweb()
orwire()
optional attribute
resolution
of the primitive objectvalue 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 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)
andSegment(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)
andSegment(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
- 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
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
an iterable of names of variable members in the constraint object
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
- Powell for simple non-continuous problems with tons of variables
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 top
- 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 bytransformation
).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 aCircle
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
orWire
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
orintri_smooth
the resulting tangent is computed in pointa
asweight * 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
- blendloop(interface, center=None, tangents='tangent', weight=1.0, resolution=None) → madcad.mesh.Mesh¶
blend inside a loop interface
see
junction
for the parameters.
- 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.
- 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
![]()
- 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
![]()
- 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
![]()
cutters (cut methods)¶
- 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
ortangentjunction
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)
- 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)
- intersection(a, b) → madcad.mesh.Mesh¶
return a mesh for the common volume. It is a boolean with selector (True, True)
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.
- 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
orOrthographic
- navigation
Orbit
orTurntable
- 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 ofMesh, Web, Wire, axis, vec3
![]()
- 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![]()
- note_distance_planes(s0, s1, offset=None, d=None, tol=None, text=None)¶
place a distance quotation between 2 meshes
s0
ands1
can be any ofMesh, Web, Wire
![]()
- 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
ands1
can be any ofMesh, Web, Wire, vec3
![]()
- note_angle(a0, a1, offset=0, d=None, tol=None, text=None, unit='deg')¶
place an angle quotation between 2 axis
![]()
- 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
ands1
can be any ofMesh, 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
![]()
- note_label(placement, offset=None, text='!', style='rect')¶
place a text label upon an object
placement
can be any ofMesh, Web, Wire, axis, vec3
![]()
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.htmCurrently only shape ‘hex’ is available.
- 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:
see wikipedia for the [drive types](https://en.wikipedia.org/wiki/List_of_screw_drives)
see [here for a guide of what to use](https://www.homestratosphere.com/types-of-screws/)
see wikipedia for [standard screw thread](https://en.wikipedia.org/wiki/ISO_metric_screw_thread)
- 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
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
- 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
- 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
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 bearingpi/2
for thrust (axial) bearings0 < 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 thedetail=True
option.# bounded representation of a ball bearing bearing(12)![]()
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)![]()
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)![]()
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)![]()
- 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)![]()
with shoulder
# put a shoulder to support a slight thrust slidebearing(10, 12, shoulder=3)![]()
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):
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
, orgearhub
- Parameters
step (float) – tooth step over the primitive curve, same as the matching rack step the primitive perimeter is
step * z
and radius isstep * 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 betotal_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 specifiedExtra 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)
a more advanced use:
>>> gear(3, 30, 4, bore_radius=2, pattern='rounded', patterns=6, int_height=1, chamfer=radians(20))
- 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 anglechamfer (float) – chamfer angle - only for straight pinion
- 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 ofrepeat_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 betotal_height = depth - 2 * int_height
- 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
andgearstructure
int_height (float) – only useful for no hub case, checkout the function
gearstructure
for more informationhub_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 forgeargather
functionif
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 radiusc
, starting fromt0
on the circlet
andt0
are angular positions
- involuteat(c, r)¶
give the parameter for the involute of circle radius
c
to reach radiusr
- involuteof(c, t0, d, t)¶
give a point of parameter
t
on involute with offset, from circle or radiusc
, starting fromt0
on the circlet
andt0
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 secondWeb
is the bottom of the structure and the last elementMesh
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 betotal_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.5if
circles_radius
is chosen,ratio
won’t impact the radius of circlescircles_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 secondWeb
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 betotal_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 functionpattern_rounded
to get rounded corners.Return a tuple (Web, Web, Mesh) where the first
Web
is the top of the structure, the secondWeb
is the bottom of the structure and the last elementMesh
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 betotal_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
andr_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 functionpattern_rect
to get straight corners.Return a tuple (Web, Web, Mesh) where the first
Web
is the top of the structure, the secondWeb
is the bottom of the structure and the last elementMesh
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 betotal_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
andr_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
orintri_smooth
the resulting tangent is computed in pointa
asweight * 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 modulesee
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