Build the helical internal gear directly, not via boolen cut

The original, now replaced, way had the benefit of reusing the existing
helicalextrusion method, but the following boolean cut operation was a)
slow and b) suffered from co-planar issues. The new way extrudes the
face directly which does not have these shortcommings.
Unfortunaly, we had to use a different API: the original one is simple
and straight forward, gives no easy way to access the end of the sweep.
Collecting the wires manually, filtering on the position of all vertexes,
unvailed tolerances heigher then 0.1, so that a face created from it was
not planar and prevented ceating a valid solid.
The now used API requires more manual work in the initial setup, but the
end wires for construction the top face are directly accesible and the
tolerances are below 0.001 so we can create a planar face and valid solid.
This new way also much faster (1.6 sec vs. 5.0 sec on my machine
using default gear parameters on a double helix, naively measured via
`time.perf_counter()`).
Currently, the new helical extusion method is only used for the internal
gear. Migrating the other usages will be done as a separate commit.
This commit is contained in:
Jonas Bähr
2021-09-13 00:38:27 +02:00
committed by looooo
parent c40bae47b3
commit 055aab2e07

View File

@@ -398,9 +398,9 @@ class InternalInvoluteGear(BaseGear):
sh = Face([outer_circle, wi])
return sh.extrude(App.Vector(0, 0, fp.height.Value))
else:
tool = helicalextrusion(
wi, fp.height.Value, fp.height.Value * np.tan(fp.gear.beta) * 2 / fp.gear.d, fp.double_helix)
return Part.makeCylinder(fp.outside_diameter / 2., fp.height.Value).cut(tool)
sh = Face([outer_circle, wi])
twist_angle = fp.height.Value * np.tan(fp.gear.beta) * 2 / fp.gear.d
return helicalextrusion2(sh, fp.height.Value, twist_angle, fp.double_helix)
else:
inner_circle = Part.Wire(Part.makeCircle(fp.dw / 2.))
inner_circle.reverse()
@@ -1437,6 +1437,62 @@ def helicalextrusion(wire, height, angle, double_helix=False):
first_solid = first_spine.makePipeShell([wire], True, True)
return first_solid
def helicalextrusion2(face, height, angle, double_helix=False):
"""
A helical extrusion using the BRepOffsetAPI
face -- the face to extrude (may contain holes, i.e. more then one wires)
height -- the hight of the extrusion, normal to the face
angle -- the twist angle of the extrusion in radians
returns a solid
"""
pitch = height * 2 * np.pi / abs(angle)
radius = 10.0 # as we are only interested in the "twist", we take an arbitrary constant here
cone_angle = 0
direction = bool(angle < 0)
if double_helix:
spine = Part.makeHelix(pitch, height / 2.0, radius, cone_angle, direction)
else:
spine = Part.makeHelix(pitch, height, radius, cone_angle, direction)
def make_pipe(path, profile):
"""
returns (shell, last_wire)
"""
mkPS = Part.BRepOffsetAPI.MakePipeShell(path)
mkPS.setFrenetMode(True) # otherwise, the profile's normal would follow the path
mkPS.add(profile, False, False)
mkPS.build()
return (mkPS.shape(), mkPS.lastShape())
shell_faces = []
top_wires = []
for wire in face.Wires:
pipe_shell, top_wire = make_pipe(spine, wire)
shell_faces.extend(pipe_shell.Faces)
top_wires.append(top_wire)
top_face = Part.Face(top_wires)
shell_faces.append(top_face)
if double_helix:
origin = App.Vector(0, 0, 0)
xy_normal = App.Vector(0, 0, 1)
mirror_xy = lambda f: f.mirror(origin, xy_normal)
bottom_faces = list(map(mirror_xy, shell_faces))
shell_faces.extend(bottom_faces)
matrix = App.Matrix()
matrix.move(App.Vector(0, 0, height / 2.0))
move_up = lambda f: f.transformGeometry(matrix)
shell_faces = list(map(move_up, shell_faces))
else:
shell_faces.append(face) # the bottom is what we extruded
shell = Part.makeShell(shell_faces)
# TODO: why the heck is this shell empty if double_helix???
if len(shell.Faces) == 0:
# ... and why the heck does it work when making an intermediate compound???
hacky_intermediate_compound = Part.makeCompound(shell_faces)
shell = Part.makeShell(hacky_intermediate_compound.Faces)
App.Console.PrintMessage(f"shell.Faces from compound: {len(shell.Faces)}\n")
#shell.sewShape() # fill gaps that may result from accumulated tolerances. Needed?
#shell = shell.removeSplitter() # refine. Needed?
return Part.makeSolid(shell)
def make_face(edge1, edge2):
v1, v2 = edge1.Vertexes