pythonocc-core
pythonocc-core copied to clipboard
Building pythonocc for geometry info and visualization only.
Dear all. I want to use PythonOCC to create a small library for reading (step, iges, brep) CAD files, extract info as volume, centroid, inertia properties, bounding box..... and also export to json, and stl, file to render for the web. But I don't need any of the modeling tools. Is there any advice to build it only with these features, in order to reduce package size, and build time?
You have to compile pythonocc by your own. The CMakeLists.txt file enables to choose which packages to wrap. Modeling tools are mandatory, but you can set the PYTHONOCC_WRAP_OCAF and PYTHONOCC_WRAP_VISU to OFF (see https://github.com/tpaviot/pythonocc-core/blob/master/CMakeLists.txt#L92), that will result in a smaller package size, and you won't need opengl as well.
You have to compile pythonocc by your own. The CMakeLists.txt file enables to choose which packages to wrap. Modeling tools are mandatory, but you can set the
PYTHONOCC_WRAP_OCAFandPYTHONOCC_WRAP_VISUtoOFF(see https://github.com/tpaviot/pythonocc-core/blob/master/CMakeLists.txt#L92), that will result in a smaller package size, and you won't need opengl as well.
And offscreen rendering will work with these functions disabled?
No, offscreen rendering won't work, it requires opengl and, at least, a virtual frame buffer. But export to webgl will be possible (threejs, x3d), they only need a mesher
@efirvida If you release it as open source, could you post the link here? I would be interested.
@efirvida If you release it as open-source, could you post the link here? I would be interested.
There is no problem with sharing the code. It's nothing from the other world. It is still under development but is functional:
import uuid
import enum
from subprocess import check_output
# OCC IMPORT
from OCC.Core.BRepTools import breptools_Read
from OCC.Core.BRep import BRep_Builder
from OCC.Core.TopoDS import TopoDS_Shape
from OCC.Core.BRepGProp import brepgprop_VolumeProperties
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.BRepBndLib import brepbndlib_Add
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.GProp import GProp_GProps
from OCC.Extend.DataExchange import read_stl_file, read_step_file, read_iges_file
from OCC.Core.Tesselator import ShapeTesselator
from OCC.Display.WebGl import x3dom_renderer, threejs_renderer
from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB
from OCC.Display.SimpleGui import init_display
class CADFormats(enum.Enum):
CAD = ["step", "stp", "iges", "igs", "brep"]
# READERS
class Readers(ABC):
@abstractmethod
def __call__(self, file: str):
...
class StepReader(Readers):
def __call__(self, file):
return read_step_file(file)
class IgesReader(Readers):
def __call__(self, file):
return read_iges_file(file)
class BrepReader(Readers):
def __call__(self, file):
shape = TopoDS_Shape()
builder = BRep_Builder()
breptools_Read(shape, file, builder)
return shape
CAD_READERS = {
"brep": BrepReader,
"step": StepReader,
"stp": StepReader,
"iges": IgesReader,
"igs": IgesReader,
}
class Model:
def __init__(self, file):
self.file = file
self.model = None
def __call__(self):
return self.read()
@property
def ttype(self):
"""Get model type as defined in ``CADFormats`` enumerator
based on ``file`` extension"""
_ext = self.file.split(".")[-1]
type = {
"format": _format
for _format in CADFormats
if _format.value and _ext in _format.value
}
if type:
return type
return CADFormats.UNDEFINED
def __hash__(self) -> str:
return self.hash
@property
def hash(self) -> str:
hash_output = check_output(["sha1sum", self.file]).decode()
return hash_output.split()[0]
def read(self):
if self.ttype["format"] == CADFormats.CAD:
self.model = CADModel()(self.file)
return self
@property
def info(self) -> dict:
self.model.info.update({"hash": self.hash})
return self.model.info
def to_png(self, output_file: str = None) -> None:
if not output_file:
output_file = f"shp_{self.hash}.png"
self.model.to_png(output_file)
def to_web(self, output_file: str = None) -> None:
ext = "json" if self.ttype["format"] == CADFormats.CAD else "stl"
if not output_file:
output_file = f"shp_{self.hash}.{ext}"
self.model.to_web(output_file, self.hash)
class CADModel:
def __call__(self, file):
self.file = file
return self.read()
def read(self):
_ext = self.file.split(".")[-1]
reader = CAD_READERS[_ext]()
self.__shape = reader(self.file)
return self
@property
def info(self) -> dict:
# Compute inertia properties
props = GProp_GProps()
brepgprop_VolumeProperties(self.__shape, props)
volume = props.Mass()
# Get inertia properties
cog = props.CentreOfMass()
cog_x, cog_y, cog_z = cog.Coord()
# _m = props.MatrixOfInertia()
# matrix_of_inertia = [
# (_m.Row(row + 1).X(), _m.Row(row + 1).Y(), _m.Row(row + 1).Z())
# for row in range(3)
# ]
box = self._get_boundingbox(use_mesh=False)
shape_info = {
"volume": volume,
"center_of_mass": (cog_x, cog_y, cog_z),
"bounding_box": box["box"],
"shape_size": box["size"],
}
return shape_info
def _get_boundingbox(self, tol=1e-6, use_mesh=True) -> tuple:
""" return the bounding box of the TopoDS_Shape `shape`
Parameters
----------
shape : TopoDS_Shape or a subclass such as TopoDS_Face
the shape to compute the bounding box from
tol: float
tolerance of the computed boundingbox
use_mesh : bool
a flag that tells whether or not the shape has first to be meshed before the bbox
computation. This produces more accurate results
"""
bbox = Bnd_Box()
bbox.SetGap(tol)
if use_mesh:
mesh = BRepMesh_IncrementalMesh()
mesh.SetParallelDefault(True)
mesh.SetShape(self.__shape)
mesh.Perform()
if not mesh.IsDone():
raise AssertionError("Mesh not done.")
brepbndlib_Add(self.__shape, bbox, use_mesh)
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
return {
"box": ((xmin, ymin, zmin), (xmax, ymax, zmax)),
"size": {
"X": abs(xmax - xmin),
"Y": abs(ymax - ymin),
"Z": abs(zmax - zmin),
},
}
def to_png(self, output_file: str) -> None:
display, *_ = init_display()
display.DisplayShape(self.__shape)
display.FitAll()
display.View.Dump(output_file)
def to_web(self, output_file: str, hash: str = None) -> None:
if not hash:
shape_uuid = uuid.uuid4().hex
hash = "shp%s" % shape_uuid
tess = ShapeTesselator(self.__shape)
tess.Compute(compute_edges=False, mesh_quality=1.0)
web_shape = tess.ExportShapeToThreejsJSONString(hash)
with open(output_file, "w") as web_file:
web_file.write(web_shape)
def __str__(self):
info = self.info
center_of_mass = (
f"\tX: {info['center_of_mass'][0]}",
f"\tY: {info['center_of_mass'][1]}",
f"\tZ: {info['center_of_mass'][2]}",
)
matrix_of_inertia = (
f"\t{info['matrix_of_inertia'][0]}",
f"\t{info['matrix_of_inertia'][1]}",
f"\t{info['matrix_of_inertia'][2]}",
)
bounding_box = (
f"\tMIN: {info['bounding_box'][0]}",
f"\tMAX: {info['bounding_box'][1]}",
)
_str = (
f"Volume: {info['volume']}",
f"Size: {info['shape_size']}",
"Center of mass:\n" + "\n".join(center_of_mass),
"Matrix of inertia:\n" + "\n".join(matrix_of_inertia),
"Bounding box:\n" + "\n".join(bounding_box),
)
return "\n".join(_str)
@efirvida Thank you.
If you called the to_png method of CADModel from another function, could you prevent the graphics from being closed? This is related to my question #855.
@efirvida Thank you.
If you called the
to_pngmethod ofCADModelfrom another function, could you prevent the graphics from being closed? This is related to my question #855.
I made this only to save screenshoot of the 3d model. On my project I don need the window, so to run it well I also use "PYTHONOCC_OFFSCREEN_RENDERER=1" as an environment variable, to completely hide the windows, which is not your case. In your your example, I think you just need to call start_display() after display.DisplayShape(...
@efirvida If you release it as open-source, could you post the link here? I would be interested.
There is no problem with sharing the code. It's nothing from the other world. It is still under development but is functional:
import uuid import enum from subprocess import check_output # OCC IMPORT from OCC.Core.BRepTools import breptools_Read from OCC.Core.BRep import BRep_Builder from OCC.Core.TopoDS import TopoDS_Shape from OCC.Core.BRepGProp import brepgprop_VolumeProperties from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh from OCC.Core.BRepBndLib import brepbndlib_Add from OCC.Core.Bnd import Bnd_Box from OCC.Core.GProp import GProp_GProps from OCC.Extend.DataExchange import read_stl_file, read_step_file, read_iges_file from OCC.Core.Tesselator import ShapeTesselator from OCC.Display.WebGl import x3dom_renderer, threejs_renderer from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB from OCC.Display.SimpleGui import init_display class CADFormats(enum.Enum): CAD = ["step", "stp", "iges", "igs", "brep"] # READERS class Readers(ABC): @abstractmethod def __call__(self, file: str): ... class StepReader(Readers): def __call__(self, file): return read_step_file(file) class IgesReader(Readers): def __call__(self, file): return read_iges_file(file) class BrepReader(Readers): def __call__(self, file): shape = TopoDS_Shape() builder = BRep_Builder() breptools_Read(shape, file, builder) return shape CAD_READERS = { "brep": BrepReader, "step": StepReader, "stp": StepReader, "iges": IgesReader, "igs": IgesReader, } class Model: def __init__(self, file): self.file = file self.model = None def __call__(self): return self.read() @property def ttype(self): """Get model type as defined in ``CADFormats`` enumerator based on ``file`` extension""" _ext = self.file.split(".")[-1] type = { "format": _format for _format in CADFormats if _format.value and _ext in _format.value } if type: return type return CADFormats.UNDEFINED def __hash__(self) -> str: return self.hash @property def hash(self) -> str: hash_output = check_output(["sha1sum", self.file]).decode() return hash_output.split()[0] def read(self): if self.ttype["format"] == CADFormats.CAD: self.model = CADModel()(self.file) return self @property def info(self) -> dict: self.model.info.update({"hash": self.hash}) return self.model.info def to_png(self, output_file: str = None) -> None: if not output_file: output_file = f"shp_{self.hash}.png" self.model.to_png(output_file) def to_web(self, output_file: str = None) -> None: ext = "json" if self.ttype["format"] == CADFormats.CAD else "stl" if not output_file: output_file = f"shp_{self.hash}.{ext}" self.model.to_web(output_file, self.hash) class CADModel: def __call__(self, file): self.file = file return self.read() def read(self): _ext = self.file.split(".")[-1] reader = CAD_READERS[_ext]() self.__shape = reader(self.file) return self @property def info(self) -> dict: # Compute inertia properties props = GProp_GProps() brepgprop_VolumeProperties(self.__shape, props) volume = props.Mass() # Get inertia properties cog = props.CentreOfMass() cog_x, cog_y, cog_z = cog.Coord() # _m = props.MatrixOfInertia() # matrix_of_inertia = [ # (_m.Row(row + 1).X(), _m.Row(row + 1).Y(), _m.Row(row + 1).Z()) # for row in range(3) # ] box = self._get_boundingbox(use_mesh=False) shape_info = { "volume": volume, "center_of_mass": (cog_x, cog_y, cog_z), "bounding_box": box["box"], "shape_size": box["size"], } return shape_info def _get_boundingbox(self, tol=1e-6, use_mesh=True) -> tuple: """ return the bounding box of the TopoDS_Shape `shape` Parameters ---------- shape : TopoDS_Shape or a subclass such as TopoDS_Face the shape to compute the bounding box from tol: float tolerance of the computed boundingbox use_mesh : bool a flag that tells whether or not the shape has first to be meshed before the bbox computation. This produces more accurate results """ bbox = Bnd_Box() bbox.SetGap(tol) if use_mesh: mesh = BRepMesh_IncrementalMesh() mesh.SetParallelDefault(True) mesh.SetShape(self.__shape) mesh.Perform() if not mesh.IsDone(): raise AssertionError("Mesh not done.") brepbndlib_Add(self.__shape, bbox, use_mesh) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() return { "box": ((xmin, ymin, zmin), (xmax, ymax, zmax)), "size": { "X": abs(xmax - xmin), "Y": abs(ymax - ymin), "Z": abs(zmax - zmin), }, } def to_png(self, output_file: str) -> None: display, *_ = init_display() display.DisplayShape(self.__shape) display.FitAll() display.View.Dump(output_file) def to_web(self, output_file: str, hash: str = None) -> None: if not hash: shape_uuid = uuid.uuid4().hex hash = "shp%s" % shape_uuid tess = ShapeTesselator(self.__shape) tess.Compute(compute_edges=False, mesh_quality=1.0) web_shape = tess.ExportShapeToThreejsJSONString(hash) with open(output_file, "w") as web_file: web_file.write(web_shape) def __str__(self): info = self.info center_of_mass = ( f"\tX: {info['center_of_mass'][0]}", f"\tY: {info['center_of_mass'][1]}", f"\tZ: {info['center_of_mass'][2]}", ) matrix_of_inertia = ( f"\t{info['matrix_of_inertia'][0]}", f"\t{info['matrix_of_inertia'][1]}", f"\t{info['matrix_of_inertia'][2]}", ) bounding_box = ( f"\tMIN: {info['bounding_box'][0]}", f"\tMAX: {info['bounding_box'][1]}", ) _str = ( f"Volume: {info['volume']}", f"Size: {info['shape_size']}", "Center of mass:\n" + "\n".join(center_of_mass), "Matrix of inertia:\n" + "\n".join(matrix_of_inertia), "Bounding box:\n" + "\n".join(bounding_box), ) return "\n".join(_str)
I get an "NameError: name 'ABC' is not defined" on "class Readers(ABC):"
add the following line to import :
from abc import ABC, abstractmethod
add the following line to import :
from abc import ABC, abstractmethod
@Tanneguydv Thank you.