""" Utilities to work with CAD objects """ import hashlib from typing import Optional, Union, List, Tuple from OCP.TopLoc import TopLoc_Location from OCP.TopoDS import TopoDS_Shape from yacv_server.gltf import GLTFMgr CADLike = Union[TopoDS_Shape, TopLoc_Location] # Faces, Edges, Vertices and Locations for now def get_shape(obj: any, error: bool = True) -> Optional[CADLike]: """ Get the shape of a CAD-like object """ # Try to grab a shape if a different type of object was passed if isinstance(obj, TopoDS_Shape) or isinstance(obj, TopLoc_Location): return obj # Return locations (drawn as axes) if 'wrapped' in dir(obj) and isinstance(obj.wrapped, TopLoc_Location): return obj.wrapped # Build123D if 'part' in dir(obj): obj = obj.part if 'sketch' in dir(obj): obj = obj.sketch if 'line' in dir(obj): obj = obj.line # Build123D & CadQuery while 'wrapped' in dir(obj) and not isinstance(obj, TopoDS_Shape) and not isinstance(obj, TopLoc_Location): obj = obj.wrapped # Return shapes if isinstance(obj, TopoDS_Shape): return obj if error: raise ValueError(f'Cannot show object of type {type(obj)} (submit issue?)') else: return None def grab_all_cad() -> List[Tuple[str, CADLike]]: """ Grab all shapes by inspecting the stack """ import inspect stack = inspect.stack() shapes = [] for frame in stack: for key, value in frame.frame.f_locals.items(): shape = get_shape(value, error=False) if shape: shapes.append((key, shape)) return shapes def image_to_gltf(source: str | bytes, center: any, ppmm: int, name: Optional[str] = None, save_mime: str = 'image/jpeg') -> Tuple[bytes, str]: """Convert an image to a GLTF CAD object, indicating the center location and pixels per millimeter.""" from PIL import Image import io import os from build123d import Plane from build123d import Location from build123d import Vector # Handle arguments if name is None: if isinstance(source, str): name = os.path.basename(source) else: hasher = hashlib.md5() hasher.update(source) name = 'image_' + hasher.hexdigest() format: str if save_mime == 'image/jpeg': format = 'JPEG' elif save_mime == 'image/png': format = 'PNG' else: raise ValueError(f'Unsupported save MIME type (for GLTF files): {save_mime}') # Get the plane of the image center_loc = get_shape(center) if not isinstance(center_loc, TopLoc_Location): raise ValueError('Center location not valid') plane = Plane(Location(center_loc)) # Convert coordinates system plane.origin = Vector(plane.origin.X, plane.origin.Z, -plane.origin.Y) plane.z_dir = -plane.y_dir plane.y_dir = plane.z_dir def vert(v: Vector) -> Tuple[float, float, float]: return v.X, v.Y, v.Z # Load the image to a byte buffer img = Image.open(source) img_buf = io.BytesIO() img.save(img_buf, format=format) img_buf = img_buf.getvalue() # Build the gltf mgr = GLTFMgr(image=(img_buf, save_mime)) mgr.add_face([ vert(plane.origin - plane.x_dir * img.width / (2 * ppmm) - plane.y_dir * img.height / (2 * ppmm)), vert(plane.origin + plane.x_dir * img.width / (2 * ppmm) - plane.y_dir * img.height / (2 * ppmm)), vert(plane.origin + plane.x_dir * img.width / (2 * ppmm) + plane.y_dir * img.height / (2 * ppmm)), vert(plane.origin - plane.x_dir * img.width / (2 * ppmm) + plane.y_dir * img.height / (2 * ppmm)), ], [ (0, 2, 1), (0, 3, 2), ], [ (0, 0), (1, 0), (1, 1), (0, 1), ]) # Return the GLTF binary blob and the suggested name of the image return b''.join(mgr.gltf.save_to_bytes()), name