-
-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: add support for scaling imported STLs #874
Comments
How about this? from build123d import *
from ocp_vscode import show
from os import PathLike, fsdecode
from OCP.RWStl import RWStl
from OCP.gp import gp_Pnt, gp_Trsf
from OCP.Poly import Poly_Triangulation
from OCP.TopoDS import TopoDS_Face
from OCP.BRep import BRep_Builder
from OCP.TColgp import TColgp_Array1OfPnt
def import_stl(file_name: PathLike | str | bytes, model_unit: Unit = Unit.MM) -> Face:
"""import_stl
Extract shape from an STL file and return it as a Face reference object.
Note that importing with this method and creating a reference is very fast while
creating an editable model (with Mesher) may take minutes depending on the size
of the STL file.
Args:
file_name (Union[PathLike, str, bytes]): file path of STL file to import
model_unit (Unit, optional): the default unit used when creating the model. For
example, Blender defaults to Unit.M. Defaults to Unit.MM.
Raises:
ValueError: Could not import file
ValueError: Invalid model_unit
Returns:
Face: STL model
"""
# Read STL file
reader = RWStl.ReadFile_s(fsdecode(file_name))
# Check for any required scaling
if model_unit == Unit.MM:
face = TopoDS_Face()
BRep_Builder().MakeFace(face, reader)
else:
conversion_factor = {
Unit.MC: MC, # MICRO
Unit.MM: MM, # MILLIMETER
Unit.CM: CM, # CENTIMETER
Unit.M: M, # METER
Unit.IN: IN, # INCH
Unit.FT: FT, # FOOT
}
try:
scale_factor = conversion_factor[model_unit]
except KeyError:
raise ValueError(
f"model_scale must one a valid unit: {Unit._member_names_}"
)
# Apply scaling transformation
trsf = gp_Trsf()
trsf.SetScaleFactor(scale_factor)
# Access the nodes (vertices) of the triangulation
num_nodes = reader.NbNodes()
nodes = [reader.Node(i) for i in range(1, num_nodes + 1)]
# Create a new array for the transformed nodes
scaled_nodes = TColgp_Array1OfPnt(1, num_nodes)
for i in range(1, num_nodes + 1):
# Apply the scaling transformation to each node
point = nodes[i - 1]
scaled_point = gp_Pnt(point.XYZ())
scaled_point.Transform(trsf)
scaled_nodes.SetValue(i, scaled_point)
# Create a new Poly_Triangulation with the scaled nodes
scaled_triangulation = Poly_Triangulation(scaled_nodes, reader.Triangles())
# Create TopoDS_Face from transformed triangulation
face = TopoDS_Face()
BRep_Builder().MakeFace(face, scaled_triangulation)
return Face.cast(face)
benchy = import_stl("3DBenchy.stl")
big_benchy = import_stl("3DBenchy.stl", Unit.IN)
show(benchy, big_benchy) The SVG/DXF exporter uses |
I did some experimentation and came to a similar solution as @gumyr (and reused some of gumyr's code) def import_stl_jdegenstein(file_name: PathLike | str | bytes, model_unit: Unit = Unit.MM) -> Face:
"""import_stl
Extract shape from an STL file and return it as a Face reference object.
Note that importing with this method and creating a reference is very fast while
creating an editable model (with Mesher) may take minutes depending on the size
of the STL file.
Args:
file_name (Union[PathLike, str, bytes]): file path of STL file to import
model_unit (Unit, optional): the default unit used when creating the model. For
example, Blender defaults to Unit.M. Defaults to Unit.MM.
Raises:
ValueError: Could not import file
ValueError: Invalid model_unit
Returns:
Face: STL model
"""
# Read STL file
reader = RWStl.ReadFile_s(fsdecode(file_name))
# Check for any required scaling
if model_unit == Unit.MM:
pass
else:
conversion_factor = {
# Unit.MC: MC, # MICRO
Unit.MM: MM, # MILLIMETER
Unit.CM: CM, # CENTIMETER
Unit.M: M, # METER
Unit.IN: IN, # INCH
Unit.FT: FT, # FOOT
}
try:
scale_factor = conversion_factor[model_unit]
except KeyError:
raise ValueError(
f"model_scale must one a valid unit: {Unit._member_names_}"
)
transformation = gp_Trsf()
transformation.SetScaleFactor(scale_factor)
node_arr = reader.InternalNodes()
for i in range(reader.NbNodes()):
node_arr.SetValue(i, node_arr.Value(i).Transformed(transformation))
face = TopoDS_Face()
BRep_Builder().MakeFace(face, reader)
return Face.cast(face) benchmark: from time import time
fn = "flake.stl" # 5.6 million triangles
t1 = time()
a = import_stl_gumyr(fn, model_unit=Unit.MM)
print(time()-t1)
t1 = time()
a = import_stl_gumyr(fn, model_unit=Unit.IN)
print(time()-t1)
t1 = time()
b = import_stl_jdegenstein(fn, model_unit=Unit.MM)
print(time()-t1)
t1 = time()
b = import_stl_jdegenstein(fn, model_unit=Unit.IN)
print(time()-t1) returns:
My version is about 1.8x faster than gumyr's. I was hoping to avoid this method of transforming each |
Nice! |
@jdegenstein would transforming in-place save you a few milliseconds? for i in range(1, reader.NbNodes() + 1):
p = reader.Node(i)
p.Transform(transformation)
reader.SetNode(i, p) |
@snoyer yes, great suggestion -- makes this version ~2.1x faster |
Most 3d scans and software like Blender use meters as the base unit while B3D uses mm. This causes most models to be imported way too small. This can be fixed by either pre-processing which is an unnecessary extra step that needs to be done every time, or by using Mesher.import() which can take minutes to import the model.
The text was updated successfully, but these errors were encountered: