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/*.png
.coverage
*.py[oc]
build

View file

@ -5,33 +5,30 @@
*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 Python2 programs, executed in an environment that
*poc* programs are Python3 programs, executed in an environment that
provides convenient shorthand for performing geometric operations.
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*.
*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*.
![pocview showing selective fillet of CSG object](/images/selective_fillet.png)
# Setup
* Install dependencies
* 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.
* 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.
* 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)
* pythonocc-core
* python-vtk6
* [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)
# Stability

View file

@ -1,11 +1,7 @@
#!/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
#!/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

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.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXBUILD = sphinx3-build
PAPER =
BUILDDIR = _build

View file

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

View file

@ -1,11 +1,13 @@
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::
from __future__ import division, print_function
from math import *
from geotools import *
Xform = Transform
from occmodel import *
from poctools import *

View file

@ -4,12 +4,9 @@ Examples
Selective fillet
----------------
.. literalinclude:: ../examples/selective_fillet.poc
:language: python
:linenos:
.. include:: ../examples/selective_fillet.poc
:code: python
.. 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.

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
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.
*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*,
which uses CGAL. For instance, OpenCASCADE has `fillet` as a first-class
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
there are likely to be compatibility-breaking changes as it develops.
For installation and setup instructions, see README.md.
Contents:
.. toctree::
:maxdepth: 2

View file

@ -3,8 +3,8 @@ poctools module
.. automodule:: poctools
Solid Primitives
~~~~~~~~~~~~~~~~
Primitives
~~~~~~~~~~
Each primitive is added to the currently active group operation.
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:: 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
:members:
:undoc-members:
.. autoclass:: Face
:members:
:undoc-members:
.. autoclass:: Wire
:members:
:undoc-members:
.. autofunction:: Vertex
.. autofunction:: Matrix
Postfix operations
~~~~~~~~~~~~~~~~~~
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.
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
import geotools
with Union():
with Rotated(1, (0,0,1)):
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))
print(Bbox())
print(CenterOfMass())
@ -11,31 +12,3 @@ 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

View file

@ -1,8 +1,6 @@
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))
x = Edge().createCircle((0,.25,0), (0,0,1), (.1))
f1 = Face().createFace((w1, x))
#do_op(f1)
f1 = Face().createFace(w1)
Extrude(f1, (0.,0.,0.), (0.,0.,1.))
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)
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)
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)

View file

@ -1,4 +1,6 @@
#!/usr/bin/env poc
import geotools
with Translated((0,0,0)):
Sphere((0,0,0), 12)
@ -8,7 +10,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(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)
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)
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((0,-100,0), (0,100,0), 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 -*-
# main module of 'poc' modeling program
# 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 __future__
import itertools
import geotools
import math
import occmodel
import os
import six
import shutil
import struct
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__ = [
'Box', 'Cylinder', 'Cone', 'Sphere', 'Text', 'Torus',
@ -92,7 +35,6 @@ __all__ = [
'Intersection', 'Difference', 'Union', 'Op',
'Object', 'Bbox', 'CenterOfMass', 'CentreOfMass',
'Edges', 'Faces', 'Vertices', 'Wires',
'Matrix', 'Vertex', 'Edge', 'Wire', 'Face',
'execpoc', 'occ_to_stl', 'do_op',
]
@ -106,6 +48,9 @@ def initial_ns():
}
six.exec_("""if 1:
from math import *
from geotools import *
Xform = Transform
from occmodel import *
from poctools import *
""", ns)
return ns
@ -114,8 +59,7 @@ 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
@ -137,158 +81,127 @@ 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):
global obj
obj = b
return a.copyFrom(b)
def _fuse(a, b):
global obj
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Fuse(a, b).Shape()
return a.fuse(b)
def _common(a, b):
global obj
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Common(a, b).Shape()
return a.common(b)
def _cut(a, b):
global obj
obj = OCC.BRepAlgoAPI.BRepAlgoAPI_Cut(a, b).Shape()
return a.cut(b)
def op1(x):
return iter(itertools.chain([_assign], itertools.repeat(x)))
def start():
global obj, op
obj = OCC.TopoDS.TopoDS_Shape()
obj = occmodel.Solid()
op = op1(_fuse)
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):
"""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)
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)
@contextlib.contextmanager
def withhelper(newop, newobj=None, finalop=None):
global obj, op
holdobj = obj
holdop = op
obj = newobj = newobj or OCC.TopoDS.TopoDS_Shape()
obj = newobj = newobj or occmodel.Solid()
op = iter(newop)
try:
yield
finally:
if finalop: finalop()
newobj = obj
obj = holdobj
op = holdop
do_op(newobj)
@contextlib.contextmanager
def TemporaryDirectory(*args):
d = tempfile.mkdtemp(*args)
try:
yield d
finally:
shutil.rmtree(d)
### Primitives
def Box(p1, p2):
"""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):
"""Create a cylinder primitive"""
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())
do_op(occmodel.Solid().createCylinder(p1, p2, radius))
def Cone(p1, p2, radius1, radius2):
"""Create a cone primitive"""
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)
do_op(occmodel.Solid().createCone(p1, p2, radius1, radius2))
def Sphere(center, radius):
"""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):
"""TODO Create extruded text
"""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')"""
return Box((0,0,0), (1,1,1))
do_op(occmodel.Solid().createText(height, depth, text, fontpath))
def Torus(p1, p2, ringRadius, radius):
"""Create a torus"""
axis = _axpt(p1, p2)
builder = OCC.BRepPrimAPI.BRepPrimAPI_MakeTorus(axis, ringRadius, radius)
do_op(builder.Shape())
do_op(occmodel.Solid().createTorus(p1, p2, ringRadius, radius))
def Extrude(obj, p1, p2):
"""Create a solid by extruding edge, wire, or face from p1 to p2"""
p1 = _pt(p1)
p2 = _pt(p2)
direction = OCC.gp.gp_Vec(p1, p2)
do_op(OCC.BRepPrimAPI.BRepPrimAPI_MakePrism(obj, direction).Shape())
do_op(occmodel.Solid().extrude(obj, p1, p2))
def Revolve(face, p1, p2, angle):
"""Create a solid by revolving the face around the given axis"""
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())
do_op(occmodel.Solid().revolve(face, p1, p2, angle))
def Loft(profiles, ruled=True, tolerance=1e-6):
"""Create a solid by lofting through a sequence of wires or closed edges"""
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())
do_op(occmodel.Solid().loft(profiles, ruled, tolerance))
def 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())
do_op(occmodel.Solid().pipe(face, path))
### Group operations
@ -331,7 +244,9 @@ def Translated(delta):
return Op(Translate, delta)
def Transformed(mat):
"""Perform a transformation."""
"""Perform a transformation.
Note that `geotools.Transform` is imported as `Xform` within poc files."""
return Op(Transform, mat)
def Filleted(radius, edges=None):
@ -344,58 +259,19 @@ 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"""
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)
obj.rotate(angle, axis, center)
def Translate(delta):
"""Translate the active object"""
t = OCC.gp.gp_Trsf()
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
obj.translate(delta)
def Transform(mat):
"""Transform the active object
Note that `geotools.Transform` is imported as `Xform` within poc files."""
_transform(obj, mat)
obj.transform(mat)
def Fillet(radius, edges=None):
"""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.
"""
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())
if callable(edges): edges = [e for e in Edges() if edges(e)]
obj.fillet(radius, edges)
def Chamfer(distance, edges=None):
"""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.
"""
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())
if callable(edges): edges = [e for e in Edges() if edges(e)]
obj.chamfer(distance, edges)
### 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 center of mass box of the current item"""
prop = OCC.GProp.GProp_GProps()
OCC.BRepGProp.brepgprop_VolumeProperties(obj, prop)
return prop.CentreOfMass()
"""Return the bounding box of the current item"""
return obj.centreOfMass()
CentreOfMass = CenterOfMass
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 Bbox():
"""Return the bounding box of the current item"""
return obj.boundingBox()
def Edges():
"""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():
"""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():
"""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():
"""Return the wire iterator of the current item"""
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()
return occmodel.WireIterator(Object())

63
pocview
View file

@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# auto-updating viewer of 'poc' modeling program
# Copyright © 2017 Jeff Epler <jepler@gmail.com>
@ -17,12 +17,13 @@
# 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]
@ -34,48 +35,46 @@ 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.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.iren.Start()
def onChar(self, ch):
if ch == 'r': self.reloadModel()
if ch == 'q': self.running = False
def reloadModel(self, modtime=None):
self.modtime = modtime or getmtime(self.filename)
try:
ns = poctools.execpoc(sys.argv[1:],
__output__= os.path.splitext(filename)[0] + ".stl")
poctools.output(ns['__output__'])
poctools.execpoc(sys.argv[1:])
except:
traceback.print_exc()
return
reader = vtk.vtkSTLReader()
reader.SetFileName(ns['__output__'])
self.clear()
o = poctools.obj
bb = o.boundingBox()
if bb.min != bb.max:
self.add(o)
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(reader.GetOutputPort())
self.actor.SetMapper(self.mapper)
def idle(self, obj, event):
# 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:
if newmodtime + .1 > now and newmodtime != self.modtime:
self.reloadModel(newmodtime)
self.renWin.Render()
self.updateBounds()
self.redraw()
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',
url='https://github.com/jepler/poc',
py_modules=['poctools'],
scripts=['poc', 'pocview', 'pocimg'],
scripts=['poc', 'pocview'],
)