mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-08-20 06:31:57 +02:00
Merge branch 'BelfrySCAD:master' into general_dev
This commit is contained in:
@@ -1232,7 +1232,7 @@ function _contour_vertices(pxlist, pxsize, isovalmin, isovalmax, segtablemin, se
|
|||||||
else if(f1<=isovalmin && isovalmin<=f0 && f0<=isovalmax) [p0, midptmin]
|
else if(f1<=isovalmin && isovalmin<=f0 && f0<=isovalmax) [p0, midptmin]
|
||||||
else if(f0<isovalmin && f1>isovalmax) [midptmin, midptmax]
|
else if(f0<isovalmin && f1>isovalmax) [midptmin, midptmax]
|
||||||
else if(f0>isovalmax && f1<isovalmin) [midptmax, midptmin]
|
else if(f0>isovalmax && f1<isovalmin) [midptmax, midptmin]
|
||||||
else if((f0<f1 && isovalmin<=f0 && isovalmax>=f1) || (f1<f0 && isovalmin<=f1 && isovalmax>=f0))
|
else if((f0<=f1 && isovalmin<=f0 && isovalmax>=f1) || (f1<=f0 && isovalmin<=f1 && isovalmax>=f0))
|
||||||
[p0, p1]
|
[p0, p1]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
14
joiners.scad
14
joiners.scad
@@ -687,7 +687,7 @@ module dovetail(gender, width, height, slide, h, w, angle, slope, thickness, tap
|
|||||||
|
|
||||||
type = is_def(chamfer) && chamfer>0 ? "chamfer" : "circle";
|
type = is_def(chamfer) && chamfer>0 ? "chamfer" : "circle";
|
||||||
|
|
||||||
smallend_half = round_corners(
|
bigend_half = round_corners(
|
||||||
move(
|
move(
|
||||||
[0,-slide/2-extra,0],
|
[0,-slide/2-extra,0],
|
||||||
p=[
|
p=[
|
||||||
@@ -700,13 +700,13 @@ module dovetail(gender, width, height, slide, h, w, angle, slope, thickness, tap
|
|||||||
method=type, cut = fullsize, closed=false
|
method=type, cut = fullsize, closed=false
|
||||||
);
|
);
|
||||||
|
|
||||||
smallend_points = concat(select(smallend_half, 1, -2), [down(extra,p=select(smallend_half, -2))]);
|
bigend_points = concat(select(bigend_half, 1, -2), [down(extra,p=select(bigend_half, -2))]);
|
||||||
offset = is_def(taper) ? -slide * tan(taper)
|
offset = is_def(taper) ? -slide * tan(taper)
|
||||||
: is_def(back_width) ? (back_width-width) / 2
|
: is_def(back_width) ? (back_width-width) / 2
|
||||||
: 0;
|
: 0;
|
||||||
bigend_points = move([offset+2*extra_offset,slide+2*extra,0], p=smallend_points);
|
smallend_points = move([offset+2*extra_offset,slide+2*extra,0], p=bigend_points);
|
||||||
|
|
||||||
bigenough = all_nonnegative(column(smallend_half,0)) && all_nonnegative(column(bigend_points,0));
|
bigenough = all_nonnegative(column(bigend_half,0)) && all_nonnegative(column(smallend_points,0));
|
||||||
|
|
||||||
assert(bigenough, "Width (or back_width) of dovetail is not large enough for its geometry (angle and taper");
|
assert(bigenough, "Width (or back_width) of dovetail is not large enough for its geometry (angle and taper");
|
||||||
|
|
||||||
@@ -715,7 +715,7 @@ module dovetail(gender, width, height, slide, h, w, angle, slope, thickness, tap
|
|||||||
|
|
||||||
// This code computes the true normal from which the exact width factor can be obtained
|
// This code computes the true normal from which the exact width factor can be obtained
|
||||||
// as the x component. Comparing to wfactor above shows that they agree.
|
// as the x component. Comparing to wfactor above shows that they agree.
|
||||||
// pts = [smallend_points[0], smallend_points[1], bigend_points[1],bigend_points[0]];
|
// pts = [bigend_points[0], bigend_points[1], smallend_points[1],smallend_points[0]];
|
||||||
// n = -polygon_normal(pts);
|
// n = -polygon_normal(pts);
|
||||||
// echo(n=n);
|
// echo(n=n);
|
||||||
// echo(invwfactor = 1/wfactor, error = n.x-1/wfactor);
|
// echo(invwfactor = 1/wfactor, error = n.x-1/wfactor);
|
||||||
@@ -726,8 +726,8 @@ module dovetail(gender, width, height, slide, h, w, angle, slope, thickness, tap
|
|||||||
|
|
||||||
skin(
|
skin(
|
||||||
[
|
[
|
||||||
reverse(concat(smallend_points, xflip(p=reverse(smallend_points)))),
|
reverse(concat(bigend_points, xflip(p=reverse(bigend_points)))),
|
||||||
reverse(concat(bigend_points, xflip(p=reverse(bigend_points))))
|
reverse(concat(smallend_points, xflip(p=reverse(smallend_points))))
|
||||||
],
|
],
|
||||||
slices=0, convexity=4
|
slices=0, convexity=4
|
||||||
);
|
);
|
||||||
|
@@ -1,18 +1,33 @@
|
|||||||
# 3d2scad.py - convert STL or 3MF to OpenSCAD polyhedron arrays.
|
# 3d2scad.py - convert STL or 3MF to OpenSCAD polyhedron arrays.
|
||||||
#
|
#
|
||||||
# This utility does these things (in this order):
|
# This utility does these things (in this order):
|
||||||
|
# - creates list of vertices and faces as the mesh is loaded
|
||||||
|
# - separates object into shells if multiple objects are detected
|
||||||
# - removes invalid triangles
|
# - removes invalid triangles
|
||||||
# - optionally simplifies mesh (reduces polygon count) using quadric decimation
|
# - optionally simplifies mesh (reduces polygon count) using quadric decimation (a robust method of simplification)
|
||||||
|
# - attempts repairs if a shell is detected as non-watertight (fill holes, remove unreferenced vertices, fix inversion and winding order, remove duplicate faces)
|
||||||
|
# - ensure normals are consistently pointing outward
|
||||||
# - quantizes coordinates to nearest 0.001 (or whatever you specify) for more compact output
|
# - quantizes coordinates to nearest 0.001 (or whatever you specify) for more compact output
|
||||||
# - removes zero-area triangles
|
# - removes zero-area triangles
|
||||||
# - removes duplicate vertices for significant size reduction (often a STL vertex is repeated six times)
|
# - removes duplicate vertices for significant size reduction (often a STL vertex is repeated six times)
|
||||||
|
# - another pass of removing unreferenced vertices
|
||||||
# - removes shared edges from coplanar polygons
|
# - removes shared edges from coplanar polygons
|
||||||
|
# - outputs a text file with a raw list of polyhedron structures (NOT an .scad file); see below for usage.
|
||||||
#
|
#
|
||||||
# In some cases, the operations above can result in non-manifold shapes, such as when two objects
|
# In some cases, the operations above can result in non-manifold shapes, such as when two objects
|
||||||
# share an edge, the resulting edge may be shared by more than two faces.
|
# share an edge, the resulting edge may be shared by more than two faces.
|
||||||
#
|
#
|
||||||
# June 2025
|
# June 2025
|
||||||
|
|
||||||
|
# TO USE IN OPENSCAD WITH BOLS2 LIBRARY:
|
||||||
|
# See VNF documentation at https://github.com/BelfrySCAD/BOSL2/wiki/vnf.scad
|
||||||
|
# If your output file is "model.txt" then use it this way:
|
||||||
|
#
|
||||||
|
# include <BOSL2/std.scad>
|
||||||
|
# vnf_list = include <model.txt>; // end with semicolon
|
||||||
|
# // vnf_list now contains a list of VNF (OpenSCAD polyhedron) structures
|
||||||
|
# vnf_polyhedron(vnf_list);
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
REQUIRED = ["numpy", "scipy", "trimesh", "open3d", "networkx", "lxml"] # required libraries not typically included in Python
|
REQUIRED = ["numpy", "scipy", "trimesh", "open3d", "networkx", "lxml"] # required libraries not typically included in Python
|
||||||
MISSING = []
|
MISSING = []
|
||||||
@@ -216,20 +231,25 @@ def format_number(n, precision):
|
|||||||
s = "0"
|
s = "0"
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def export_openscad_structure(vertices, polygons, name, shell_index, precision, f):
|
def export_openscad_structure(vertices, polygons, nshells, shell_index, precision, f):
|
||||||
varname = f"{name}{shell_index}"
|
if shell_index == 0:
|
||||||
f.write(f"{varname}=[\n[")
|
f.write("[ ")
|
||||||
|
f.write("\n[[")
|
||||||
f.write(",".join("[" + ",".join(format_number(c, precision) for c in v) + "]" for v in vertices))
|
f.write(",".join("[" + ",".join(format_number(c, precision) for c in v) + "]" for v in vertices))
|
||||||
f.write("],\n[")
|
f.write("],\n[")
|
||||||
f.write(",".join("[" + ",".join(str(i) for i in poly) + "]" for poly in polygons))
|
f.write(",".join("[" + ",".join(str(i) for i in poly) + "]" for poly in polygons))
|
||||||
f.write("]];\n")
|
f.write("]]")
|
||||||
|
if shell_index < nshells-1:
|
||||||
|
f.write(",\n")
|
||||||
|
else:
|
||||||
|
f.write(f"\n// shells: {nshells}\n]\n")
|
||||||
print(f" Wrote shell {shell_index+1} with {len(vertices)} vertices and {len(polygons)} faces", flush=True)
|
print(f" Wrote shell {shell_index+1} with {len(vertices)} vertices and {len(polygons)} faces", flush=True)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="3D model to OpenSCAD polyhedron converter", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
parser = argparse.ArgumentParser(description="3D model to OpenSCAD polyhedron converter", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("input", help="Input STL or 3MF file")
|
parser.add_argument("input", help="Input STL or 3MF file")
|
||||||
parser.add_argument("output", help="Output OpenSCAD file")
|
parser.add_argument("output", help="Output data file (list of VNF structures)")
|
||||||
parser.add_argument("--tolerance", type=float, metavar="FRAC", default=0.0,
|
parser.add_argument("--polycount", type=float, metavar="FRAC", default=0.0,
|
||||||
help="Fraction of faces to remove via quadric decimation (0-0.9)")
|
help="Fraction of faces to remove via quadric decimation (0-0.9)")
|
||||||
parser.add_argument("--quantize", type=float, metavar="GRIDUNIT", default=0.001,
|
parser.add_argument("--quantize", type=float, metavar="GRIDUNIT", default=0.001,
|
||||||
help="Grid size to quantize vertices")
|
help="Grid size to quantize vertices")
|
||||||
@@ -244,6 +264,7 @@ def main():
|
|||||||
|
|
||||||
mesh = load_mesh(args.input)
|
mesh = load_mesh(args.input)
|
||||||
shells = split_into_shells(mesh)
|
shells = split_into_shells(mesh)
|
||||||
|
nshells = len(shells)
|
||||||
|
|
||||||
if args.merge_shells:
|
if args.merge_shells:
|
||||||
merged = []
|
merged = []
|
||||||
@@ -274,8 +295,8 @@ def main():
|
|||||||
for i, shell in enumerate(shells):
|
for i, shell in enumerate(shells):
|
||||||
print(f"Processing shell {i + 1}:", flush=True)
|
print(f"Processing shell {i + 1}:", flush=True)
|
||||||
shell = remove_invalid_triangles(shell)
|
shell = remove_invalid_triangles(shell)
|
||||||
if args.tolerance > 0:
|
if args.polycount > 0:
|
||||||
shell = decimate_mesh(shell, args.tolerance)
|
shell = decimate_mesh(shell, args.polycount)
|
||||||
|
|
||||||
if not shell.is_watertight:
|
if not shell.is_watertight:
|
||||||
print(" Mesh is not watertight after simplification; attempting repair...", flush=True)
|
print(" Mesh is not watertight after simplification; attempting repair...", flush=True)
|
||||||
@@ -296,6 +317,7 @@ def main():
|
|||||||
|
|
||||||
if len(shell.faces) < args.min_faces:
|
if len(shell.faces) < args.min_faces:
|
||||||
print(f" Skipping shell with only {len(shell.faces)} face{'s' if len(shell.faces) != 1 else ''}", flush=True)
|
print(f" Skipping shell with only {len(shell.faces)} face{'s' if len(shell.faces) != 1 else ''}", flush=True)
|
||||||
|
nshells = nshells-1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f" Diagnostics:")
|
print(f" Diagnostics:")
|
||||||
@@ -306,7 +328,7 @@ def main():
|
|||||||
print(f" - Genus: {int(genus)}")
|
print(f" - Genus: {int(genus)}")
|
||||||
|
|
||||||
polygons = merge_coplanar_triangles(shell.vertices, shell.faces)
|
polygons = merge_coplanar_triangles(shell.vertices, shell.faces)
|
||||||
export_openscad_structure(shell.vertices.tolist(), polygons, name, i, precision, f)
|
export_openscad_structure(shell.vertices.tolist(), polygons, nshells, i, precision, f)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
Reference in New Issue
Block a user