Compare commits

..

1 commit

Author SHA1 Message Date
Jeff Epler
837e02328f Revert "compiler flags are no longer needed"
This reverts commit bc6146ef0e.
2017-08-09 21:04:30 -05:00
24 changed files with 157 additions and 600 deletions

1
.gitignore vendored
View file

@ -1,5 +1,4 @@
examples/*.stl examples/*.stl
examples/*.png
.coverage .coverage
*.py[oc] *.py[oc]
build build

View file

@ -5,33 +5,30 @@
*poc* is a tool in the vein of *OpenSCAD* for creating 3D models in a high *poc* is a tool in the vein of *OpenSCAD* for creating 3D models in a high
level language with a minimum of boilerplate. level language with a minimum of boilerplate.
*poc* programs are Python2 programs, executed in an environment that *poc* programs are Python3 programs, executed in an environment that
provides convenient shorthand for performing geometric operations. provides convenient shorthand for performing geometric operations.
Python2 is used instead of Python3 because a python3 compatible version of *poc* uses OpenCASCADE (via occmodel) to implement its geometric operations.
vtk is not availble in debian stretch. However, the python3-like features of This means it has different strengths and weaknesses compared to *OpenSCAD*,
`print_function` and `division` are automatically enabled. which uses CGAL. For instance, OpenCASCADE has `fillet` as a first-class
operation, while it lacks `minkowski` and `hull` which are quite frequently
*poc* uses OpenCASCADE (via pythonocc-core) to implement its geometric used in *OpenSCAD*.
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*.
![pocview showing selective fillet of CSG object](/images/selective_fillet.png) ![pocview showing selective fillet of CSG object](/images/selective_fillet.png)
# Setup # Setup
* Install dependencies * Install dependencies
* Run setup, e.g., `sudo python setup.py install` * 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. * 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.
* Invoke `poc somefile.poc` to create `somefile.stl` * Invoke `poc somefile.poc` to create `somefile.stl`
* or use `#!/usr/bin/env poc` so that `./somefile.poc` is executable * or use `#!/usr/bin/env poc` so that `./somefile.poc` is executable
# Dependencies # Dependencies
* [OpenCASCADE Community Edition (OCE)](https://github.com/tpaviot/oce) * [OpenCASCADE Community Edition (OCE)](https://github.com/tpaviot/oce/pulls)
* pythonocc-core * [occmodel](https://github.com/tenko/occmodel)
* python-vtk6 * [geotools](https://github.com/tenko/geotools)
* [gltools](https://github.com/tenko/gltools)
# Stability # Stability

View file

@ -1,11 +1,7 @@
#!/bin/bash #!/bin/sh
set -eo pipefail python3-coverage run ./poc || true
python-coverage erase for i in examples/*.poc; do
python-coverage run ./poc || true echo $i
find examples -name \*.poc -print0 | xargs -0n1 -P`getconf _NPROCESSORS_ONLN` python-coverage run -p ./poc python3-coverage run -a ./poc $i || exit $?
#for i in examples/*.poc; do done
# echo $i python3-coverage report -m
# python-coverage run -a ./poc $i || exit $?
#done
python-coverage combine -a .coverage.*
python-coverage report -m --include poctools.py

View file

@ -1,6 +0,0 @@
#!/bin/bash
if [ -z "${DISPLAY-}" ]; then
exec xvfb-run -s "-screen 0 640x480x24" bash "$0"
fi
find examples -name "*.poc" -print0 | xargs -0 -n1 -P`getconf _NPROCESSORS_ONLN` ./pocimg

View file

@ -3,7 +3,7 @@
# You can set these variables from the command line. # You can set these variables from the command line.
SPHINXOPTS = SPHINXOPTS =
SPHINXBUILD = sphinx-build SPHINXBUILD = sphinx3-build
PAPER = PAPER =
BUILDDIR = _build BUILDDIR = _build

View file

@ -18,34 +18,8 @@
# #
import os import os
import sys import sys
import mock
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
for s in ''' sys.modules['occmodel'] = object()
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 ------------------------------------------------ # -- General configuration ------------------------------------------------

View file

@ -1,11 +1,13 @@
poc environment poc environment
=============== ===============
The poc environment is just a Python (2.x) interpreter with some extras The poc environment is just a Python (3.x) interpreter with some extras
preloaded:: preloaded::
from __future__ import division, print_function
from math import * from math import *
from geotools import *
Xform = Transform
from occmodel import *
from poctools import * from poctools import *

View file

@ -4,12 +4,9 @@ Examples
Selective fillet Selective fillet
---------------- ----------------
.. literalinclude:: ../examples/selective_fillet.poc .. include:: ../examples/selective_fillet.poc
:language: python :code: python
:linenos:
.. image:: ../images/selective_fillet.png .. image:: ../images/selective_fillet.png
:alt: The result of rendering selective_fillet.poc. All edges entirely above the centerline of the object are filleted.
:width: 75%
For more, see the 'examples' directory in the source distribution. For more, see the 'examples' directory in the source distribution.

View file

@ -9,10 +9,10 @@ Python OCE Composer (poc)
*poc* is a tool in the vein of *OpenSCAD* for creating 3D models in a high *poc* is a tool in the vein of *OpenSCAD* for creating 3D models in a high
level language with a minimum of boilerplate. level language with a minimum of boilerplate.
*poc* programs are Python2 programs, executed in an environment that *poc* programs are Python3 programs, executed in an environment that
provides convenient shorthand for performing geometric operations. provides convenient shorthand for performing geometric operations.
*poc* uses OpenCASCADE to implement its geometric operations. *poc* uses OpenCASCADE (via occmodel) to implement its geometric operations.
This means it has different strengths and weaknesses compared to *OpenSCAD*, This means it has different strengths and weaknesses compared to *OpenSCAD*,
which uses CGAL. For instance, OpenCASCADE has `fillet` as a first-class which uses CGAL. For instance, OpenCASCADE has `fillet` as a first-class
operation, while it lacks `minkowski` and `hull` which are quite frequently operation, while it lacks `minkowski` and `hull` which are quite frequently
@ -21,7 +21,7 @@ used in *OpenSCAD*.
The the design of the *poc* standard library is very much in flux, and 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. there are likely to be compatibility-breaking changes as it develops.
For installation and setup instructions, see README.md. Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2

View file

@ -3,8 +3,8 @@ poctools module
.. automodule:: poctools .. automodule:: poctools
Solid Primitives Primitives
~~~~~~~~~~~~~~~~ ~~~~~~~~~~
Each primitive is added to the currently active group operation. Each primitive is added to the currently active group operation.
At the outermost level, the active group operation is union(). At the outermost level, the active group operation is union().
@ -12,35 +12,13 @@ At the outermost level, the active group operation is union().
.. autofunction:: Cylinder .. autofunction:: Cylinder
.. autofunction:: Cone .. autofunction:: Cone
.. autofunction:: Sphere .. autofunction:: Sphere
.. autofunction:: Text
.. autofunction:: Torus .. autofunction:: Torus
.. autofunction:: Extrude .. autofunction:: Extrude
.. autofunction:: Loft .. autofunction:: Loft
.. autofunction:: Pipe .. autofunction:: Pipe
.. autofunction:: Revolve .. 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
:members:
:undoc-members:
.. autoclass:: Face
:members:
:undoc-members:
.. autoclass:: Wire
:members:
:undoc-members:
.. autofunction:: Vertex
.. autofunction:: Matrix
Postfix operations Postfix operations
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
A postfix operation modifies the currently active group operation A postfix operation modifies the currently active group operation

View file

@ -16,15 +16,3 @@ Execute *input.poc* and show the result onscreen. When *input.poc* is
modified, **pocview** updates the preview. modified, **pocview** updates the preview.
Program: pocimg
----------------
Usage: **pocimg** *input.poc* *optional-args...*
Execute *input.poc* and render an image of it to *input.png. On
Linux, needs an X server; you can use `xvfb` for this purpose if you
need to run in a headless fashion::
xvfb-run -s "-screen 0 640x480x24" pocimg ...

View file

@ -1,8 +1,9 @@
# This example isn't useful, except that it increases code coverage # This example isn't useful, except that it increases code coverage
import geotools
with Union(): with Union():
with Rotated(1, (0,0,1)): with Rotated(1, (0,0,1)):
Sphere((0,0,0), 6) Sphere((0,0,0), 6)
with Filleted(.5), Transformed(Matrix()), Chamfered(2): with Filleted(2), Transformed(geotools.Transform()):
Box((-5,-5,-5), (5,5,5)) Box((-5,-5,-5), (5,5,5))
print(Bbox()) print(Bbox())
print(CenterOfMass()) print(CenterOfMass())
@ -11,31 +12,3 @@ with Union():
print(len(list(Wires()))) print(len(list(Wires())))
Text(8, .25, 'Hello_world') 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

View file

@ -1,8 +1,6 @@
e1 = Edge().createLine((-.5,0.,0.),(.5,0.,0.)) e1 = Edge().createLine((-.5,0.,0.),(.5,0.,0.))
e2 = Edge().createArc3P((.5,0.,0.),(-.5,0.,0.),(0.,.5,0.)) e2 = Edge().createArc3P((.5,0.,0.),(-.5,0.,0.),(0.,.5,0.))
w1 = Wire().createWire((e1,e2)) w1 = Wire().createWire((e1,e2))
x = Edge().createCircle((0,.25,0), (0,0,1), (.1)) f1 = Face().createFace(w1)
f1 = Face().createFace((w1, x))
#do_op(f1)
Extrude(f1, (0.,0.,0.), (0.,0.,1.)) Extrude(f1, (0.,0.,0.), (0.,0.,1.))
Fillet(.05) Fillet(.05)

View file

@ -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) e2 = Edge().createCircle(center=(.25,0.,.5),normal=(0.,.25,1.),radius = .5)
v1 = Vertex(.25,0.,1.) v1 = Vertex(.25,0.,1.)
Loft((e1, Wire.createWire((e2)), v1)) Loft((e1, e2, v1))

View file

@ -1,11 +1,8 @@
Fudge = Xform().scale((1+1e-7, 1+1e-7, 1+1e-7))
e1 = Edge().createHelix(.5, 4, 1, 0) e1 = Edge().createHelix(.5, 4, 1, 0)
e2 = Edge().createCircle(center=(1.,0.,0.),normal=(0.,-1.,0.),radius = 0.2) e2 = Edge().createCircle(center=(1.,0.,0.),normal=(0.,-1.,0.),radius = 0.2)
f1 = Face().createFace(e2) f1 = Face().createFace(e2)
e1b = Edge().createHelix(.5, 4, 1, 15, True)
with Translated((10,0,0)):
Pipe(f1, Wire.createWire(e1b))
with Difference(): with Difference():
Cylinder((0,0,-.5), (0,0,3.5), 1) Cylinder((0,0,-.5), (0,0,3.5), 1)
Pipe(f1, e1) Pipe(f1, e1)

View file

@ -1,4 +1,6 @@
#!/usr/bin/env poc #!/usr/bin/env poc
import geotools
with Translated((0,0,0)): with Translated((0,0,0)):
Sphere((0,0,0), 12) Sphere((0,0,0), 12)
@ -8,7 +10,7 @@ with Translated((-20,0,0)):
with Translated((20,0,0)), Filleted(1): with Translated((20,0,0)), Filleted(1):
Cylinder((0,0,-5), (0,0,5), 5) Cylinder((0,0,-5), (0,0,5), 5)
with Translated((0,20,0)), Chamfered(2, lambda e: Bbox(e)[2] < 0): with Translated((0,20,0)), Chamfered(1, lambda e: e.boundingBox().max.z < 0):
Cone((0,0,-5), (0,0,5), 5, 2) Cone((0,0,-5), (0,0,5), 5, 2)
with Translated((0,-20,0)): with Translated((0,-20,0)):

View file

@ -1,4 +1,4 @@
e = Edge().createEllipse(center=(0.,0.,0.),normal=(0.,0.,1.), rMajor = .5, rMinor=.2) e = Edge().createEllipse(center=(0.,0.,0.),normal=(0.,0.,1.), rMajor = .5, rMinor=.2)
f = Face().createFace(e) f = Face().createFace(e)
Revolve(f, (1.,0.,0.), (1.,1.,0.), 90) Revolve(f, (1.,0.,0.), (1.,1.,0.), pi/2.)

View file

@ -3,4 +3,4 @@ with Difference():
Cylinder((-100,0,0), (100,0,0), 25) Cylinder((-100,0,0), (100,0,0), 25)
Cylinder((0,-100,0), (0,100,0), 25) Cylinder((0,-100,0), (0,100,0), 25)
Cylinder((0,0,-100), (0,0,100), 25) Cylinder((0,0,-100), (0,0,100), 25)
Fillet(12, lambda e: Bbox(e)[2] > 0) Fillet(12, lambda e: e.boundingBox().min.z > 0)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 86 KiB

2
poc
View file

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# main module of 'poc' modeling program # main module of 'poc' modeling program
# Copyright © 2017 Jeff Epler <jepler@gmail.com> # Copyright © 2017 Jeff Epler <jepler@gmail.com>

83
pocimg
View file

@ -1,83 +0,0 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
# auto-updating viewer of 'poc' modeling program
# Copyright © 2017 Jeff Epler <jepler@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from occmodelviewer import Viewer
import os
import poctools
import sys
import time
import traceback
import vtk
filename = sys.argv[1]
with poctools.TemporaryDirectory() as d:
stlfile = os.path.join(d, "out.stl")
ns = poctools.execpoc(sys.argv[1:])
poctools.output(stlfile)
reader = vtk.vtkSTLReader()
reader.SetFileName(stlfile)
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(reader.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetBounds() # forces STL file to be read now
b = poctools.Bbox(poctools.Object())
diag = ((b[3] - b[0]) ** 2 + (b[4] - b[1]) ** 2 + (b[5] - b[2]) ** 2) ** .5
center = ((b[0] + b[3]) / 2, (b[4] + b[1]) / 2, (b[5]+ b[2]) / 2)
ren = vtk.vtkRenderer()
ren.AddActor(actor)
ren.SetBackground(0,0,.5)
ren.SetBackground2(.1,.1,.2)
ren.SetGradientBackground(True)
camera = vtk.vtkCamera()
camera.SetFocalPoint(center)
camera.SetViewUp((0,0,1))
camera.ParallelProjectionOff()
camera.Azimuth(-37.5)
camera.Elevation(30)
camera.SetPosition(1.4*diag, diag, diag)
ren.SetActiveCamera(camera)
ren.ResetCamera()
renWin = vtk.vtkRenderWindow()
renWin.OffScreenRenderingOn()
renWin.AddRenderer(ren)
writer = vtk.vtkPNGWriter()
renWin.Render()
w2if = vtk.vtkWindowToImageFilter()
w2if.SetInput(renWin)
w2if.ReadFrontBufferOff()
w2if.SetMagnification(3)
w2if.Update()
writer = vtk.vtkPNGWriter()
writer.SetFileName(os.path.splitext(filename)[0] + ".png")
writer.SetInputConnection(w2if.GetOutputPort())
writer.Write()

View file

@ -19,70 +19,13 @@
import contextlib import contextlib
import __future__ import __future__
import itertools import itertools
import geotools
import math import math
import occmodel
import os import os
import six import six
import shutil
import struct import struct
import sys import sys
import tempfile
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__ = [ __all__ = [
'Box', 'Cylinder', 'Cone', 'Sphere', 'Text', 'Torus', 'Box', 'Cylinder', 'Cone', 'Sphere', 'Text', 'Torus',
@ -92,7 +35,6 @@ __all__ = [
'Intersection', 'Difference', 'Union', 'Op', 'Intersection', 'Difference', 'Union', 'Op',
'Object', 'Bbox', 'CenterOfMass', 'CentreOfMass', 'Object', 'Bbox', 'CenterOfMass', 'CentreOfMass',
'Edges', 'Faces', 'Vertices', 'Wires', 'Edges', 'Faces', 'Vertices', 'Wires',
'Matrix', 'Vertex', 'Edge', 'Wire', 'Face',
'execpoc', 'occ_to_stl', 'do_op', 'execpoc', 'occ_to_stl', 'do_op',
] ]
@ -106,6 +48,9 @@ def initial_ns():
} }
six.exec_("""if 1: six.exec_("""if 1:
from math import * from math import *
from geotools import *
Xform = Transform
from occmodel import *
from poctools import * from poctools import *
""", ns) """, ns)
return ns return ns
@ -114,8 +59,7 @@ compile_flags = (__future__.division.compiler_flag
| __future__.print_function.compiler_flag) | __future__.print_function.compiler_flag)
def getsource(filename): def getsource(filename):
with open(filename, "rU") as f: with open(filename, "rU") as f: return f.read()
return f.read()
def execpoc(args, **kw): def execpoc(args, **kw):
"""Execute the named .poc file from disk """Execute the named .poc file from disk
@ -137,158 +81,127 @@ Returns the resulting top level object"""
def do_op(b): def do_op(b):
"""Adds the object 'b' to the current operation""" """Adds the object 'b' to the current operation"""
if b is None: if b is None: raise ValueError
raise ValueError
n = next(op) n = next(op)
n(obj, b) n(obj, b)
def _assign(a, b): def _assign(a, b):
global obj return a.copyFrom(b)
obj = b
def _fuse(a, b): def _fuse(a, b):
global obj return a.fuse(b)
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Fuse(a, b).Shape()
def _common(a, b): def _common(a, b):
global obj return a.common(b)
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Common(a, b).Shape()
def _cut(a, b): def _cut(a, b):
global obj return a.cut(b)
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Cut(a, b).Shape()
def op1(x): def op1(x):
return iter(itertools.chain([_assign], itertools.repeat(x))) return iter(itertools.chain([_assign], itertools.repeat(x)))
def start(): def start():
global obj, op global obj, op
obj = OCC.TopoDS.TopoDS_Shape() obj = occmodel.Solid()
op = op1(_fuse) op = op1(_fuse)
def output(fn): def output(fn):
occ_to_stl(obj, fn) with open(fn + ".tmp", "wb") as f: occ_to_stl(obj, f)
os.rename(fn + ".tmp", fn)
def occ_to_stl(obj, filename, prec=.05): def mesh_to_stl(m, dest):
"""Convert a solid to stl""" dest.write(b"\0" * 80)
w = OCC.StlAPI.StlAPI_Writer() dest.write(struct.pack("<i", m.ntriangles()))
w.SetASCIIMode(False)
w.SetDeflection(prec) n0 = struct.pack("<fff", 0., 0., 0.)
w.SetRelativeMode(False) for i in range(0, m.ntriangles()*3, 3):
w.Write(obj, filename + ".tmp", True) dest.write(n0)
os.rename(filename + ".tmp", filename) 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)
@contextlib.contextmanager @contextlib.contextmanager
def withhelper(newop, newobj=None, finalop=None): def withhelper(newop, newobj=None, finalop=None):
global obj, op global obj, op
holdobj = obj holdobj = obj
holdop = op holdop = op
obj = newobj = newobj or OCC.TopoDS.TopoDS_Shape() obj = newobj = newobj or occmodel.Solid()
op = iter(newop) op = iter(newop)
try: try:
yield yield
finally: finally:
if finalop: finalop() if finalop: finalop()
newobj = obj
obj = holdobj obj = holdobj
op = holdop op = holdop
do_op(newobj) do_op(newobj)
@contextlib.contextmanager
def TemporaryDirectory(*args):
d = tempfile.mkdtemp(*args)
try:
yield d
finally:
shutil.rmtree(d)
### Primitives ### Primitives
def Box(p1, p2): def Box(p1, p2):
"""Create a box primitive""" """Create a box primitive"""
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakeBox(_pt(p1), _pt(p2)).Shape()) do_op(occmodel.Solid().createBox(p1, p2))
def Cylinder(p1, p2, radius): def Cylinder(p1, p2, radius):
"""Create a cylinder primitive""" """Create a cylinder primitive"""
p1 = _pt(p1) do_op(occmodel.Solid().createCylinder(p1, p2, radius))
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): def Cone(p1, p2, radius1, radius2):
"""Create a cone primitive""" """Create a cone primitive"""
p1 = _pt(p1) do_op(occmodel.Solid().createCone(p1, p2, radius1, radius2))
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): def Sphere(center, radius):
"""Create a sphere primitive""" """Create a sphere primitive"""
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakeSphere(_pt(center), radius).Shape()) 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)
def Text(height, depth, text, fontpath=None): def Text(height, depth, text, fontpath=None):
"""TODO Create extruded text """Create extruded text
Note that the text may not contain whitespace! Note that the text may not contain whitespace!
(this appears to be a bug in occmodel, failing with occmodel.OCCError: (this appears to be a bug in occmodel, failing with occmodel.OCCError:
b'failed to create edges')""" b'failed to create edges')"""
return Box((0,0,0), (1,1,1)) do_op(occmodel.Solid().createText(height, depth, text, fontpath))
def Torus(p1, p2, ringRadius, radius): def Torus(p1, p2, ringRadius, radius):
"""Create a torus""" """Create a torus"""
axis = _axpt(p1, p2) do_op(occmodel.Solid().createTorus(p1, p2, ringRadius, radius))
builder = OCC.BRepPrimAPI.BRepPrimAPI_MakeTorus(axis, ringRadius, radius)
do_op(builder.Shape())
def Extrude(obj, p1, p2): def Extrude(obj, p1, p2):
"""Create a solid by extruding edge, wire, or face from p1 to p2""" """Create a solid by extruding edge, wire, or face from p1 to p2"""
p1 = _pt(p1) do_op(occmodel.Solid().extrude(obj, p1, p2))
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): def Revolve(face, p1, p2, angle):
"""Create a solid by revolving the face around the given axis""" """Create a solid by revolving the face around the given axis"""
p1 = _pt(p1) do_op(occmodel.Solid().revolve(face, p1, p2, angle))
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): def Loft(profiles, ruled=True, tolerance=1e-6):
"""Create a solid by lofting through a sequence of wires or closed edges""" """Create a solid by lofting through a sequence of wires or closed edges"""
builder = OCC.BRepOffsetAPI.BRepOffsetAPI_ThruSections(True, ruled, do_op(occmodel.Solid().loft(profiles, ruled, tolerance))
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): def Pipe(face, path):
if isinstance(path, OCC.TopoDS.TopoDS_Edge): do_op(occmodel.Solid().pipe(face, path))
wire = Wire.createWire((path,))
else:
wire = path
builder = OCC.BRepOffsetAPI.BRepOffsetAPI_MakePipe(wire, face)
do_op(builder.Shape())
### Group operations ### Group operations
@ -331,7 +244,9 @@ def Translated(delta):
return Op(Translate, delta) return Op(Translate, delta)
def Transformed(mat): def Transformed(mat):
"""Perform a transformation.""" """Perform a transformation.
Note that `geotools.Transform` is imported as `Xform` within poc files."""
return Op(Transform, mat) return Op(Transform, mat)
def Filleted(radius, edges=None): def Filleted(radius, edges=None):
@ -344,58 +259,19 @@ def Chamfered(distance, edges=None):
### Postfix operations ### Postfix operations
def _transform(obj, t):
_assign(obj,
OCC.BRepBuilderAPI.BRepBuilderAPI_Transform(obj, t, True).Shape())
def Rotate(angle, axis, center=(0,0,0)): def Rotate(angle, axis, center=(0,0,0)):
"""Rotate the active object""" """Rotate the active object"""
angle = math.radians(angle) obj.rotate(angle, axis, center)
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): def Translate(delta):
"""Translate the active object""" """Translate the active object"""
t = OCC.gp.gp_Trsf() obj.translate(delta)
t.SetTranslation(_vec(delta))
_transform(obj, t)
def Matrix(*args):
"""Construct a 4x3 matrix from arguments, which may be
- A list of 12 values
- A list of 9 values (in which case the last column is taken to be zeros)
- A list of 3 4-tuples, each taken as a row
- A list of 3 3-tuples, each taken as a row (the last column taken to be zeros)
"""
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): def Transform(mat):
"""Transform the active object """Transform the active object
Note that `geotools.Transform` is imported as `Xform` within poc files.""" Note that `geotools.Transform` is imported as `Xform` within poc files."""
_transform(obj, mat) obj.transform(mat)
def Fillet(radius, edges=None): def Fillet(radius, edges=None):
"""Fillet the active object """Fillet the active object
@ -407,14 +283,8 @@ True for each edge that should be filleted.
Otherwise, `edges` must be a sequence of edges to fillet. Otherwise, `edges` must be a sequence of edges to fillet.
""" """
if callable(edges): if callable(edges): edges = [e for e in Edges() if edges(e)]
edges = [e for e in Edges() if edges(e)] obj.fillet(radius, edges)
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): def Chamfer(distance, edges=None):
"""Chamfer the active object """Chamfer the active object
@ -426,159 +296,35 @@ True for each edge that should be filleted.
Otherwise, `edges` must be a sequence of edges to fillet. Otherwise, `edges` must be a sequence of edges to fillet.
""" """
if callable(edges): if callable(edges): edges = [e for e in Edges() if edges(e)]
edges = [e for e in Edges() if edges(e)] obj.chamfer(distance, edges)
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 ### 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(): def Object():
return obj return obj
def CenterOfMass(): def CenterOfMass():
"""Return the center of mass box of the current item""" """Return the bounding box of the current item"""
prop = OCC.GProp.GProp_GProps() return obj.centreOfMass()
OCC.BRepGProp.brepgprop_VolumeProperties(obj, prop)
return prop.CentreOfMass()
CentreOfMass = CenterOfMass CentreOfMass = CenterOfMass
def Bbox(o=None): def Bbox():
"""Return the bounding box of given object or the current item a a 6-tuple """Return the bounding box of the current item"""
return obj.boundingBox()
(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(): def Edges():
"""Return the edge iterator of the current item""" """Return the edge iterator of the current item"""
return visit(Object(), OCC.TopAbs.TopAbs_EDGE, OCC.TopoDS.topods.Edge) return occmodel.EdgeIterator(Object())
def Faces(): def Faces():
"""Return the face iterator of the current item""" """Return the face iterator of the current item"""
return visit(Object(), OCC.TopAbs.TopAbs_FACE, OCC.TopoDS.topods.Face) return occmodel.FaceIterator(Object())
def Vertices(): def Vertices():
"""Return the vertex iterator of the current item""" """Return the vertex iterator of the current item"""
return visit(Object(), OCC.TopAbs.TopAbs_VERTEX, OCC.TopoDS.topods.Vertex) return occmodel.VertexIterator(Object())
def Wires(): def Wires():
"""Return the wire iterator of the current item""" """Return the wire iterator of the current item"""
return visit(Object(), OCC.TopAbs.TopAbs_WIRE, OCC.TopoDS.topods.Wire) return occmodel.WireIterator(Object())
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
View file

@ -1,4 +1,4 @@
#!/usr/bin/python2 #!/usr/bin/python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# auto-updating viewer of 'poc' modeling program # auto-updating viewer of 'poc' modeling program
# Copyright © 2017 Jeff Epler <jepler@gmail.com> # Copyright © 2017 Jeff Epler <jepler@gmail.com>
@ -17,12 +17,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from occmodelviewer import Viewer from occmodelviewer import Viewer
import gltools
import geotools
import os import os
import poctools import poctools
import sys import sys
import time import time
import traceback import traceback
import vtk
filename = sys.argv[1] filename = sys.argv[1]
@ -34,48 +35,46 @@ def getmtime(filename):
class PocViewer(Viewer): class PocViewer(Viewer):
def __init__(self, filename): 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.filename = filename
self.modtime = -2
self.actor = vtk.vtkActor()
self.ren = vtk.vtkRenderer()
self.renWin = vtk.vtkRenderWindow()
self.renWin.AddRenderer(self.ren)
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.reloadModel()
self.iren.Start()
def onChar(self, ch):
if ch == 'r': self.reloadModel()
if ch == 'q': self.running = False
def reloadModel(self, modtime=None): def reloadModel(self, modtime=None):
self.modtime = modtime or getmtime(self.filename) self.modtime = modtime or getmtime(self.filename)
try: try:
ns = poctools.execpoc(sys.argv[1:], poctools.execpoc(sys.argv[1:])
__output__= os.path.splitext(filename)[0] + ".stl")
poctools.output(ns['__output__'])
except: except:
traceback.print_exc() traceback.print_exc()
return return
reader = vtk.vtkSTLReader() self.clear()
reader.SetFileName(ns['__output__']) o = poctools.obj
bb = o.boundingBox()
if bb.min != bb.max:
self.add(o)
self.mapper = vtk.vtkPolyDataMapper() # Viewer.mainLoop seems to have no provision for code to run when
self.mapper.SetInputConnection(reader.GetOutputPort()) # 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.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 = PocViewer(filename)
mw.Start() mw.running = True
mw.onIsoView()
mw.mainLoop()

View file

@ -10,5 +10,5 @@ setup(name='poc',
author_email='jepler@gmail.com', author_email='jepler@gmail.com',
url='https://github.com/jepler/poc', url='https://github.com/jepler/poc',
py_modules=['poctools'], py_modules=['poctools'],
scripts=['poc', 'pocview', 'pocimg'], scripts=['poc', 'pocview'],
) )