Major rewrite: switch from occmodel to pythonocc-core + vtk
.. this requires reverting to python2 due to lack of python3 vtk packages in debian stretch. pythonocc-core is much more verbose, but it exposes the whole oce API to Python, which means that I'm not stuck when I want to expose something not in occmodel. I hope that it's a better way forward.
This commit is contained in:
parent
34be878d10
commit
db5b15bfc4
18 changed files with 469 additions and 145 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
examples/*.stl
|
||||
examples/*.png
|
||||
.coverage
|
||||
*.py[oc]
|
||||
build
|
||||
|
|
|
|||
25
README.md
25
README.md
|
|
@ -5,30 +5,33 @@
|
|||
*poc* is a tool in the vein of *OpenSCAD* for creating 3D models in a high
|
||||
level language with a minimum of boilerplate.
|
||||
|
||||
*poc* programs are Python3 programs, executed in an environment that
|
||||
*poc* programs are Python2 programs, executed in an environment that
|
||||
provides convenient shorthand for performing geometric operations.
|
||||
|
||||
*poc* uses OpenCASCADE (via occmodel) to implement its geometric operations.
|
||||
This means it has different strengths and weaknesses compared to *OpenSCAD*,
|
||||
which uses CGAL. For instance, OpenCASCADE has `fillet` as a first-class
|
||||
operation, while it lacks `minkowski` and `hull` which are quite frequently
|
||||
used in *OpenSCAD*.
|
||||
Python2 is used instead of Python3 because a python3 compatible version of
|
||||
vtk is not availble in debian stretch. However, the python3-like features of
|
||||
`print_function` and `division` are automatically enabled.
|
||||
|
||||
*poc* uses OpenCASCADE (via pythonocc-core) to implement its geometric
|
||||
operations. This means it has different strengths and weaknesses compared to
|
||||
*OpenSCAD*, which uses CGAL. For instance, OpenCASCADE has `fillet` as a
|
||||
first-class operation, while it lacks `minkowski` and `hull` which are quite
|
||||
frequently used in *OpenSCAD*.
|
||||
|
||||

|
||||
|
||||
# Setup
|
||||
* Install dependencies
|
||||
* Run setup, e.g., `sudo python3 setup.py install`
|
||||
* Invoke `pocview somefile.poc` to lanuch a viewer. It autoupdates if you modify the input file. Note that some versions of `gltools` create fullscreen windows unconditionally (and iconify them when they lose focus), which is inconvenient for this use.
|
||||
* Run setup, e.g., `sudo python setup.py install`
|
||||
* Invoke `pocview somefile.poc` to lanuch a viewer. It autoupdates if you modify the input file.
|
||||
* Invoke `poc somefile.poc` to create `somefile.stl`
|
||||
* or use `#!/usr/bin/env poc` so that `./somefile.poc` is executable
|
||||
|
||||
# Dependencies
|
||||
|
||||
* [OpenCASCADE Community Edition (OCE)](https://github.com/tpaviot/oce/pulls)
|
||||
* [occmodel](https://github.com/tenko/occmodel)
|
||||
* [geotools](https://github.com/tenko/geotools)
|
||||
* [gltools](https://github.com/tenko/gltools)
|
||||
* pythonocc-core
|
||||
* python-vtk6
|
||||
|
||||
# Stability
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
#!/bin/sh
|
||||
python3-coverage run ./poc || true
|
||||
for i in examples/*.poc; do
|
||||
echo $i
|
||||
python3-coverage run -a ./poc $i || exit $?
|
||||
done
|
||||
python3-coverage report -m
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
python-coverage erase
|
||||
python-coverage run ./poc || true
|
||||
find examples -name \*.poc -print0 | xargs -0n1 -P`getconf _NPROCESSORS_ONLN` python-coverage run -p ./poc
|
||||
#for i in examples/*.poc; do
|
||||
# echo $i
|
||||
# python-coverage run -a ./poc $i || exit $?
|
||||
#done
|
||||
python-coverage combine -a .coverage.*
|
||||
python-coverage report -m --include poctools.py
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx3-build
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
|
|
|
|||
28
docs/conf.py
28
docs/conf.py
|
|
@ -18,8 +18,34 @@
|
|||
#
|
||||
import os
|
||||
import sys
|
||||
import mock
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.modules['occmodel'] = object()
|
||||
for s in '''
|
||||
OCC
|
||||
OCC.BRepAlgo
|
||||
OCC.BRepAlgoAPI
|
||||
OCC.BRepBuilderAPI
|
||||
OCC.BRepBndLib
|
||||
OCC.BRepFilletAPI
|
||||
OCC.BRepGProp
|
||||
OCC.BRepLib
|
||||
OCC.BRepOffsetAPI
|
||||
OCC.BRepPrimAPI
|
||||
OCC.BRepTools
|
||||
OCC.Bnd
|
||||
OCC.GC
|
||||
OCC.GCE2d
|
||||
OCC.Geom
|
||||
OCC.Geom2d
|
||||
OCC.gp
|
||||
OCC.GProp
|
||||
OCC.StlAPI
|
||||
OCC.TopAbs
|
||||
OCC.TopExp
|
||||
OCC.TopTools
|
||||
OCC.TopoDS
|
||||
'''.split():
|
||||
sys.modules[s] = mock.Mock()
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ Python OCE Composer (poc)
|
|||
*poc* is a tool in the vein of *OpenSCAD* for creating 3D models in a high
|
||||
level language with a minimum of boilerplate.
|
||||
|
||||
*poc* programs are Python3 programs, executed in an environment that
|
||||
*poc* programs are Python2 programs, executed in an environment that
|
||||
provides convenient shorthand for performing geometric operations.
|
||||
|
||||
*poc* uses OpenCASCADE (via occmodel) to implement its geometric operations.
|
||||
*poc* uses OpenCASCADE to implement its geometric operations.
|
||||
This means it has different strengths and weaknesses compared to *OpenSCAD*,
|
||||
which uses CGAL. For instance, OpenCASCADE has `fillet` as a first-class
|
||||
operation, while it lacks `minkowski` and `hull` which are quite frequently
|
||||
|
|
@ -21,6 +21,8 @@ used in *OpenSCAD*.
|
|||
The the design of the *poc* standard library is very much in flux, and
|
||||
there are likely to be compatibility-breaking changes as it develops.
|
||||
|
||||
For installation and setup instructions, see README.md.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ poctools module
|
|||
|
||||
.. automodule:: poctools
|
||||
|
||||
Primitives
|
||||
~~~~~~~~~~
|
||||
Solid Primitives
|
||||
~~~~~~~~~~~~~~~~
|
||||
Each primitive is added to the currently active group operation.
|
||||
At the outermost level, the active group operation is union().
|
||||
|
||||
|
|
@ -12,13 +12,28 @@ At the outermost level, the active group operation is union().
|
|||
.. autofunction:: Cylinder
|
||||
.. autofunction:: Cone
|
||||
.. autofunction:: Sphere
|
||||
.. autofunction:: Text
|
||||
.. autofunction:: Torus
|
||||
.. autofunction:: Extrude
|
||||
.. autofunction:: Loft
|
||||
.. autofunction:: Pipe
|
||||
.. autofunction:: Revolve
|
||||
|
||||
Other primitives
|
||||
~~~~~~~~~~~~~~~~
|
||||
These classes have classmethods to construct objects of the given
|
||||
type. They may be useful in constructing solid primitives;
|
||||
for example, `poctools.Loft` needs a sequence of `Edge` objects.
|
||||
|
||||
This syntax is for compatibility with `occmodel`. A future improvement
|
||||
to `poc` should change them so that e.g., `Loft` becomes a group operation,
|
||||
and `Edge` becomes a solid primitive that implicitly adds itself to the
|
||||
surrounding `Loft`.
|
||||
|
||||
.. autoclass:: Edge
|
||||
.. autoclass:: Face
|
||||
.. autoclass:: Wire
|
||||
.. autoclass:: Vertex
|
||||
|
||||
Postfix operations
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
A postfix operation modifies the currently active group operation
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
# This example isn't useful, except that it increases code coverage
|
||||
import geotools
|
||||
with Union():
|
||||
with Rotated(1, (0,0,1)):
|
||||
Sphere((0,0,0), 6)
|
||||
with Filleted(2), Transformed(geotools.Transform()):
|
||||
with Filleted(.5), Transformed(Matrix()), Chamfered(2):
|
||||
Box((-5,-5,-5), (5,5,5))
|
||||
print(Bbox())
|
||||
print(CenterOfMass())
|
||||
|
|
@ -12,3 +11,31 @@ with Union():
|
|||
print(len(list(Wires())))
|
||||
|
||||
Text(8, .25, 'Hello_world')
|
||||
|
||||
import poctools
|
||||
poctools._dir(poctools._dir((1,2,3)))
|
||||
poctools._vec(poctools._vec((1,2,3)))
|
||||
poctools._pt(poctools._pt((1,2,3)))
|
||||
poctools.Matrix(
|
||||
(1,0,0),
|
||||
(0,1,0),
|
||||
(0,0,1))
|
||||
poctools.Matrix(
|
||||
(1,0,0,0),
|
||||
(0,1,0,0),
|
||||
(0,0,1,0))
|
||||
poctools.Matrix(
|
||||
1,0,0,0,
|
||||
0,1,0,0,
|
||||
0,0,1,0)
|
||||
poctools.Matrix(
|
||||
1,0,0,
|
||||
0,1,0,
|
||||
0,0,1)
|
||||
poctools._dump(Object())
|
||||
try:
|
||||
poctools.do_op(None)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
1/0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
e1 = Edge().createLine((-.5,0.,0.),(.5,0.,0.))
|
||||
e2 = Edge().createArc3P((.5,0.,0.),(-.5,0.,0.),(0.,.5,0.))
|
||||
w1 = Wire().createWire((e1,e2))
|
||||
f1 = Face().createFace(w1)
|
||||
x = Edge().createCircle((0,.25,0), (0,0,1), (.1))
|
||||
f1 = Face().createFace((w1, x))
|
||||
#do_op(f1)
|
||||
Extrude(f1, (0.,0.,0.), (0.,0.,1.))
|
||||
Fillet(.05)
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ e1 = Edge().createCircle(center=(.25,0.,0.),normal=(-.25,0.,1.),radius = .25)
|
|||
e2 = Edge().createCircle(center=(.25,0.,.5),normal=(0.,.25,1.),radius = .5)
|
||||
v1 = Vertex(.25,0.,1.)
|
||||
|
||||
Loft((e1, e2, v1))
|
||||
Loft((e1, Wire.createWire((e2)), v1))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
Fudge = Xform().scale((1+1e-7, 1+1e-7, 1+1e-7))
|
||||
|
||||
e1 = Edge().createHelix(.5, 4, 1, 0)
|
||||
e2 = Edge().createCircle(center=(1.,0.,0.),normal=(0.,-1.,0.),radius = 0.2)
|
||||
f1 = Face().createFace(e2)
|
||||
|
||||
e1b = Edge().createHelix(.5, 4, 1, 15, True)
|
||||
with Translated((10,0,0)):
|
||||
Pipe(f1, Wire.createWire(e1b))
|
||||
|
||||
with Difference():
|
||||
Cylinder((0,0,-.5), (0,0,3.5), 1)
|
||||
Pipe(f1, e1)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
#!/usr/bin/env poc
|
||||
import geotools
|
||||
|
||||
with Translated((0,0,0)):
|
||||
Sphere((0,0,0), 12)
|
||||
|
||||
|
|
@ -10,7 +8,7 @@ with Translated((-20,0,0)):
|
|||
with Translated((20,0,0)), Filleted(1):
|
||||
Cylinder((0,0,-5), (0,0,5), 5)
|
||||
|
||||
with Translated((0,20,0)), Chamfered(1, lambda e: e.boundingBox().max.z < 0):
|
||||
with Translated((0,20,0)), Chamfered(2, lambda e: Bbox(e)[2] < 0):
|
||||
Cone((0,0,-5), (0,0,5), 5, 2)
|
||||
|
||||
with Translated((0,-20,0)):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
e = Edge().createEllipse(center=(0.,0.,0.),normal=(0.,0.,1.), rMajor = .5, rMinor=.2)
|
||||
f = Face().createFace(e)
|
||||
Revolve(f, (1.,0.,0.), (1.,1.,0.), pi/2.)
|
||||
Revolve(f, (1.,0.,0.), (1.,1.,0.), 90)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ with Difference():
|
|||
Cylinder((-100,0,0), (100,0,0), 25)
|
||||
Cylinder((0,-100,0), (0,100,0), 25)
|
||||
Cylinder((0,0,-100), (0,0,100), 25)
|
||||
Fillet(12, lambda e: e.boundingBox().min.z > 0)
|
||||
Fillet(12, lambda e: Bbox(e)[2] > 0)
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 117 KiB |
2
poc
2
poc
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python3
|
||||
#!/usr/bin/python2
|
||||
# -*- coding: utf-8 -*-
|
||||
# main module of 'poc' modeling program
|
||||
# Copyright © 2017 Jeff Epler <jepler@gmail.com>
|
||||
|
|
|
|||
392
poctools.py
392
poctools.py
|
|
@ -19,13 +19,68 @@
|
|||
import contextlib
|
||||
import __future__
|
||||
import itertools
|
||||
import geotools
|
||||
import math
|
||||
import occmodel
|
||||
import os
|
||||
import six
|
||||
import struct
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import OCC.BRepAlgo
|
||||
import OCC.BRepAlgoAPI
|
||||
import OCC.BRepBuilderAPI
|
||||
import OCC.BRepBndLib
|
||||
import OCC.BRepFilletAPI
|
||||
import OCC.BRepGProp
|
||||
import OCC.BRepLib
|
||||
import OCC.BRepOffsetAPI
|
||||
import OCC.BRepPrimAPI
|
||||
import OCC.BRepTools
|
||||
import OCC.Bnd
|
||||
import OCC.GC
|
||||
import OCC.GCE2d
|
||||
import OCC.Geom
|
||||
import OCC.Geom2d
|
||||
import OCC.gp
|
||||
import OCC.GProp
|
||||
import OCC.StlAPI
|
||||
import OCC.TopAbs
|
||||
import OCC.TopExp
|
||||
import OCC.TopTools
|
||||
import OCC.TopoDS
|
||||
|
||||
def _dir(x):
|
||||
if isinstance(x, (tuple, list)):
|
||||
return OCC.gp.gp_Dir(*x)
|
||||
return x
|
||||
|
||||
def _vec(x):
|
||||
if isinstance(x, (tuple, list)):
|
||||
return OCC.gp.gp_Vec(*x)
|
||||
return x
|
||||
|
||||
def _pt(x):
|
||||
if isinstance(x, (tuple, list)):
|
||||
return OCC.gp.gp_Pnt(*x)
|
||||
return x
|
||||
|
||||
def _axpt(p1, p2):
|
||||
p1 = _pt(p1)
|
||||
p2 = _pt(p2)
|
||||
dx = p2.X() - p1.X()
|
||||
dy = p2.Y() - p1.Y()
|
||||
dz = p2.Z() - p1.Z()
|
||||
length = (dx*dx + dy*dy + dz*dz) ** .5
|
||||
return OCC.gp.gp_Ax2(p1, OCC.gp.gp_Dir(dx/length, dy/length, dz/length))
|
||||
|
||||
def _axcn(p, n):
|
||||
p = _pt(p)
|
||||
n = _pt(n)
|
||||
dx = n.X()
|
||||
dy = n.Y()
|
||||
dz = n.Z()
|
||||
length = (dx*dx + dy*dy + dz*dz) ** .5
|
||||
return OCC.gp.gp_Ax2(p, OCC.gp.gp_Dir(dx/length, dy/length, dz/length))
|
||||
|
||||
__all__ = [
|
||||
'Box', 'Cylinder', 'Cone', 'Sphere', 'Text', 'Torus',
|
||||
|
|
@ -35,6 +90,7 @@ __all__ = [
|
|||
'Intersection', 'Difference', 'Union', 'Op',
|
||||
'Object', 'Bbox', 'CenterOfMass', 'CentreOfMass',
|
||||
'Edges', 'Faces', 'Vertices', 'Wires',
|
||||
'Matrix', 'Vertex', 'Edge', 'Wire', 'Face',
|
||||
'execpoc', 'occ_to_stl', 'do_op',
|
||||
]
|
||||
|
||||
|
|
@ -48,15 +104,16 @@ def initial_ns():
|
|||
}
|
||||
six.exec_("""if 1:
|
||||
from math import *
|
||||
from geotools import *
|
||||
Xform = Transform
|
||||
from occmodel import *
|
||||
from poctools import *
|
||||
""", ns)
|
||||
return ns
|
||||
|
||||
compile_flags = (__future__.division.compiler_flag
|
||||
| __future__.print_function.compiler_flag)
|
||||
|
||||
def getsource(filename):
|
||||
with open(filename, "rU") as f: return f.read()
|
||||
with open(filename, "rU") as f:
|
||||
return f.read()
|
||||
|
||||
def execpoc(args, **kw):
|
||||
"""Execute the named .poc file from disk
|
||||
|
|
@ -66,7 +123,7 @@ Returns the resulting top level object"""
|
|||
oldargv = sys.argv[:]
|
||||
try:
|
||||
filename = args[0]
|
||||
code = compile(getsource(filename), filename, 'exec')
|
||||
code = compile(getsource(filename), filename, 'exec', compile_flags)
|
||||
sys.argv[:] = args
|
||||
ns = initial_ns()
|
||||
ns['__file__'] = filename
|
||||
|
|
@ -78,67 +135,59 @@ Returns the resulting top level object"""
|
|||
|
||||
def do_op(b):
|
||||
"""Adds the object 'b' to the current operation"""
|
||||
if b is None: raise ValueError
|
||||
if b is None:
|
||||
raise ValueError
|
||||
n = next(op)
|
||||
n(obj, b)
|
||||
|
||||
def _assign(a, b):
|
||||
return a.copyFrom(b)
|
||||
global obj
|
||||
obj = b
|
||||
|
||||
def _fuse(a, b):
|
||||
return a.fuse(b)
|
||||
global obj
|
||||
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Fuse(a, b).Shape()
|
||||
|
||||
def _common(a, b):
|
||||
return a.common(b)
|
||||
global obj
|
||||
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Common(a, b).Shape()
|
||||
|
||||
def _cut(a, b):
|
||||
return a.cut(b)
|
||||
global obj
|
||||
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Cut(a, b).Shape()
|
||||
|
||||
def op1(x):
|
||||
return iter(itertools.chain([_assign], itertools.repeat(x)))
|
||||
|
||||
def start():
|
||||
global obj, op
|
||||
obj = occmodel.Solid()
|
||||
obj = OCC.TopoDS.TopoDS_Shape()
|
||||
op = op1(_fuse)
|
||||
|
||||
def output(fn):
|
||||
with open(fn + ".tmp", "wb") as f: occ_to_stl(obj, f)
|
||||
os.rename(fn + ".tmp", fn)
|
||||
occ_to_stl(obj, fn)
|
||||
|
||||
def mesh_to_stl(m, dest):
|
||||
dest.write(b"\0" * 80)
|
||||
dest.write(struct.pack("<i", m.ntriangles()))
|
||||
|
||||
n0 = struct.pack("<fff", 0., 0., 0.)
|
||||
for i in range(0, m.ntriangles()*3, 3):
|
||||
dest.write(n0)
|
||||
dest.write(struct.pack("<fff", *m.vertex(m.triangles[i])))
|
||||
dest.write(struct.pack("<fff", *m.vertex(m.triangles[i+1])))
|
||||
dest.write(struct.pack("<fff", *m.vertex(m.triangles[i+2])))
|
||||
dest.write(b'\0\0')
|
||||
|
||||
def occ_to_stl(o, dest, prec=.001):
|
||||
"""Convert a mesh or solid to stl
|
||||
|
||||
Writes to the open file object 'dest'
|
||||
|
||||
If a solid is passed it, it is converted to a mesh with the given
|
||||
precision, defaulting to .001."""
|
||||
if isinstance(o, occmodel.Mesh): mesh_to_stl(o, dest)
|
||||
mesh_to_stl(o.createMesh(prec), dest)
|
||||
def occ_to_stl(obj, filename, prec=.05):
|
||||
"""Convert a solid to stl"""
|
||||
w = OCC.StlAPI.StlAPI_Writer()
|
||||
w.SetASCIIMode(False)
|
||||
w.SetDeflection(prec)
|
||||
w.SetRelativeMode(False)
|
||||
w.Write(obj, filename + ".tmp", True)
|
||||
os.rename(filename + ".tmp", filename)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def withhelper(newop, newobj=None, finalop=None):
|
||||
global obj, op
|
||||
holdobj = obj
|
||||
holdop = op
|
||||
obj = newobj = newobj or occmodel.Solid()
|
||||
obj = newobj = newobj or OCC.TopoDS.TopoDS_Shape()
|
||||
op = iter(newop)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if finalop: finalop()
|
||||
newobj = obj
|
||||
obj = holdobj
|
||||
op = holdop
|
||||
do_op(newobj)
|
||||
|
|
@ -147,58 +196,89 @@ def withhelper(newop, newobj=None, finalop=None):
|
|||
|
||||
def Box(p1, p2):
|
||||
"""Create a box primitive"""
|
||||
do_op(occmodel.Solid().createBox(p1, p2))
|
||||
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakeBox(_pt(p1), _pt(p2)).Shape())
|
||||
|
||||
def Cylinder(p1, p2, radius):
|
||||
"""Create a cylinder primitive"""
|
||||
do_op(occmodel.Solid().createCylinder(p1, p2, radius))
|
||||
p1 = _pt(p1)
|
||||
p2 = _pt(p2)
|
||||
dx = p2.X() - p1.X()
|
||||
dy = p2.Y() - p1.Y()
|
||||
dz = p2.Z() - p1.Z()
|
||||
length = (dx*dx + dy*dy + dz*dz) ** .5
|
||||
ax = OCC.gp.gp_Ax2(p1, OCC.gp.gp_Dir(dx/length, dy/length, dz/length))
|
||||
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakeCylinder(ax, radius, length).Shape())
|
||||
|
||||
def Cone(p1, p2, radius1, radius2):
|
||||
"""Create a cone primitive"""
|
||||
do_op(occmodel.Solid().createCone(p1, p2, radius1, radius2))
|
||||
p1 = _pt(p1)
|
||||
p2 = _pt(p2)
|
||||
dx = p2.X() - p1.X()
|
||||
dy = p2.Y() - p1.Y()
|
||||
dz = p2.Z() - p1.Z()
|
||||
length = (dx*dx + dy*dy + dz*dz) ** .5
|
||||
ax = OCC.gp.gp_Ax2(p1, OCC.gp.gp_Dir(dx/length, dy/length, dz/length))
|
||||
builder = OCC.BRepPrimAPI.BRepPrimAPI_MakeCone(
|
||||
ax, radius1, radius2, length)
|
||||
shape = builder.Shape()
|
||||
do_op(shape)
|
||||
|
||||
def Sphere(center, radius):
|
||||
"""Create a sphere primitive"""
|
||||
e1 = occmodel.Edge().createArc((1,0,0), (0,1,0), (0,0,0))
|
||||
e2 = occmodel.Edge().createLine((0,1,0), (0,0,0))
|
||||
e3 = occmodel.Edge().createLine((0,0,0), (1,0,0))
|
||||
w1 = occmodel.Wire().createWire((e1, e2, e3))
|
||||
f1 = occmodel.Face().createFace(w1)
|
||||
|
||||
o = occmodel.Solid()
|
||||
o.revolve(f1, (0,0,0), (1,0,0), 2*math.pi)
|
||||
p = o.copy().mirror(geotools.Plane.fromNormal((0,0,0), (1,0,0)))
|
||||
o.fuse(p)
|
||||
o.scale((0,0,0), radius)
|
||||
o.translate(center)
|
||||
do_op(o)
|
||||
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakeSphere(_pt(center), radius).Shape())
|
||||
|
||||
def Text(height, depth, text, fontpath=None):
|
||||
"""Create extruded text
|
||||
"""TODO Create extruded text
|
||||
|
||||
Note that the text may not contain whitespace!
|
||||
(this appears to be a bug in occmodel, failing with occmodel.OCCError:
|
||||
b'failed to create edges')"""
|
||||
do_op(occmodel.Solid().createText(height, depth, text, fontpath))
|
||||
return Box((0,0,0), (1,1,1))
|
||||
|
||||
def Torus(p1, p2, ringRadius, radius):
|
||||
"""Create a torus"""
|
||||
do_op(occmodel.Solid().createTorus(p1, p2, ringRadius, radius))
|
||||
axis = _axpt(p1, p2)
|
||||
builder = OCC.BRepPrimAPI.BRepPrimAPI_MakeTorus(axis, ringRadius, radius)
|
||||
do_op(builder.Shape())
|
||||
|
||||
def Extrude(obj, p1, p2):
|
||||
"""Create a solid by extruding edge, wire, or face from p1 to p2"""
|
||||
do_op(occmodel.Solid().extrude(obj, p1, p2))
|
||||
p1 = _pt(p1)
|
||||
p2 = _pt(p2)
|
||||
direction = OCC.gp.gp_Vec(p1, p2)
|
||||
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakePrism(obj, direction).Shape())
|
||||
|
||||
def Revolve(face, p1, p2, angle):
|
||||
"""Create a solid by revolving the face around the given axis"""
|
||||
do_op(occmodel.Solid().revolve(face, p1, p2, angle))
|
||||
p1 = _pt(p1)
|
||||
p2 = _pt(p2)
|
||||
dx = p2.X() - p1.X()
|
||||
dy = p2.Y() - p1.Y()
|
||||
dz = p2.Z() - p1.Z()
|
||||
axis = OCC.gp.gp_Ax1(p1, _dir((dx, dy, dz)))
|
||||
angle = math.radians(angle)
|
||||
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakeRevol(face, axis, angle, False).Shape())
|
||||
|
||||
def Loft(profiles, ruled=True, tolerance=1e-6):
|
||||
"""Create a solid by lofting through a sequence of wires or closed edges"""
|
||||
do_op(occmodel.Solid().loft(profiles, ruled, tolerance))
|
||||
builder = OCC.BRepOffsetAPI.BRepOffsetAPI_ThruSections(True, ruled,
|
||||
tolerance)
|
||||
for i in profiles:
|
||||
if isinstance(i, OCC.TopoDS.TopoDS_Wire):
|
||||
builder.AddWire(i)
|
||||
elif isinstance(i, OCC.TopoDS.TopoDS_Vertex):
|
||||
builder.AddVertex(i)
|
||||
else:
|
||||
builder.AddWire(Wire.createWire(i))
|
||||
do_op(builder.Shape())
|
||||
|
||||
def Pipe(face, path):
|
||||
do_op(occmodel.Solid().pipe(face, path))
|
||||
if isinstance(path, OCC.TopoDS.TopoDS_Edge):
|
||||
wire = Wire.createWire((path,))
|
||||
else:
|
||||
wire = path
|
||||
builder = OCC.BRepOffsetAPI.BRepOffsetAPI_MakePipe(wire, face)
|
||||
do_op(builder.Shape())
|
||||
|
||||
### Group operations
|
||||
|
||||
|
|
@ -256,19 +336,51 @@ def Chamfered(distance, edges=None):
|
|||
|
||||
### Postfix operations
|
||||
|
||||
def _transform(obj, t):
|
||||
_assign(obj,
|
||||
OCC.BRepBuilderAPI.BRepBuilderAPI_Transform(obj, t, True).Shape())
|
||||
|
||||
def Rotate(angle, axis, center=(0,0,0)):
|
||||
"""Rotate the active object"""
|
||||
obj.rotate(angle, axis, center)
|
||||
angle = math.radians(angle)
|
||||
a = OCC.gp.gp_Ax1()
|
||||
a.SetLocation(_pt(center))
|
||||
a.SetDirection(_dir(axis))
|
||||
t = OCC.gp.gp_Trsf()
|
||||
t.SetRotation(a, angle)
|
||||
_transform(obj, t)
|
||||
|
||||
def Translate(delta):
|
||||
"""Translate the active object"""
|
||||
obj.translate(delta)
|
||||
t = OCC.gp.gp_Trsf()
|
||||
t.SetTranslation(_vec(delta))
|
||||
_transform(obj, t)
|
||||
|
||||
def Matrix(*args):
|
||||
result = OCC.gp.gp_Trsf()
|
||||
if len(args) == 0:
|
||||
return result
|
||||
if len(args) == 3:
|
||||
if len(args[0]) == 4:
|
||||
result.SetValues(*(args[0] + args[1] + args[2]))
|
||||
return result
|
||||
elif len(args[0]) == 3:
|
||||
result.SetValues(*(args[0] + (0,) + args[1] + (0,) + args[2] + (0,)))
|
||||
return result
|
||||
elif len(args) == 9:
|
||||
result.SetValues(
|
||||
args[0], args[1], args[2], 0,
|
||||
args[3], args[4], args[5], 0,
|
||||
args[6], args[7], args[8], 0)
|
||||
return result
|
||||
result.SetValues(*args)
|
||||
return result
|
||||
|
||||
def Transform(mat):
|
||||
"""Transform the active object
|
||||
|
||||
Note that `geotools.Transform` is imported as `Xform` within poc files."""
|
||||
obj.transform(mat)
|
||||
_transform(obj, mat)
|
||||
|
||||
def Fillet(radius, edges=None):
|
||||
"""Fillet the active object
|
||||
|
|
@ -280,8 +392,14 @@ True for each edge that should be filleted.
|
|||
|
||||
Otherwise, `edges` must be a sequence of edges to fillet.
|
||||
"""
|
||||
if callable(edges): edges = [e for e in Edges() if edges(e)]
|
||||
obj.fillet(radius, edges)
|
||||
if callable(edges):
|
||||
edges = [e for e in Edges() if edges(e)]
|
||||
elif edges is None:
|
||||
edges = [e for e in Edges()]
|
||||
fillet = OCC.BRepFilletAPI.BRepFilletAPI_MakeFillet(obj)
|
||||
for e in edges:
|
||||
fillet.Add(radius, e)
|
||||
_assign(obj, fillet.Shape())
|
||||
|
||||
def Chamfer(distance, edges=None):
|
||||
"""Chamfer the active object
|
||||
|
|
@ -293,35 +411,159 @@ True for each edge that should be filleted.
|
|||
|
||||
Otherwise, `edges` must be a sequence of edges to fillet.
|
||||
"""
|
||||
if callable(edges): edges = [e for e in Edges() if edges(e)]
|
||||
obj.chamfer(distance, edges)
|
||||
if callable(edges):
|
||||
edges = [e for e in Edges() if edges(e)]
|
||||
elif edges is None:
|
||||
edges = [e for e in Edges()]
|
||||
chamfer = OCC.BRepFilletAPI.BRepFilletAPI_MakeChamfer(obj)
|
||||
m = OCC.TopTools.TopTools_IndexedDataMapOfShapeListOfShape();
|
||||
OCC.TopExp.topexp.MapShapesAndAncestors(obj, OCC.TopAbs.TopAbs_EDGE,
|
||||
OCC.TopAbs.TopAbs_FACE, m)
|
||||
|
||||
for e in edges:
|
||||
f = m.FindFromKey(e).First()
|
||||
f = OCC.TopoDS.topods.Face(f)
|
||||
chamfer.Add(distance, e, f)
|
||||
_assign(obj, chamfer.Shape())
|
||||
|
||||
### Inquiries
|
||||
|
||||
def visit(shape, topologyType, factory):
|
||||
explorer = OCC.TopExp.TopExp_Explorer()
|
||||
explorer.Init(shape, topologyType)
|
||||
while explorer.More():
|
||||
it = explorer.Current()
|
||||
# XXX pythonocc-core examples _loop_topo avoids yielding
|
||||
# items with equal _hash__ more than once but this seems bogus
|
||||
yield factory(it)
|
||||
explorer.Next()
|
||||
|
||||
def Object():
|
||||
return obj
|
||||
|
||||
def CenterOfMass():
|
||||
"""Return the bounding box of the current item"""
|
||||
return obj.centreOfMass()
|
||||
prop = OCC.GProp.GProp_GProps()
|
||||
OCC.BRepGProp.brepgprop_VolumeProperties(obj, prop)
|
||||
return prop.CentreOfMass()
|
||||
CentreOfMass = CenterOfMass
|
||||
|
||||
def Bbox():
|
||||
"""Return the bounding box of the current item"""
|
||||
return obj.boundingBox()
|
||||
def Bbox(o=None):
|
||||
"""Return the bounding box of given object or the current item a a 6-tuple
|
||||
|
||||
(minx, miny, minz, maxx, maxy, maxz)"""
|
||||
box = OCC.Bnd.Bnd_Box()
|
||||
OCC.BRepBndLib.brepbndlib.Add(o or obj, box)
|
||||
lo = box.CornerMin()
|
||||
hi = box.CornerMax()
|
||||
return ((lo.X(), lo.Y(), lo.Z(), hi.X(), hi.Y(), hi.Z()))
|
||||
|
||||
def Edges():
|
||||
"""Return the edge iterator of the current item"""
|
||||
return occmodel.EdgeIterator(Object())
|
||||
return visit(Object(), OCC.TopAbs.TopAbs_EDGE, OCC.TopoDS.topods.Edge)
|
||||
|
||||
def Faces():
|
||||
"""Return the face iterator of the current item"""
|
||||
return occmodel.FaceIterator(Object())
|
||||
return visit(Object(), OCC.TopAbs.TopAbs_FACE, OCC.TopoDS.topods.Face)
|
||||
|
||||
def Vertices():
|
||||
"""Return the vertex iterator of the current item"""
|
||||
return occmodel.VertexIterator(Object())
|
||||
return visit(Object(), OCC.TopAbs.TopAbs_VERTEX, OCC.TopoDS.topods.Vertex)
|
||||
|
||||
def Wires():
|
||||
"""Return the wire iterator of the current item"""
|
||||
return occmodel.WireIterator(Object())
|
||||
return visit(Object(), OCC.TopAbs.TopAbs_WIRE, OCC.TopoDS.topods.Wire)
|
||||
|
||||
class Edge:
|
||||
@classmethod
|
||||
def createLine(cls, p1, p2):
|
||||
p1 = _pt(p1)
|
||||
p2 = _pt(p2)
|
||||
return OCC.BRepBuilderAPI.BRepBuilderAPI_MakeEdge(p1, p2).Edge()
|
||||
|
||||
@classmethod
|
||||
def createArc3P(cls, start, end, mid):
|
||||
p1 = _pt(start)
|
||||
p2 = _pt(mid)
|
||||
p3 = _pt(end)
|
||||
arc = OCC.GC.GC_MakeArcOfCircle(p1, p2, p3).Value()
|
||||
return OCC.BRepBuilderAPI.BRepBuilderAPI_MakeEdge(arc).Edge()
|
||||
|
||||
@classmethod
|
||||
def createCircle(cls, center, normal, radius):
|
||||
ax = _axcn(center, normal)
|
||||
arc = OCC.GC.GC_MakeCircle(ax, radius).Value()
|
||||
return OCC.BRepBuilderAPI.BRepBuilderAPI_MakeEdge(arc).Edge()
|
||||
|
||||
@classmethod
|
||||
def createHelix(cls, pitch, height, radius, angle, leftHanded=False):
|
||||
axis = OCC.gp.gp_Ax2(_pt((0,0,0)), OCC.gp.gp.DZ())
|
||||
axis = OCC.gp.gp_Ax3(axis)
|
||||
if angle <= 0:
|
||||
surf = OCC.Geom.Geom_CylindricalSurface(axis, radius)
|
||||
else:
|
||||
angle = math.radians(angle)
|
||||
surf = OCC.Geom.Geom_ConicalSurface(axis, angle, radius)
|
||||
if leftHanded:
|
||||
p = OCC.gp.gp_Pnt2d(2*math.pi,0)
|
||||
d = OCC.gp.gp_Dir2d(-2 * math.pi, pitch)
|
||||
else:
|
||||
p = OCC.gp.gp_Pnt2d(0,0)
|
||||
d = OCC.gp.gp_Dir2d(2 * math.pi, pitch)
|
||||
|
||||
axis2 = OCC.gp.gp_Ax2d(p, d)
|
||||
line = OCC.Geom2d.Geom2d_Line(axis2)
|
||||
end_u = (4*math.pi*math.pi+pitch*pitch)**.5*(height/pitch)
|
||||
begin = line.Value(0)
|
||||
end = line.Value(end_u)
|
||||
|
||||
seg = OCC.GCE2d.GCE2d_MakeSegment(begin, end).Value()
|
||||
edge = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeEdge(seg,
|
||||
OCC.Geom.Handle_Geom_Surface(surf)).Edge()
|
||||
OCC.BRepLib.breplib.BuildCurves3d(edge)
|
||||
return edge
|
||||
|
||||
@classmethod
|
||||
def createEllipse(cls, center, normal, rMajor, rMinor):
|
||||
ax = _axcn(center, normal)
|
||||
arc = OCC.GC.GC_MakeEllipse(ax, rMajor, rMinor).Value()
|
||||
return OCC.BRepBuilderAPI.BRepBuilderAPI_MakeEdge(arc).Edge()
|
||||
|
||||
class Wire:
|
||||
@classmethod
|
||||
def createWire(cls, arg):
|
||||
if isinstance(arg, (list, tuple)): pass
|
||||
else: arg = (arg,)
|
||||
builder = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeWire()
|
||||
for i in arg:
|
||||
builder.Add(i)
|
||||
return builder.Wire()
|
||||
|
||||
def Vertex(x,y,z):
|
||||
builder = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeVertex(_pt((x,y,z)))
|
||||
return builder.Vertex()
|
||||
|
||||
def _dump(obj):
|
||||
import OCC.BRepTools
|
||||
OCC.BRepTools.breptools.Write(obj, "/dev/stdout")
|
||||
|
||||
class Face:
|
||||
@classmethod
|
||||
def createFace(cls, arg):
|
||||
def f(x):
|
||||
if isinstance(x, OCC.TopoDS.TopoDS_Edge):
|
||||
return Wire.createWire(x)
|
||||
else:
|
||||
return x
|
||||
if isinstance(arg, (list, tuple)):
|
||||
pass
|
||||
else:
|
||||
arg = (arg,)
|
||||
outer = arg[0]
|
||||
rest = arg[1:]
|
||||
outer = f(outer)
|
||||
builder = OCC.BRepBuilderAPI.BRepBuilderAPI_MakeFace(outer)
|
||||
for i in rest:
|
||||
i = f(i)
|
||||
builder.Add(i)
|
||||
return builder.Face()
|
||||
|
|
|
|||
67
pocview
67
pocview
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python3
|
||||
#!/usr/bin/python2
|
||||
# -*- coding: utf-8 -*-
|
||||
# auto-updating viewer of 'poc' modeling program
|
||||
# Copyright © 2017 Jeff Epler <jepler@gmail.com>
|
||||
|
|
@ -17,13 +17,12 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from occmodelviewer import Viewer
|
||||
import gltools
|
||||
import geotools
|
||||
import os
|
||||
import poctools
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import vtk
|
||||
|
||||
filename = sys.argv[1]
|
||||
|
||||
|
|
@ -35,46 +34,48 @@ def getmtime(filename):
|
|||
|
||||
class PocViewer(Viewer):
|
||||
def __init__(self, filename):
|
||||
title = "poc viewer - 'r' to reload, 'q' to quit"
|
||||
Viewer.__init__(self, 640, 480, title, fullscreen=False)
|
||||
|
||||
self.defaultColor = gltools.ColorRGBA(100,100,100,255)
|
||||
self.edgeColor = gltools.ColorRGBA(255,255,255,255)
|
||||
self.filename = filename
|
||||
self.reloadModel()
|
||||
self.modtime = -2
|
||||
self.actor = vtk.vtkActor()
|
||||
self.ren = vtk.vtkRenderer()
|
||||
self.renWin = vtk.vtkRenderWindow()
|
||||
self.renWin.AddRenderer(self.ren)
|
||||
|
||||
def onChar(self, ch):
|
||||
if ch == 'r': self.reloadModel()
|
||||
if ch == 'q': self.running = False
|
||||
self.iren = vtk.vtkRenderWindowInteractor()
|
||||
self.iren.SetRenderWindow(self.renWin)
|
||||
|
||||
self.ren.AddActor(self.actor)
|
||||
self.iren.Initialize()
|
||||
self.iren.CreateRepeatingTimer(100)
|
||||
self.iren.AddObserver('TimerEvent', self.idle)
|
||||
|
||||
def Start(self):
|
||||
self.reloadModel()
|
||||
self.iren.Start()
|
||||
|
||||
def reloadModel(self, modtime=None):
|
||||
self.modtime = modtime or getmtime(self.filename)
|
||||
try:
|
||||
poctools.execpoc(sys.argv[1:])
|
||||
ns = poctools.execpoc(sys.argv[1:],
|
||||
__output__= os.path.splitext(filename)[0] + ".stl")
|
||||
poctools.output(ns['__output__'])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
self.clear()
|
||||
o = poctools.obj
|
||||
bb = o.boundingBox()
|
||||
if bb.min != bb.max:
|
||||
self.add(o)
|
||||
reader = vtk.vtkSTLReader()
|
||||
reader.SetFileName(ns['__output__'])
|
||||
|
||||
# Viewer.mainLoop seems to have no provision for code to run when
|
||||
# idle, so we are forced to do this..
|
||||
def mainLoop(self):
|
||||
while self.running:
|
||||
time.sleep(1/120.)
|
||||
gltools.PollEvents()
|
||||
newmodtime = getmtime(self.filename)
|
||||
now = time.time()
|
||||
if newmodtime + .1 > now and newmodtime != self.modtime:
|
||||
self.reloadModel(newmodtime)
|
||||
self.updateBounds()
|
||||
self.redraw()
|
||||
self.mapper = vtk.vtkPolyDataMapper()
|
||||
self.mapper.SetInputConnection(reader.GetOutputPort())
|
||||
|
||||
self.actor.SetMapper(self.mapper)
|
||||
|
||||
def idle(self, obj, event):
|
||||
newmodtime = getmtime(self.filename)
|
||||
now = time.time()
|
||||
if newmodtime + .1 < now and newmodtime != self.modtime:
|
||||
self.reloadModel(newmodtime)
|
||||
self.renWin.Render()
|
||||
mw = PocViewer(filename)
|
||||
mw.running = True
|
||||
mw.onIsoView()
|
||||
mw.mainLoop()
|
||||
mw.Start()
|
||||
|
|
|
|||
Loading…
Reference in a new issue