diff --git a/README.md b/README.md index b152d76..53b27ec 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Alternatively, | -n | creating a new model profile with model name "p_wall" | |-gp| and geometry parameters *length, width, height, radius* (as are corresponding with the ones in the *pm* in *am4.py*. The order doesn't matter.) | |-gpv|input geometry parameters correspondingly, which are 1, 2, 3 and 10| - |-ip|Setting iteration parameters by the index number, endpoint and number of geom_files expected. In this example, we iterate parameter *width(2)* from its start point which is defined in -gp, to its endpoint(4) and we want 2 geom_files including the startpoint and endpoint.Also we want iterate *radius*(4) from 10 to 20 with 4 geom_files created in total.| + |-ip|Setting iteration parameters by the index number, endpoint and number of geom_files expected. In this example, we iterate parameter *width(2)* from its start point which is defined in -gpv, to its endpoint(4) and we want 2 geom_files including the startpoint and endpoint.Also we want iterate *radius*(4) from 10 to 20 with 4 geom_files created in total.| |-mbt|Mesh geometries with layer thickness defined. In this case every layer will be 0.2 of thickness.| |-msf|Mesh geometries with a global size factor. It is recommended to set a number smaller than the layer thickness to get a better performance.| diff --git a/amworkflow/api.py b/amworkflow/api.py index 8358450..6ae72da 100644 --- a/amworkflow/api.py +++ b/amworkflow/api.py @@ -8,9 +8,12 @@ import amworkflow.src.utils.reader as utr import amworkflow.src.infrastructure.database.cruds.crud as cr import amworkflow.src.utils.db_io as dio -from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Wire, TopoDS_Shell, TopoDS_Solid, TopoDS_Face, TopoDS_Edge, topods_Compound -from OCC.Core.gp import gp_Pnt, gp_Vec +import amworkflow.src.interface.gui.Qt.draft_ui as dui +from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Wire, TopoDS_Shell, TopoDS_Solid, TopoDS_Face, TopoDS_Edge, TopoDS_Compound +from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Pln, gp_Dir from OCC.Core.Geom import Geom_TrimmedCurve +from OCCUtils.Topology import Topo +from OCCUtils.Construct import vec_to_dir import numpy as np import gmsh from amworkflow.src.interface.cli.cli_workflow import cli @@ -20,11 +23,16 @@ import sys import inspect import pandas as pd +import importlib + class amWorkflow(object): class engine(object): @staticmethod def amworkflow(mode: str = "production"): + ''' + Engine to run the entire workflow. + ''' args = DeepMapParamModel(cli().__dict__) args.mode = mode caller_frame = inspect.stack()[1] @@ -41,12 +49,15 @@ def inner_decorator(func): def wrapped(*args, **kwargs): flow.geometry_spawn = func i = flow.indicator - if (i[4] == 1) or flow.onlyimport: - flow.create() - if flow.cmesh: - flow.mesh() - if flow.pcs_indicator[0] == 1: - flow.auto_download() + if i[0] == 3: + dui.draft_ui(func,flow) + else: + if (i[4] == 1) or flow.onlyimport: + flow.create() + if flow.cmesh: + flow.mesh() + if flow.pcs_indicator[0] == 1: + flow.auto_download() wrapped() return wrapped return inner_decorator @@ -77,11 +88,11 @@ def have_data_in_db(table: str, column_name, dataset: list, filter_by: str = Non @staticmethod def query_join_data(table: str, join_column: str, table1: str, join_column1:str, table2: str = None, join_column2:str = None, filter0: str = None, filter1: str = None, filter2: str = None, on_column_tb: str = None, on_column_tb1: str = None, on_column_tb2: str = None): - return cr.query_join_tables(table=table, join_column=join_column, table1=table1, join_column1=join_column1, table2=table2, filter0=filter0, filter1=filter1, filter2=filter2, on_column_tb=on_column_tb, on_column_tb1=on_column_tb1, on_column_tb2=on_column_tb2) + return cr.query_join_tables(table=table, join_column=join_column, table1=table1, join_column1=join_column1, table2=table2, filter0=filter0, filter1=filter1, filter2=filter2, on_column_tb=on_column_tb, on_column_tb1=on_column_tb1, on_column_tb2=on_column_tb2, join_column2=join_column2) class geom(object): @staticmethod - def geometry_builder(*args) -> topods_Compound: + def make_compound(*args) -> TopoDS_Compound: return b.geometry_builder(*args) @staticmethod @@ -92,9 +103,21 @@ def sew(*component) -> TopoDS_Shape: def make_solid(item: TopoDS_Shape) -> TopoDS_Shape: return b.solid_maker(item=item) @staticmethod - def pnt(x: float, y:float, z:float) -> gp_Pnt: + def pnt(x: float, y:float, z:float = 0) -> gp_Pnt: return gp_Pnt(x, y, z) + @staticmethod + def vec(x: float, y:float, z:float = 0) -> gp_Vec: + return gp_Vec(x,y,z) + + @staticmethod + def plane(pnt: gp_Pnt, vec: gp_Vec) -> gp_Pln: + return gp_Pln(pnt, vec) + + @staticmethod + def vec2dir(vec: gp_Vec) -> gp_Dir: + return vec_to_dir(vec) + @staticmethod def create_face(wire: TopoDS_Wire) -> TopoDS_Face: return sg.create_face(wire=wire) @@ -107,24 +130,24 @@ def create_box(length: float, alpha: float = None, shell: bool = False) -> TopoDS_Shape: """ - @brief Create a box with given length width height and radius. If radius is None or 0 the box will be sewed by a solid. - @param length Length of the box in points - @param width Width of the box. - @param height Height of the box. - @param radius Radius of the box. Default is None which means that the box is without curves. - @param alpha defines the angle of bending the box. Default is half the length divided by the radius. - @param shell If True the box will be shell. Default is False. - @return TopoDS_Shape with box in it's topolar form. Note that this is a Shape + Create a box with given length width height and radius. If radius is None or 0 the box will be sewed by a solid. + :param length Length of the box in points + :param width Width of the box. + :param height Height of the box. + :param radius Radius of the box. Default is None which means that the box is without curves. + :param alpha defines the angle of bending the box. Default is half the length divided by the radius. + :param shell If True the box will be shell. Default is False. + :return: TopoDS_Shape with box in it's topolar form. Note that this is a Shape """ return sg.create_box(length,width,height,radius,alpha,shell) @staticmethod def create_cylinder(radius: float, length: float) -> TopoDS_Shape: """ - @brief Create a cylinder shape. This is a convenience function for BRepPrimAPI_MakeCylinder - @param radius Radius of the cylinder in metres - @param length Length of the cylinder in metres. - @return Shape of the cylinder ( TopoDS_Shape ) that is created and ready to be added to topology + Create a cylinder shape. This is a convenience function for BRepPrimAPI_MakeCylinder + :param radius Radius of the cylinder in metres + :param length Length of the cylinder in metres. + :return: Shape of the cylinder ( TopoDS_Shape ) that is created and ready to be added to topology """ return sg.create_cylinder(radius,length) @@ -133,92 +156,100 @@ def create_prism(shape: TopoDS_Shape, vector: list, copy: bool = True) -> TopoDS_Shell: """ - @brief Create prism from TopoDS_Shape and vector. It is possible to copy the based wire(s) if copy is True. I don't know what if it's False so it is recommended to always use True. - @param shape TopoDS_Shape to be used as base - @param vector list of 3 elements ( x y z ). Normally only use z to define the height of the prism. - @param copy boolean to indicate if the shape should be copied - @return return the prism + Create prism from TopoDS_Shape and vector. It is possible to copy the based wire(s) if copy is True. I don't know what if it's False so it is recommended to always use True. + :param shape TopoDS_Shape to be used as base + :param vector list of 3 elements ( x y z ). Normally only use z to define the height of the prism. + :param copy boolean to indicate if the shape should be copied + :return: return the prism """ return sg.create_prism(shape, vector, copy) + @staticmethod + def create_prism_by_curve(shape: TopoDS_Shape, curve: TopoDS_Wire): + return sg.create_prism_by_curve(shape, curve) + + @staticmethod + def create_face_by_plane(pln: gp_Pln, *vt: gp_Pnt) -> TopoDS_Face: + return sg.create_face_by_plane(pln, *vt) + @staticmethod def create_wire(*edge) -> TopoDS_Wire: """ - @brief Create a wire. Input at least one edge to build a wire. This is a convenience function to call BRepBuilderAPI_MakeWire with the given edge and return a wire. - @return A wire built from the given edge ( s ). The wire may be used in two ways : 1 + Create a wire. Input at least one edge to build a wire. This is a convenience function to call BRepBuilderAPI_MakeWire with the given edge and return a wire. + :return: A wire built from the given edge ( s ). The wire may be used in two ways : 1 """ return sg.create_wire(edge) @staticmethod def create_edge(pnt1: gp_Pnt = None, pnt2: gp_Pnt = None, arch: Geom_TrimmedCurve = None) -> TopoDS_Edge: """ - @brief Create an edge between two points. This is a convenience function to be used in conjunction with : func : ` BRepBuilderAPI_MakeEdge ` - @param pnt1 first point of the edge - @param pnt2 second point of the edge - @param arch arch edge ( can be None ). If arch is None it will be created from pnt1 and pnt2 - @return an edge. + Create an edge between two points. This is a convenience function to be used in conjunction with : func : ` BRepBuilderAPI_MakeEdge ` + :param pnt1 first point of the edge + :param pnt2 second point of the edge + :param arch arch edge ( can be None ). If arch is None it will be created from pnt1 and pnt2 + :return: an edge. """ return sg.create_edge(pnt1, pnt2, arch) @staticmethod def create_arch(pnt1, pnt2, pnt1_2, make_edge: bool = True) -> TopoDS_Edge: """ - @brief Create an arc of circle. If make_edge is True the arc is created in TopoDS_Edge. - @param pnt1 The first point of the arc. - @param pnt2 The second point of the arc. - @param pnt1_2 The intermediate point of the arc. - @param make_edge If True the arc is created in the x - y plane. - @return arch : return an ` GC_MakeArcOfCircle` object or an edge + Create an arc of circle. If make_edge is True the arc is created in TopoDS_Edge. + :param pnt1 The first point of the arc. + :param pnt2 The second point of the arc. + :param pnt1_2 The intermediate point of the arc. + :param make_edge If True the arc is created in the x - y plane. + :return: arch : return an ` GC_MakeArcOfCircle` object or an edge """ return sg.create_arch(pnt1, pnt2, pnt1_2, make_edge) @staticmethod def create_wire_by_points(points: list): """ - @brief Create a closed wire (loop) by points. The wire is defined by a list of points which are connected by an edge. - @param points A list of points. Each point is a gp_Pnt ( x y z) where x, y and z are the coordinates of a point. - @return A wire with the given points connected by an edge. This will be an instance of : class : `BRepBuilderAPI_MakeWire` + Create a closed wire (loop) by points. The wire is defined by a list of points which are connected by an edge. + :param points A list of points. Each point is a gp_Pnt ( x y z) where x, y and z are the coordinates of a point. + :return: A wire with the given points connected by an edge. This will be an instance of : class : `BRepBuilderAPI_MakeWire` """ return sg.create_wire_by_points(points) @staticmethod def random_polygon_constructor(points:list, isface: bool = True) -> TopoDS_Face or TopoDS_Wire: """ - @brief Creates a polygon in any shape. If isface is True the polygon is made face - oriented otherwise it is wires - @param points List of points defining the polygon - @param isface True if you want to create a face - oriented - @return A polygon + Creates a polygon in any shape. If isface is True the polygon is made face - oriented otherwise it is wires + :param points List of points defining the polygon + :param isface True if you want to create a face - oriented + :return: A polygon """ return sg.random_polygon_constructor(points, isface) @staticmethod def angle_of_two_arrays(a1:np.ndarray, a2:np.ndarray, rad: bool = True) -> float: """ - @brief Returns the angle between two vectors. This is useful for calculating the rotation angle between a vector and another vector - @param a1 1D array of shape ( n_features ) - @param a2 2D array of shape ( n_features ) - @param rad If True the angle is in radians otherwise in degrees - @return Angle between a1 and a2 in degrees or radians depending on rad = True or False + Returns the angle between two vectors. This is useful for calculating the rotation angle between a vector and another vector + :param a1 1D array of shape ( n_features ) + :param a2 2D array of shape ( n_features ) + :param rad If True the angle is in radians otherwise in degrees + :return: Angle between a1 and a2 in degrees or radians depending on rad = True or False """ return sg.angle_of_two_arrays(a1, a2, rad) @staticmethod def create_lateral_vector(a: np.ndarray, d:bool): """ - @brief Compute lateral vector of a vector. This is used to create a vector which is perpendicular to the based vector on its left side ( d = True ) or right side ( d = False ) - @param a vector ( a ) - @param d True if on left or False if on right - @return A vector. + Compute lateral vector of a vector. This is used to create a vector which is perpendicular to the based vector on its left side ( d = True ) or right side ( d = False ) + :param a vector ( a ) + :param d True if on left or False if on right + :return: A vector. """ return sg.laterality_indicator(a, d) @staticmethod def angular_bisector(a1:np.ndarray, a2:np.ndarray) -> np.ndarray: """ - @brief Angular bisector between two vectors. The result is a vector splitting the angle between two vectors uniformly. - @param a1 1xN numpy array - @param a2 1xN numpy array - @return the bisector vector + Angular bisector between two vectors. The result is a vector splitting the angle between two vectors uniformly. + :param a1 1xN numpy array + :param a2 1xN numpy array + :return: the bisector vector """ return sg.angular_bisector(a1, a2) @@ -228,158 +259,181 @@ def make_regular_polygon(side_num: int, rotate: float = None, bound: bool = False) -> TopoDS_Face or TopoDS_Wire: """ - @brief Creates a regular polygon. The polygon is oriented counterclockwise around the origin. If bound is True the polygon will be only the boundary (TopoDS_Wire) the polygon. - @param side_num Number of sides of the polygon. - @param side_len Length of the side of the polygon. - @param rotate Rotation angle ( in radians ). Defaults to None which means no rotation. - @param bound output only the boundary. Defaults to False. See documentation for create_wire for more information. - @return face or boundary of the polygon. + Creates a regular polygon. The polygon is oriented counterclockwise around the origin. If bound is True the polygon will be only the boundary (TopoDS_Wire) the polygon. + :param side_num Number of sides of the polygon. + :param side_len Length of the side of the polygon. + :param rotate Rotation angle ( in radians ). Defaults to None which means no rotation. + :param bound output only the boundary. Defaults to False. See documentation for create_wire for more information. + :return: face or boundary of the polygon. """ return cg.polygon_maker(side_num, side_len, rotate, bound) @staticmethod def multiply_hexagon(side_num: int, side_len: float, iter_num: int, wall: float, center: gp_Pnt = None) -> TopoDS_Face: """ - @brief Creates a hexagon with multiplier. This is an iterative approach to the topological sorting algorithm. - @param side_num Number of sides in the hexagon. - @param side_len Length of the side ( s ) to be used for the multiplication. - @param iter_num Number of iterations to perform. Default is 1. - @param wall Wall thickness. - @param center Center of the multiplication. Default is original point. - @return TopoDS_Face. Note that it is the caller's responsibility to check if there is enough space + Creates a hexagon with multiplier. This is an iterative approach to the topological sorting algorithm. + :param side_num Number of sides in the hexagon. + :param side_len Length of the side ( s ) to be used for the multiplication. + :param iter_num Number of iterations to perform. Default is 1. + :param wall Wall thickness. + :param center Center of the multiplication. Default is original point. + :return: TopoDS_Face. Note that it is the caller's responsibility to check if there is enough space """ return cg.hexagon_multiplier(side_num, side_len, iter_num, wall, center) @staticmethod def make_isoceles_triangle(bbox_len:float, bbox_wid: float, thickness: float = None) -> TopoDS_Face: """ - @brief (Having problem with wall thickness now.) Create isoceles triangulation. This is a function to create isoceles triangulation of a bounding box and its widest corner - @param bbox_len length of bounding box of the triangle - @param bbox_wid width of bounding box of the triangle - @param thickness thickness of the wall of the triangle - @return a hollowed triangle face. + (Having problem with wall thickness now.) Create isoceles triangulation. This is a function to create isoceles triangulation of a bounding box and its widest corner + :param bbox_len length of bounding box of the triangle + :param bbox_wid width of bounding box of the triangle + :param thickness thickness of the wall of the triangle + :return: a hollowed triangle face. """ return cg.isoceles_triangle_maker(bbox_len, bbox_wid, thickness) @staticmethod def create_sym_hexagon1_infill(total_len: float, total_wid:float, height:float, th: float) : """ - @brief Create an infill pattern using symmetrical hexagon with defined len, height and numbers. - @param total_len total length of the bounding box. - @param total_wid total wid of the bounding box. - @param height height of the prism. This is the same as height of the hexagon. - @param th thickness of the wall of the hexagon. - @return + Create an infill pattern using symmetrical hexagon with defined len, height and numbers. + :param total_len total length of the bounding box. + :param total_wid total wid of the bounding box. + :param height height of the prism. This is the same as height of the hexagon. + :param th thickness of the wall of the hexagon. + :return: """ return cg.create_sym_hexagon1_infill(total_len, total_wid, height, th) + # @staticmethod + # def create_wall_by_points(pts:list, th: float, isclose:bool, height: float = None, debug: bool = False, debug_type: str = "linear", output: str = "prism", interpolate: float = None, R: float = None) -> np.ndarray or TopoDS_Face or TopoDS_Shell: + # """ + # Create a prism wall by points. It takes a list of points as a skeleton of a central path and then build a strip or a loop. + # :param pts: list of 2D points that define the wall. The algorithm can compute points in 3D theoretically but the result may make no sense. + # :param th: thickness of the wall. + # :param isclose: True if the wall is closed (loop) + # :param height: height of the wall if a prism is needed. + # :param debug: if True output two groups of points for plotting. + # :param output: selecting result intended to output. can be varied among "face" and "prism". + # :return: two arrays or a face or a prism. + # """ + # return cg.create_wall_by_points(pts, th, isclose, height,debug, debug_type, output,interpolate, R) + @staticmethod - def create_wall_by_points(pts:list, th: float, isclose:bool, height: float = None, debug: bool = False, output: str = "prism") -> np.ndarray or TopoDS_Face or TopoDS_Shell: - """ - @brief Create a prism wall by points. It takes a list of points as a skeleton of a central path and then build a strip or a loop. - @param pts list of 2D points that define the wall. The algorithm can compute points in 3D theoretically but the result may make no sense. - @param th thickness of the wall. - @param isclose True if the wall is closed (loop) - @param height height of the wall if a prism is needed. - @param debug if True output two groups of points for plotting. - @param output selecting result intended to output. can be varied among "face" and "prism". - @return two arrays or a face or a prism. - """ - return cg.create_wall_by_points(pts, th, isclose, height,debug, output) + class CreateWallByPoints(cg.CreateWallByPoints): + def __init__(self, pts: list, th: float, height: float): + super().__init__(pts,th,height) @staticmethod def get_face_center_of_mass(face: TopoDS_Face, gp_pnt: bool = False) -> tuple | gp_Pnt: """ - @brief Get the center of mass of a TopoDS_Face. This is useful for determining the center of mass of a face or to get the centre of mass of an object's surface. - @param face TopoDS_Face to get the center of mass of - @param gp_pnt If True return an gp_Pnt object otherwise a tuple of coordinates. + Get the center of mass of a TopoDS_Face. This is useful for determining the center of mass of a face or to get the centre of mass of an object's surface. + :param face TopoDS_Face to get the center of mass of + :param gp_pnt If True return an gp_Pnt object otherwise a tuple of coordinates. """ return p.get_face_center_of_mass(face, gp_pnt) @staticmethod def get_face_area(face: TopoDS_Face) -> float: """ - @brief Get the area of a TopoDS_Face. This is an approximation of the area of the face. - @param face to get the area of. - @return The area of the face. + Get the area of a TopoDS_Face. This is an approximation of the area of the face. + :param face: to get the area of. + :return: The area of the face. """ return p.get_face_area(face) @staticmethod def get_occ_bounding_box(shape: TopoDS_Shape) -> tuple: """ - @brief Get bounding box of occupied space of topo shape. - @param shape TopoDS_Shape to be searched for occupied space - @return bounding box of occupied space in x y z coordinates + Get bounding box of occupied space of topo shape. + :param shape: TopoDS_Shape to be searched for occupied space + :return: bounding box of occupied space in x y z coordinates """ return p.get_occ_bounding_box(shape) @staticmethod def get_faces(_shape): """ - @brief (This function now can be replaced by topo_explorer(shape, "face").)Get faces from a shape. - @param _shape shape to get faces of - @return list of topods_Face objects ( one for each face in the shape ) for each face + (This function now can be replaced by topo_explorer(shape, "face").)Get faces from a shape. + :param _shape: shape to get faces of + :return: list of topods_Face objects ( one for each face in the shape ) for each face """ return p.get_faces(_shape) @staticmethod + def get_boundary(item: TopoDS_Shape) -> TopoDS_Wire: + ''' + Get the boundary line of a rectangle shape. + :param item: Item to be inspected. + ''' + return o.get_boundary(item=item) + @staticmethod def get_point_coord(_p: gp_Pnt) -> tuple: """ - @brief Returns the coord of a point. This is useful for debugging and to get the coordinates of an object that is a part of a geometry. - @param p gp_Pnt to get the coord of - @return tuple of the coordinate of the point ( x y z ) or None if not a point ( in which case the coordinates are None + Returns the coord of a point. This is useful for debugging and to get the coordinates of an object that is a part of a geometry. + :param p: gp_Pnt to get the coord of + :return: tuple of the coordinate of the point ( x y z ) or None if not a point ( in which case the coordinates are None """ return p.point_coord(_p) @staticmethod def topo_explorer(shape: TopoDS_Shape, shape_type: str) -> list: """ - @brief TopoDS Explorer for shape_type. This is a wrapper around TopExp_Explorer to allow more flexibility in the explorer - @param shape TopoDS_Shape to be explored. - @param shape_type Type of shape e. g. wire face shell solid compound edge - @return List of TopoDS_Shape that are explored by shape_type. Example : [ TopoDS_Shape ( " face " ) TopoDS_Shape ( " shell " + TopoDS Explorer for shape_type. This is a wrapper around TopExp_Explorer to allow more flexibility in the explorer + :param shape: TopoDS_Shape to be explored. + :param shape_type: Type of shape e. g. wire face shell solid compound edge + :return: List of TopoDS_Shape that are explored by shape_type. Example : [ TopoDS_Shape ( " face " ) TopoDS_Shape ( " shell " """ return p.topo_explorer(shape, shape_type) + @staticmethod + def traverse(item: TopoDS_Shape) -> Topo: + '''Traverse the whole item and return how the topological parts connecting with each other. + :param item: item to be traversed. + :return: An instance of Topo class. To get for example all solids from the instacne, do foo.solids(), which will return an python iterator of all solids. + + tips: If necessary, it can be converted to a list by list() + + ''' + return p.traverser(item=item) + @staticmethod def rotate(shape: TopoDS_Shape, angle: float, axis: str = "z"): """ - @brief Rotate the topography by the given angle around the center of mass of the face. - @param shape TopoDS_Shape to be rotated. - @param angle Angle ( in degrees ) to rotate by. - @param axis determine the rotation axis. - @return the rotated shape. + :brief: Rotate the topography by the given angle around the center of mass of the face. + :param shape: TopoDS_Shape to be rotated. + :param angle: Angle ( in degrees ) to rotate by. + :param axis: determine the rotation axis. + :return: the rotated shape. """ return o.rotate_face(shape, angle, axis) @staticmethod def fuse(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: """ - @brief Fuse two shapes into one. - @param shape1 first shape to fuse. - @param shape2 second shape to fuse. - @return topoDS_Shape + :brief: Fuse two shapes into one. + :param shape1: first shape to fuse. + :param shape2: second shape to fuse. + :return: topoDS_Shape """ return o.fuser(shape1, shape2) @staticmethod def cutter3D(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: """ - @brief Cut a TopoDS_Shape from shape1 by shape2. It is possible to use this function to cut an object in 3D - @param shape1 shape that is to be cut - @param shape2 shape that is to be cut. It is possible to use this function to cut an object in 3D - @return a shape that is the result of cutting shape1 by shape2 ( or None if shape1 and shape2 are equal + Cut a TopoDS_Shape from shape1 by shape2. It is possible to use this function to cut an object in 3D + :param shape1: shape that is to be cut + :param shape2: shape that is to be cut. It is possible to use this function to cut an object in 3D + :return: a shape that is the result of cutting shape1 by shape2 ( or None if shape1 and shape2 are equal """ return o.cutter3D(shape1, shape2) @staticmethod def common(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: """ - @brief Common between two TopoDS_Shapes. The result is a shape that has all components of shape1 and shape2 - @param shape1 the first shape to be compared - @param shape2 the second shape to be compared ( must be same shape! ) - @return the common shape or None if there is no common shape between shape1 and shape2 in the sense that both shapes are + Common between two TopoDS_Shapes. The result is a shape that has all components of shape1 and shape2 + :param shape1: the first shape to be compared + :param shape2: the second shape to be compared ( must be same shape! ) + :return: the common shape or None if there is no common shape between shape1 and shape2 in the sense that both shapes are """ return o.common(shape1, shape2) @@ -387,18 +441,30 @@ def common(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: def translate(item: TopoDS_Shape, vector: list) -> None: """ - @brief Translates the shape by the distance and direction of a given vector. - @param item The item to be translated. It must be a TopoDS_Shape - @param vector The vector to translate the object by. The vector has to be a list with three elements + Translates the shape by the distance and direction of a given vector. + :param item: The item to be translated. It must be a TopoDS_Shape + :param vector: The vector to translate the object by. The vector has to be a list with three elements """ return o.translate(item, vector) + @staticmethod + def p_translate(pts:np.ndarray, direct: np.ndarray) -> np.ndarray: + return sg.p_translate(pts, direct=direct) + + @staticmethod + def p_rotate(pts:np.ndarray, angle_x: float = 0, angle_y: float = 0, angle_z: float = 0, cnt:np.ndarray = None) -> np.ndarray: + return sg.p_rotate(pts = pts, cnt = cnt, angle_x = angle_x, angle_y=angle_y, angle_z=angle_z) + + @staticmethod + def p_center_of_mass(pts: np.ndarray) -> np.ndarray: + return sg.p_center_of_mass(pts=pts) + @staticmethod def reverse(item:TopoDS_Shape) -> TopoDS_Shape: """ - @brief Reverse the shape. - @param item The item to reverse. - @return The reversed item + Reverse the shape. + :param item: The item to reverse. + :return: The reversed item """ o.reverse(item) return item @@ -406,9 +472,9 @@ def reverse(item:TopoDS_Shape) -> TopoDS_Shape: @staticmethod def geom_copy(item: TopoDS_Shape): """ - @brief Copy a geometry to a db shape. This is a wrapper around BRepBuilderAPI_Copy and can be used to create a copy of a geometry without having to re - create the geometry in the same way. - @param item Geometry to be copied. - @return db geometry that is a copy of the input geometry. + Copy a geometry to a db shape. This is a wrapper around BRepBuilderAPI_Copy and can be used to create a copy of a geometry without having to re - create the geometry in the same way. + :param item: Geometry to be copied. + :return: db geometry that is a copy of the input geometry. """ return o.geom_copy(item) @@ -422,49 +488,53 @@ def split(item: TopoDS_Shape, nx: int = None, ny: int = None): """ - @brief Split a TopoDS_Shape into sub - shapes. - @param item TopoDS_Shape to be split. - @param nz Number of z - points to split. - @param layer_thickness Layer thickness ( m ). - @param split_z Split on the Z direction. - @param split_x Split on the X direction. - @param split_y Split on the Y direction. - @param nx Number of sub - shapes in the x - direction. - @param ny Number of sub - shapes in the y - direction. - @return a compound of sub-shapes + Split a TopoDS_Shape into sub - shapes. + :param item: TopoDS_Shape to be split. + :param nz: Number of z - points to split. + :param layer_thickness: Layer thickness ( m ). + :param split_z: Split on the Z direction. + :param split_x: Split on the X direction. + :param split_y: Split on the Y direction. + :param nx: Number of sub - shapes in the x - direction. + :param ny: Number of sub - shapes in the y - direction. + :return: a compound of sub-shapes """ return o.split(item=item, nz=nz, layer_thickness=layer_thickness, split_x=split_x, split_y=split_y, split_z=split_z, nx=nx, ny=ny) + @staticmethod + def split2(item: TopoDS_Shape, *tools: TopoDS_Shape) -> TopoDS_Compound: + return o.split2(item,*tools) + @staticmethod def intersector(item: TopoDS_Shape, position: float, axis: str) -> TopoDS_Shape: """ - @brief Returns the topo shape intersecting the item at the given position. - @param position Position of the plane in world coordinates. - @param axis Axis along which of the direction. - @return TopoDS_Shape with intersection or empty TopoDS_Shape if no intersection is found. + Returns the topo shape intersecting the item at the given position. + :param position: Position of the plane in world coordinates. + :param axis: Axis along which of the direction. + :return: TopoDS_Shape with intersection or empty TopoDS_Shape if no intersection is found. """ return o.intersector(item, position, axis) @staticmethod def scale(item: TopoDS_Shape, cnt_pnt: gp_Pnt, factor: float) -> TopoDS_Shape: """ - @brief Scales TopoDS_Shape to a given value. This is useful for scaling shapes that are in a shape with respect to another shape. - @param item TopoDS_Shape to be scaled. - @param cnt_pnt the point of the scaling center. - @param factor Factor to scale the shape by. Default is 1. - @return a scaled TopoDS_Shape with scaling applied to it. + Scales TopoDS_Shape to a given value. This is useful for scaling shapes that are in a shape with respect to another shape. + :param item: TopoDS_Shape to be scaled. + :param cnt_pnt: the point of the scaling center. + :param factor: Factor to scale the shape by. Default is 1. + :return: a scaled TopoDS_Shape with scaling applied to it. """ return o.scaler(item, cnt_pnt, factor) @staticmethod def hollow_carve(face: TopoDS_Shape, factor: float): """ - @brief (This can be replaced by cutter3D() now.)Carving on a face with a shape scaling down from itself. - @param face TopoDS_Shape to be cut. - @param factor Factor to be used to scale the cutter. - @return A shape with the cutter in it's center of mass scaled by factor + (This can be replaced by cutter3D() now.)Carving on a face with a shape scaling down from itself. + :param face: TopoDS_Shape to be cut. + :param factor: Factor to be used to scale the cutter. + :return: A shape with the cutter in it's center of mass scaled by factor """ return o.hollow_carver(face, factor) @@ -472,7 +542,8 @@ class mesh(object): @staticmethod def gmsh_switch(s: bool) -> None: """ - @brief + Switch on and off the Gmsh Engine. + :param s: True or False """ return m.gmsh_switch(s) @@ -492,23 +563,23 @@ class tool(object): @staticmethod def write_stl(item: any, item_name: str, linear_deflection: float = 0.001, angular_deflection: float = 0.1, output_mode = 1, store_dir: str = None) -> None: """ - @brief Write OCC to STL file. This function is used to write a file to the database. The file is written to a file named item_name. - @param item the item to be written to the file. - @param item_name the name of the item. It is used to generate the file name. - @param linear_deflection the linear deflection factor. - @param angular_deflection the angular deflection factor. - @param output_mode for using different api in occ. - @param store_dir the directory to store the file in. - @return None if success else error code ( 1 is returned if error ). In case of error it is possible to raise an exception + Write OCC to STL file. This function is used to write a file to the database. The file is written to a file named item_name. + :param item: the item to be written to the file. + :param item_name: the name of the item. It is used to generate the file name. + :param linear_deflection: the linear deflection factor. + :param angular_deflection: the angular deflection factor. + :param output_mode: for using different api in occ. + :param store_dir the directory to store the file in. + :return: None if success else error code ( 1 is returned if error ). In case of error it is possible to raise an exception """ utw.stl_writer(item, item_name, linear_deflection, angular_deflection, output_mode, store_dir) @staticmethod def write_step(item: any, filename: str, directory: str): """ - @brief Writes a step file. This is a wrapper around write_step_file to allow a user to specify the shape of the step and a filename - @param item the item to write to the file - @param filename the filename to write the file to ( default is None + Writes a step file. This is a wrapper around write_step_file to allow a user to specify the shape of the step and a filename + :param item: the item to write to the file + :param filename: the filename to write the file to ( default is None """ utw.step_writer(item, filename, directory) @@ -522,15 +593,15 @@ def namer(name_type: str, geom_name: str = None ) -> str: """ - @brief Generate a name based on the type of name. It is used to generate an output name for a layer or a geometric object - @param name_type Type of name to generate - @param dim_vector Vector of dimension values ( default : None ) - @param batch_num Number of batch to generate ( default : None ) - @param parm_title List of parameters for the layer - @param is_layer_thickness True if the layer is thickness ( default : False ) - @param layer_param Parameter of the layer ( default : None ) - @param geom_name Name of the geometric object ( default : None ) - @return Name of the layer or geometric object ( default : None ) - The string representation of the nam + brief Generate a name based on the type of name. It is used to generate an output name for a layer or a geometric object + :param name_type: Type of name to generate + :param dim_vector: Vector of dimension values ( default : None ) + :param batch_num: Number of batch to generate ( default : None ) + :param parm_title: List of parameters for the layer + :param is_layer_thickness: True if the layer is thickness ( default : False ) + :param layer_param: Parameter of the layer ( default : None ) + :param geom_name: Name of the geometric object ( default : None ) + :return: Name of the layer or geometric object ( default : None ) - The string representation of the nam """ return utw.namer(name_type, dim_vector, batch_num, parm_title, is_layer_thickness, layer_param, geom_name) @@ -540,22 +611,41 @@ def get_filename(path: str) -> str: @staticmethod def read_step(path: str) -> TopoDS_Shape(): + ''' + Read STEP file to memory in OCC format + ''' return utr.step_reader(path) @staticmethod def read_stl(path: str) -> TopoDS_Shape: + ''' + Read STL file to memory in OCC format. + ''' return utr.stl_reader(path) @staticmethod def upload(source: str, destination: str) -> bool: + ''' + For db operation only. + Copy one file to a specific place. + ''' return dio.file_copy(path1=source, path2=destination) @staticmethod def get_md5(source: str) -> str: + ''' + Get MD5 value of a file. + ''' return utr.get_file_md5(path=source) @staticmethod def mk_newdir(dirname:str, folder_name: str): + ''' + Make a new directory with given name. + :param dirname: Absolute path to the place for holding the new directory. + :param folder_name: name of the new directory. + :return: Absolute path of the new directory. + ''' return utw.mk_dir(dirname= dirname, folder_name=folder_name) @staticmethod @@ -571,6 +661,9 @@ def write_mesh(item: gmsh.model, directory: str, modelname: str, output_filename @staticmethod def is_md5(string: str) -> bool: + ''' + Use regex to check if a sting is MD5. + ''' return utr.is_md5_hash(s=string) @staticmethod @@ -579,11 +672,18 @@ def download(file_dir: str = None, task_id: int | str = None, time_range: list = None, org: bool = True): - + ''' + For db operation only. + Copy one file and translate its name from hash32 to its real name, then paste it to the destination. + ''' return dio.downloader(file_dir=file_dir, output_dir=output_dir, task_id=task_id, time_range=time_range, org=org) @staticmethod def delete(dir_path: str, filename: str = None, operate: str = None, op_list: list = None): + ''' + For db operation only. + Delete one or multiple files. + ''' dio.file_delete(dir_path=dir_path, filename=filename, operate=operate, op_list=op_list) diff --git a/amworkflow/src/core/workflow.py b/amworkflow/src/core/workflow.py index f073d6e..536763f 100644 --- a/amworkflow/src/core/workflow.py +++ b/amworkflow/src/core/workflow.py @@ -20,10 +20,10 @@ class BaseWorkflow(object): def __init__(self, args): #base - self.args = args + self.args = args # store data from the terminal self.geometry_spawn: callable # data - self.geom_data = [] + self.geom_data = [] # self.pm = 0 self.mdl = 0 self.md5 = "" @@ -34,10 +34,6 @@ def __init__(self, args): self.db_data_collect = {"geometry":[], "occ": [], "mesh":[]} - self.oil = [ - [0,0,0,1,0], - [0,4,1,1,0] - ] self.pcs_indicator = [0,0,0] #signals self.indicator = self.task_handler() @@ -46,10 +42,8 @@ def __init__(self, args): self.onlyimport = False self.cmesh = False self.init_signal = self.data_init() - print(self.indicator) #main self.task_id = task_id_creator() - self.sequence() def sequence(self): @@ -62,6 +56,9 @@ def sequence(self): value = self.args.geom_param_value self.pm = MapParamModel(label, value) # self.create() + case 3: + print("Draft ready.") + def create(self) -> None: if self.init_signal == 2: @@ -200,10 +197,6 @@ def data_init(self): case 2: # replace geom file from selected profile. aw.db.update_data("ModelProfile", self.args.name, "model_name", "imported_file_id", self.md5) - # case 2: # yaml file provided - # # self.data = DeepMapParamModel(yaml_parser(self.args.yaml_dir)) - # data = DeepMapParamModel(yaml_parser(self.args.yaml_dir)) - # return data match self.indicator[0]: case 0: # model_name provided match self.indicator[1]: @@ -256,29 +249,39 @@ def data_init(self): if self.args.import_dir is not None: path_valid_check(self.args.import_dir) - case 3: # Create a new model profile with given parameters. - query_m = aw.db.query_data("ModelProfile", self.args.name, "model_name") - if query_m.empty: - aw.db.insert_data("ModelProfile", {"model_name": self.args.name}) - if self.args.geom_param is not None: - input_data = {} - input_data2 = {} - collect = [] - collect2 = [] - have_data, diff,_,_ = aw.db.have_data_in_db("ModelParameter", "param_name", self.args.geom_param) - if not have_data: - for param in diff: - input_data.update({"param_name": param}) - collect.append(copy.copy(input_data)) - aw.db.insert_data("ModelParameter", collect, True) - for param in self.args.geom_param: - input_data2.update({"param_name": param, - "model_name": self.args.name}) - collect2.append(copy.copy(input_data2)) - aw.db.insert_data("ParameterToProfile", collect2, True) + case 1: # Create a new model profile with given parameters. + query_m = aw.db.query_data("ModelProfile", self.args.name, "model_name") + if query_m.empty: + aw.db.insert_data("ModelProfile", {"model_name": self.args.name}) + match self.indicator[1]: + case 3: + input_data = {} + input_data2 = {} + collect = [] + collect2 = [] + have_data, diff,_,_ = aw.db.have_data_in_db("ModelParameter", "param_name", self.args.geom_param) + if not have_data: + for param in diff: + input_data.update({"param_name": param}) + collect.append(copy.copy(input_data)) + aw.db.insert_data("ModelParameter", collect, True) + for param in self.args.geom_param: + input_data2.update({"param_name": param, + "model_name": self.args.name}) + collect2.append(copy.copy(input_data2)) + aw.db.insert_data("ParameterToProfile", collect2, True) case 3: # Draft mode. - pass + if self.args.geom_param is not None: + self.draft_pm = MapParamModel(self.args.geom_param, self.args.geom_param_value) + else: + self.draft_pm = MapParamModel({}) + if self.args.import_dir is not None: + fmt = path_valid_check(self.args.import_dir, format=["stp", "step","stl","STL"]) + if fmt.lower() == "stl": + self.draft_pm.imported_model = aw.tool.read_stl(self.args.import_dir) + else: + self.draft_pm.imported_model = aw.tool.read_step(self.args.import_dir) case 4: # Download files: if len(self.args.download) == 1: @@ -288,13 +291,15 @@ def data_init(self): #TODO download files from several tasks aw.tool.download(file_dir=self.args.db_file_dir, output_dir=self.args.db_opt_dir, time_range=[self.args.download[0], self.args.download[1]]) - if (self.indicator[4] == 1) and (self.args.geom_param_value is not None) and (self.args.geom_param is not None): + if (self.indicator[4] == 1): + # and (self.args.geom_param_value is not None) and (self.args.geom_param is not None): return 0 - # elif ((self.indicator[0:2] == [0,4]) or (self.indicator[0:2] == [0,0])) and ((self.indicator[2:4] == [1,0]) or (self.indicator[2:4] == [1,1])): - elif self.indicator in self.oil: + elif (self.import_fl is not None) and self.args.geom_param is None: self.onlyimport = True - self.load_geom_file() + # self.load_geom_file() return 2 + elif self.indicator[0] == 3: + return 3 else: return 1 @@ -345,13 +350,14 @@ def task_handler(self): elif self.args.remove: indicator[1] = 2 #[0,2,0,0,0] elif self.args.geom_param is not None: - indicator[1] = 3 #[0,3,0,0,0] + indicator[0] = 1 + indicator[1] = 3 #[1,3,0,0,0] if self.args.geom_param_value is not None: - indicator[4] = 1 #[0,3,0,0,1] + indicator[4] = 1 #[1,3,0,0,1] else: - indicator[1] = 4 #[0,4,0,0,0] + indicator[0] = 1 #[1,0,0,0,0] if self.args.import_dir != None: - indicator[2] = 1 + indicator[2] = 1 #[-,-,1,0,0] self.impt_format = path_valid_check(self.args.import_dir, format=["stp", "step","stl","STL"]) self.isimport = True self.impt_filename = aw.tool.get_filename(self.args.import_dir) @@ -361,30 +367,19 @@ def task_handler(self): q_filename = result.filename[0] self.impt_filename = q_filename if self.args.remove: - indicator[3] = 2 + indicator[3] = 2 #[-,-,1,2,0] else: indicator[3] = 0 print(f"Got same file {q_filename} in db, using existing file now...") else: - indicator[3] = 1 + indicator[3] = 1 #[-,-,1,1,0] aw.tool.upload(self.args.import_dir, self.args.import_file_dir) aw.db.insert_data("ImportedFile",{"filename": self.impt_filename, "md5_id": self.md5}) - if (indicator[0:2] != [0,3]) and (indicator[0:2] != [0,4]) and (indicator[0] == 0): + if (indicator[0] == 0): #profile info exists while a file needs to be imported. aw.db.update_data("ModelProfile",self.args.name, "model_name", "imported_file_id", self.md5) else: aw.db.insert_data("ModelProfile", {"model_name": self.args.name,"imported_file_id": self.md5}) - # elif (indicator[0:2] == [0,4]) and result.loc["imported_file_id"][0] is not None: - # self.md5 = result.loc["imported_file_id"][0] - # query_f = aw.db.query_data("ImportedFile", by_name=self.md5, column_name="md5_id") - # if not query_f.empty: - # q_filename = query_f.filename[0] - # self.impt_filename = q_filename - - # else: - # raise InsufficientDataException() - # elif self.args.yaml_dir is not None: - # indicator = (2,0,0) else: q0 = aw.db.query_data("ParameterToProfile") q1 = aw.db.query_data("ModelParameter") @@ -395,8 +390,6 @@ def task_handler(self): print(f"\033[1mModel Profile\033[0m:\n{q2}\n\033[1mModel Parameters\033[0m:\n{q1}\n\033[1mParameter Properties\033[0m:\n{q0}\n\033[1mImported Files\033[0m:\n{q4}\n\033[1mTask\033[0m:\n{q3}") else: print("No model profile found.") - - # raise InsufficientDataException() return indicator def load_geom_file(self): @@ -410,4 +403,7 @@ def auto_download(self): aw.tool.download(file_dir=self.args.db_file_dir, output_dir=self.args.db_opt_dir, task_id=self.task_id) else: print(f"\033[1mGeometries Creation seems not working properly, downloading abort.\033[0m") + + def create_draft(self): + self.draft = self.geometry_spawn(self.draft_pm) diff --git a/amworkflow/src/geometries/composite_geometry.py b/amworkflow/src/geometries/composite_geometry.py index 7003786..f806c86 100644 --- a/amworkflow/src/geometries/composite_geometry.py +++ b/amworkflow/src/geometries/composite_geometry.py @@ -1,10 +1,17 @@ -from amworkflow.src.geometries.simple_geometry import create_edge, create_wire, create_face, create_prism, random_polygon_constructor, angle_of_two_arrays, laterality_indicator, angular_bisector -from amworkflow.src.geometries.operator import reverse, geom_copy, translate, rotate_face, fuser, hollow_carver, cutter3D +from amworkflow.src.geometries.simple_geometry import create_edge, create_wire, create_face, create_prism, random_polygon_constructor, angle_of_two_arrays, laterality_indicator, angular_bisector, p_center_of_mass, linear_interpolate +from amworkflow.src.geometries.operator import reverse, geom_copy, translate, rotate_face, fuser, hollow_carver, cutter3D, bender +from amworkflow.src.geometries.property import topo_explorer, p_bounding_box +from amworkflow.src.geometries.builder import solid_maker from OCC.Core.gp import gp_Pnt import numpy as np -from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Shell +from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Shell, TopoDS_Shape +from OCC.Core.BRepFilletAPI import BRepFilletAPI_MakeFillet from amworkflow.src.geometries.property import get_face_center_of_mass from amworkflow.src.geometries.builder import sewer +import matplotlib.pyplot as plt +from matplotlib.patches import Polygon +import itertools +import copy as cp @@ -180,7 +187,262 @@ def create_sym_hexagon1_infill(total_len: float, total_wid:float, height:float, p0 = [0,th * 0.5] p1 = [] -def create_wall_by_points(pts:list, th: float, isclose:bool, height: float = None, debug: bool = False, output: str = "prism") -> np.ndarray or TopoDS_Face or TopoDS_Shell: +# def create_wall_by_points(pts:list, th: float, isclose:bool, height: float = None, debug: bool = False, debug_type: str = "linear", output: str = "prism", interpolate:float = None, R: float = None) -> np.ndarray or TopoDS_Face or TopoDS_Shell: +# """ +# @brief Create a prism wall by points. It takes a list of points as a skeleton of a central path and then build a strip or a loop. +# @param pts list of 2D points that define the wall. The algorithm can compute points in 3D theoretically but the result may make no sense. +# @param th thickness of the wall. +# @param isclose True if the wall is closed (loop) +# @param height height of the wall if a prism is needed. +# @param debug if True output two groups of points for plotting. +# @param output selecting result intended to output. can be varied among "face" and "prism". +# @return two arrays or a face or a prism. +# """ +# is_loop = False +# pts = [np.array(list(i.Coord())) if isinstance(i, gp_Pnt) else np.array(i) for i in pts] +# if R is not None: +# pts = polygon_interpolater(pts, interpolate) +# bender(pts, R) +# pts = [i for i in pts] +# th *= 0.5 +# opt_pts = [] +# vecs = [] +# dir_vecs = [] +# ths = [] +# lft_pts = [] +# rgt_pts = [] +# segaments = [] +# for i,p in enumerate(pts): +# if i != len(pts) - 1: +# a1 = pts[i+1] - pts[i] +# if i == 0: +# if isclose: +# dr = angular_bisector(pts[-1] - p, a1) +# # ang = angle_of_two_arrays(dir_vecs[i-1],dr) +# ang2 = angle_of_two_arrays(laterality_indicator(p - pts[-1], True), dr) +# ang_th = ang2 +# if ang2 > np.pi / 2: +# dr *= -1 +# ang_th = np.pi - ang2 +# nth = np.abs(th / np.cos(ang_th)) +# else: +# dr = laterality_indicator(a1, True) +# nth = th +# else: +# dr = angular_bisector(-vecs[i-1], a1) +# ang2 = angle_of_two_arrays(laterality_indicator(vecs[i-1], True), dr) +# ang_th = ang2 +# if ang2 > np.pi / 2: +# dr *= -1 +# ang_th = np.pi - ang2 +# nth = np.abs(th / np.cos(ang_th)) +# else: +# if isclose: +# a1 = pts[0] - pts[i] +# dr = angular_bisector(-vecs[i-1], a1) +# ang2 = angle_of_two_arrays(laterality_indicator(vecs[i-1], True), dr) +# ang_th = ang2 +# if ang2 > np.pi / 2: +# dr *= -1 +# ang_th = np.pi - ang2 +# nth = np.abs(th / np.cos(ang_th)) +# else: +# dr = laterality_indicator(a1, True) +# nth = th +# vecs.append(a1) +# ths.append(nth) +# dir_vecs.append(dr) +# lft_pts.append(dr * nth + p) +# opt_pts.append(dr * nth + p) +# if isclose: +# lft_pts.append(lft_pts[0]) +# for i,p in enumerate(pts[::-1]): +# dr = -dir_vecs[::-1][i] +# nth = ths[::-1][i] +# rgt_pts.append(dr * nth + p) +# if isclose: +# rgt_pts.append(rgt_pts[0]) +# pts.append(pts[0]) +# #find bounding box if bending needed +# if R is not None: +# mxpt, mnpt = p_bounding_box(lft_pts+rgt_pts) +# # Deal with overlapping sides. +# lft_pts = break_overlap(lft_pts) +# rgt_pts = break_overlap(rgt_pts) +# loops_lft_pt_i, peak_lft_pt_i = find_loop(lft_pts) +# loops_rgt_pt_i, peak_rgt_pt_i = find_loop(rgt_pts) +# if (len(loops_lft_pt_i+peak_lft_pt_i)>0) or (len(loops_rgt_pt_i+peak_rgt_pt_i)>0): +# is_loop = True +# loops_lft = index2array(loops_lft_pt_i, lft_pts) +# loops_rgt = index2array(loops_rgt_pt_i, rgt_pts) +# loops = loops_lft+loops_rgt +# # if interpolate is not None: +# # for i in range(len(loops)): +# # loops[i] = polygon_interpolater(loops[i], interpolate) +# # if R is not None: +# # bender(loops[i], mxpt, mnpt) +# loops_h = find_topology(loops) + +# if debug: +# if debug_type == "polygon": +# fig, ax = plt.subplots() +# for polygon in loops: +# poly2d = np.array([[i[0], i[1]] for i in polygon]) +# polygon_patch = Polygon(poly2d, closed=True, fill=False, edgecolor='black') +# ax.add_patch(polygon_patch) +# ax.set_xlabel('X-axis') +# ax.set_ylabel('Y-axis') +# ax.set_xlim(-200, 200) +# ax.set_ylim(-200, 200) +# ax.set_title('Visualization of Polygons') +# plt.show() +# elif debug_type == "linear": +# plt.figure(figsize=(8, 6)) # Optional: Set the figure size +# output1 = np.array(lft_pts) +# output2 = np.array(rgt_pts) +# talist = np.array(pts).T +# toutput1 = output1.T +# toutput2 = output2.T +# x1 = talist[0] +# y1 = talist[1] +# x2 = toutput1[0] +# y2 = toutput1[1] +# x3 = toutput2[0] +# y3 = toutput2[1] +# plt.plot(x1, y1, 'bo-', label='central path') +# # Plot Group 2 points and connect with lines in red +# plt.plot(x2, y2, 'ro-', label='outer line') +# # Plot Group 3 points and connect with lines in green +# plt.plot(x3, y3, 'go-', label='inner line') +# # Add labels and a legend +# for i in range(x2.shape[0]): +# plt.text(x2[i], y2[i], str(i)) +# for i in range(x3.shape[0]): +# plt.text(x3[i], y3[i], str(i)) + +# plt.xlabel('X-axis') +# plt.ylabel('Y-axis') +# plt.legend() +# plt.axis('equal') +# plt.grid(True) # Optional: Add grid lines +# plt.show() +# else: +# if not is_loop: +# gp_pts_lft = [gp_Pnt(i[0],i[1],i[2]) for i in lft_pts] +# gp_pts_rgt = [gp_Pnt(i[0],i[1],i[2]) for i in rgt_pts] +# gp_pts = [gp_Pnt(i[0],i[1],i[2]) for i in opt_pts] +# poly = random_polygon_constructor(gp_pts) +# else: +# for i,v in enumerate(loops_h): +# for j,vv in enumerate(loops_h[i]): +# loops_h[i][j] = list(loops_h[i][j]) +# for ind,k in enumerate(loops_h[i][j]): +# loops_h[i][j][ind] = gp_Pnt(k[0], k[1], k[2]) +# poly0 = random_polygon_constructor(loops_h[0][0]) +# poly_r = poly0 +# for i, h in enumerate(loops_h): +# if i == 0: +# continue +# for j in h: +# poly_c = random_polygon_constructor(j) +# poly_r = cutter3D(poly_r, poly_c) +# poly = poly_r +# match output: +# case "face": +# if not isclose: +# return poly +# else: +# return poly +# case "prism": +# pr = create_prism(poly, [0,0,height],True) +# # top = geom_copy(poly) +# # translate(top,[0,0,height]) +# # opt = sewer([top,pr,poly]) +# # print(opt) +# pr_face = topo_explorer(pr,"face") +# pr_remake = sewer(pr_face) +# opt = solid_maker(pr_remake) +# return opt + +def find_intersect(lines: np.ndarray) -> np.ndarray: + parallel = False + coplanarity = False + l1, l2 = lines + pt1, pt2 = l1 + pt3, pt4 = l2 + L1 = (pt2 - pt1) / np.linalg.norm(pt2 - pt1) + L2 = (pt4 - pt3) / np.linalg.norm(pt4 - pt3) + V1 = pt4 - pt1 + if np.linalg.norm(L1 - L2, 1) < 1e-8: + parallel = True + print("Two lines are parallel.") + return np.full((3,1), np.nan) + indicate = np.linalg.det(np.array([V1, L1, L2])) + if np.abs(indicate) < 1e-8: + coplanarity = True + else: + print("lines are not in the same plane.") + return np.full((3,1), np.nan) + if coplanarity and not parallel: + pt5_pt4 = np.linalg.norm(np.cross(V1, L1)) + theta = np.arccos(np.dot(L1, L2)) + o_pt5 = pt5_pt4 / np.tan(theta) + o_pt4 = pt5_pt4 / np.sin(theta) + pt1_pt4 = np.linalg.norm(V1) + V1_n = V1 / pt1_pt4 + cos_beta = np.dot(V1_n, L1) + pt1_pt5 = pt1_pt4 * cos_beta + o_pt1 = pt1_pt5 - o_pt5 + o = L1 * o_pt1 + pt1 + return o + +def index2array(ind: list, array: np.ndarray): + real_array = [] + for i,v in enumerate(ind): + item = [] + for j, vv in enumerate(v): + item.append(array[vv]) + real_array.append(item) + return real_array + + +def polygon_interpolater(plg: np.ndarray, step_len: float = None, num: int = None, isclose: bool = True): + def deter_dum(line: np.ndarray): + ratio = step_len / np.linalg.norm(line[0]-line[1]) + if ratio > 0.75: + num = 0 + elif (ratio > 0.4) and (ratio <= 0.75): + num = 1 + elif (ratio > 0.3) and (ratio <= 0.4): + num = 2 + elif (ratio > 0.22) and (ratio <= 0.3): + num = 3 + elif (ratio > 0.19) and (ratio <= 0.22): + num = 4 + elif (ratio > 0.14) and (ratio <= 0.19): + num = 5 + elif ratio <=0.14: + num = 7 + return num + new_plg = plg + pos = 0 + n = 1 + if not isclose: + n = 2 + for i, pt in enumerate(plg): + if i == len(plg) - n: + break + line = np.array([pt, plg[i+1]]) + if num is not None: + p_num = num + else: + p_num = deter_dum(line) + insert_p = linear_interpolate(line, p_num) + new_plg = np.concatenate((new_plg[:pos+1],insert_p, new_plg[pos+1:])) + pos +=p_num+1 + return new_plg + +# def create_wall_by_points2(pts:list, th: float, isclose:bool, height: float = None, debug: bool = False, output: str = "prism", interpolate:float = None, R: float = None) -> np.ndarray or TopoDS_Face or TopoDS_Shell: """ @brief Create a prism wall by points. It takes a list of points as a skeleton of a central path and then build a strip or a loop. @param pts list of 2D points that define the wall. The algorithm can compute points in 3D theoretically but the result may make no sense. @@ -191,13 +453,18 @@ def create_wall_by_points(pts:list, th: float, isclose:bool, height: float = Non @param output selecting result intended to output. can be varied among "face" and "prism". @return two arrays or a face or a prism. """ + pts = [np.array(list(i.Coord())) if isinstance(i, gp_Pnt) else np.array(i) for i in pts] + if R is not None: + pts = polygon_interpolater(pts, interpolate) + bender(pts, R) + pts = [i for i in pts] + th += 0.1 th *= 0.5 - opt_pts = [] vecs = [] dir_vecs = [] ths = [] - opt_pts_0 = [] - opt_pts_1 = [] + lft_pts = [] + rgt_pts = [] for i,p in enumerate(pts): if i != len(pts) - 1: a1 = pts[i+1] - pts[i] @@ -238,53 +505,307 @@ def create_wall_by_points(pts:list, th: float, isclose:bool, height: float = Non vecs.append(a1) ths.append(nth) dir_vecs.append(dr) - opt_pts_0.append(dr * nth + p) - opt_pts.append(dr * nth + p) + lft_pts.append(dr * nth + p) + rgt_pts.append(-dr * nth + p) if isclose: - for i,p in enumerate(pts): - dr = -dir_vecs[i] - nth = ths[i] - opt_pts_1.append(dr * nth + p) - else: - for i,p in enumerate(pts[::-1]): - dr = -dir_vecs[::-1][i] - nth = ths[::-1][i] - if debug: - opt_pts_1.append(dr * nth + p) - else: - opt_pts.append(dr * nth + p) - if debug: - return np.array(opt_pts_0), np.array(opt_pts_1) - else: - gp_pts_0 = [gp_Pnt(i[0],i[1],i[2]) for i in opt_pts_0] - gp_pts_1 = [gp_Pnt(i[0],i[1],i[2]) for i in opt_pts_1] - gp_pts = [gp_Pnt(i[0],i[1],i[2]) for i in opt_pts] - if not isclose: - poly = random_polygon_constructor(gp_pts) + lft_pts.append(lft_pts[0]) + if isclose: + rgt_pts.append(rgt_pts[0]) + pts.append(pts[0]) + fc_set = [] + for i in range(len(lft_pts)-1): + opts = lft_pts + ipts = rgt_pts + opt1 = gp_Pnt(opts[i][0], opts[i][1], opts[i][2]) + opt2 = gp_Pnt(opts[i+1][0], opts[i+1][1], opts[i+1][2]) + ipt2 = gp_Pnt(ipts[i][0], ipts[i][1], ipts[i][2]) + ipt1 = gp_Pnt(ipts[i+1][0], ipts[i+1][1], ipts[i+1][2]) + fc = random_polygon_constructor([opt1, opt2, ipt1, ipt2]) + fc_set.append(fc) + face = fc_set[0] + for fce in fc_set[1:]: + face = fuser(face, fce) + return create_prism(face, [0,0,height]) + +class CreateWallByPoints(): + def __init__(self, pts: list, th: float, height: float): + self.pts = [np.array(list(i.Coord())) if isinstance(i, gp_Pnt) else np.array(i) for i in pts] + self.height = height + self.is_loop = False + self.R = None + self.interpolate = 6 + self.th = th + self.is_close = False + self.vecs = [] + self.dir_vecs = [] + self.ths = [] + self.lft_pts = [] + self.rgt_pts = [] + self.loops_h = [] + self.loops = [] + self.poly: TopoDS_Shape + + def create_polygon(self): + if not self.is_loop: + gp_pts_lft = [gp_Pnt(i[0],i[1],i[2]) for i in self.lft_pts] + gp_pts_rgt = [gp_Pnt(i[0],i[1],i[2]) for i in self.rgt_pts] + self.gp_pts = gp_pts_lft + gp_pts_rgt + self.poly = random_polygon_constructor(self.gp_pts) else: - poly_o = random_polygon_constructor(gp_pts_0) - poly_i = random_polygon_constructor(gp_pts_1) - poly = cutter3D(poly_o, poly_i) - match output: - case "face": - if not isclose: - return poly - else: - return poly - case "prism": - pr = create_prism(poly, [0,0,height],True) - return pr + for i,v in enumerate(self.loops_h): + for j,vv in enumerate(self.loops_h[i]): + self.loops_h[i][j] = list(self.loops_h[i][j]) + for ind,k in enumerate(self.loops_h[i][j]): + self.loops_h[i][j][ind] = gp_Pnt(k[0], k[1], k[2]) + poly0 = random_polygon_constructor(self.loops_h[0][0]) + poly_r = poly0 + for i, h in enumerate(self.loops_h): + if i == 0: + continue + for j in h: + poly_c = random_polygon_constructor(j) + poly_r = cutter3D(poly_r, poly_c) + self.poly = poly_r - + def Shape(self): + self.create_sides() + self.create_loop() + self.create_polygon() + if np.isclose(self.height, 0): + opt = self.poly + else: + pr = create_prism(self.poly, [0,0,self.height],True) + pr_face = topo_explorer(pr,"face") + pr_remake = sewer(pr_face) + opt = solid_maker(pr_remake) + return opt - + def create_sides(self): + if self.R is not None: + self.pts = polygon_interpolater(self.pts, self.interpolate) + bender(self.pts, self.R) + self.pts = [i for i in self.pts] + self.th *= 0.5 + for i,p in enumerate(self.pts): + if i != len(self.pts) - 1: + a1 = self.pts[i+1] - self.pts[i] + if i == 0: + if self.is_close: + dr = angular_bisector(self.pts[-1] - p, a1) + # ang = angle_of_two_arrays(dir_vecs[i-1],dr) + ang2 = angle_of_two_arrays(laterality_indicator(p - self.pts[-1], True), dr) + ang_th = ang2 + if ang2 > np.pi / 2: + dr *= -1 + ang_th = np.pi - ang2 + nth = np.abs(self.th / np.cos(ang_th)) + else: + dr = laterality_indicator(a1, True) + nth = self.th + else: + dr = angular_bisector(-self.vecs[i-1], a1) + ang2 = angle_of_two_arrays(laterality_indicator(self.vecs[i-1], True), dr) + ang_th = ang2 + if ang2 > np.pi / 2: + dr *= -1 + ang_th = np.pi - ang2 + nth = np.abs(self.th / np.cos(ang_th)) + else: + if self.is_close: + a1 = self.pts[0] - self.pts[i] + dr = angular_bisector(-self.vecs[i-1], a1) + ang2 = angle_of_two_arrays(laterality_indicator(self.vecs[i-1], True), dr) + ang_th = ang2 + if ang2 > np.pi / 2: + dr *= -1 + ang_th = np.pi - ang2 + nth = np.abs(self.th / np.cos(ang_th)) + else: + dr = laterality_indicator(a1, True) + nth = self.th + self.vecs.append(a1) + self.ths.append(nth) + self.dir_vecs.append(dr) + self.lft_pts.append(dr * nth + p) + if self.is_close: + self.lft_pts.append(self.lft_pts[0]) + for i,p in enumerate(self.pts[::-1]): + dr = -self.dir_vecs[::-1][i] + nth = self.ths[::-1][i] + self.rgt_pts.append(dr * nth + p) + if self.is_close: + self.rgt_pts.append(self.rgt_pts[0]) + self.pts.append(self.pts[0]) + def create_loop(self): + if self.R is not None: + mxpt, mnpt = p_bounding_box(self.lft_pts+self.rgt_pts) + self.lft_pts = self.break_overlap(self.lft_pts) + self.rgt_pts = self.break_overlap(self.rgt_pts) + loops_lft_pt_i, peak_lft_pt_i = self.find_loop(self.lft_pts) + loops_rgt_pt_i, peak_rgt_pt_i = self.find_loop(self.rgt_pts) + if (len(loops_lft_pt_i+peak_lft_pt_i)>0) or (len(loops_rgt_pt_i+peak_rgt_pt_i)>0): + self.is_loop = True + self.loops_lft = index2array(loops_lft_pt_i, self.lft_pts) + self.loops_rgt = index2array(loops_rgt_pt_i, self.rgt_pts) + self.loops = self.loops_lft+self.loops_rgt + self.loops_h = self.find_topology(self.loops) - - - - - - - + def is_include(self, point: np.ndarray, polygon: np.ndarray) -> bool: + polygon = np.concatenate((polygon, np.array([polygon[0]]))) + ang = 0 + for i,v in enumerate(polygon): + if i == polygon.shape[0]-1: + break + v1 = np.array(v-point) + v2 = np.array(polygon[i+1]-point) + # if np.isclose(np.linalg.norm(v1),0,1e-8) or np.isclose(np.linalg.norm(v2),0,1e-8): + # continue + crt = angle_of_two_arrays(v1, v2) + ang += crt + if np.isclose(ang, 2*np.pi, 1e-2): + return True + else: return False + + def find_topology(self, loops: list) -> list: + ck_lst = [] + loop_with_hierarchy = [] + topo = np.zeros(len(loops)) + for i,lp in enumerate(loops): + p = lp[0] + for j, llpp in enumerate(loops): + if (i == j) or ((i,j) in ck_lst): + continue + else: + if self.is_include(p, llpp): + topo[i] += 1 + elif self.is_include(llpp[0], lp): + topo[j] += 1 + ck_lst.append((i,j)) + ck_lst.append((j,i)) + lyr_mx = int(np.max(topo)) + for i in range(lyr_mx+1): + loop_h = [loops[j] for j,v in enumerate(topo) if v == i] + loop_with_hierarchy.append(loop_h) + return loop_with_hierarchy + + def break_overlap(self, pts: np.ndarray) -> np.ndarray: + n_pts = np.copy(pts) + inst_p = [] + # otr_vecs = np.array([pts[i+1] - pts[i] if i != len(pts)-1 else pts[0] - pts[i] for i in range(len(pts))]) + ck_lst = [] + m = len(pts) + i = 0 + pos = 0 + while i < m: + for ind,v in enumerate(pts): + if i == m-1: + next_p = pts[0] + else: + next_p = pts[i+1] + if (np.linalg.norm(pts[i] - v, 1) < 1e-8) or (np.linalg.norm(next_p - v, 1) < 1e-8): + continue + else: + comb = (i, ind, i+1) + if comb in ck_lst: + continue + else: + if i == m - 1: + half1 = v - pts[i] + half2 = pts[0] - v + else: + half1 = v - pts[i] + half2 = pts[i+1] - v + coline = np.isclose(np.linalg.norm(np.cross(half1, half2)), 0, 1e-8) + same_dir = np.dot(half1, half2) > 0 + if coline and same_dir: + inst_p.append(v) + comb_f = list(itertools.permutations(cp.copy(comb))) + ck_lst += comb_f + inst_p = np.array(inst_p) + icr_pos = inst_p.shape[0] + if icr_pos != 0: + dist = [[np.linalg.norm(p-pts[i]), p] for p in inst_p] + def sort_ind(lst): + return lst[0] + std_inst_p = np.array([i[1] for i in sorted(dist, key=sort_ind)]) + n_pts = np.concatenate((n_pts[:pos+1], std_inst_p, n_pts[pos+1:])) + pos += icr_pos + i +=1 + pos+=1 + inst_p = [] + return n_pts + + def find_loop(self, pts: np.ndarray) -> tuple: + pts = np.array([np.array(list(i.Coord())) if isinstance(i, gp_Pnt) else np.array(i) for i in pts]) + peak_p = [] + dim = pts.shape[0] + distance = np.ones((dim, dim)) * np.nan + for i, p in enumerate(pts): + for j in range(i+1,dim,1): + dist = np.linalg.norm(p - pts[j]) + distance[i,j] = dist + knots = np.array(np.where(distance < 1e-6)).T + loop_l = [] + for i in range(len(knots)-1): + half1 = np.arange(knots[i, 0], knots[i+1, 0]+1) + half2 = np.arange(knots[i+1, 1]+1, knots[i, 1]) + loop = np.concatenate((half1, half2)) + if loop.shape[0] < 3: + if loop.shape[0] == 2: + if np.isclose(np.linalg.norm(pts[knots[i, 0]] - pts[loop[1]+2]), 0, 1e-8): + print(loop) + peak_p.append(loop[1]) + continue + else: + loop_l.append(loop) + lst_loop = np.arange(knots[-1,0], knots[-1,1]) + if lst_loop.shape[0] >=3: + loop_l.append(lst_loop) + return loop_l, peak_p + + def visualize(self, plot_type: str) -> None: + self.Shape() + if plot_type == "polygon": + fig, ax = plt.subplots() + for polygon in self.loops: + poly2d = np.array([[i[0], i[1]] for i in polygon]) + polygon_patch = Polygon(poly2d, closed=True, fill=False, edgecolor='black') + ax.add_patch(polygon_patch) + ax.set_xlabel('X-axis') + ax.set_ylabel('Y-axis') + ax.set_xlim(-200, 200) + ax.set_ylim(-200, 200) + ax.set_title('Visualization of Polygons') + plt.show() + elif plot_type == "linear": + plt.figure(figsize=(8, 6)) # Optional: Set the figure size + output1 = np.array(self.lft_pts) + output2 = np.array(self.rgt_pts) + talist = np.array(self.pts).T + toutput1 = output1.T + toutput2 = output2.T + x1 = talist[0] + y1 = talist[1] + x2 = toutput1[0] + y2 = toutput1[1] + x3 = toutput2[0] + y3 = toutput2[1] + plt.plot(x1, y1, 'bo-', label='central path') + # Plot Group 2 points and connect with lines in red + plt.plot(x2, y2, 'ro-', label='outer line') + # Plot Group 3 points and connect with lines in green + plt.plot(x3, y3, 'go-', label='inner line') + # Add labels and a legend + for i in range(x2.shape[0]): + plt.text(x2[i], y2[i], str(i)) + for i in range(x3.shape[0]): + plt.text(x3[i], y3[i], str(i)) + + plt.xlabel('X-axis') + plt.ylabel('Y-axis') + plt.legend() + plt.axis('equal') + plt.grid(True) # Optional: Add grid lines + plt.show() \ No newline at end of file diff --git a/amworkflow/src/geometries/mesher.py b/amworkflow/src/geometries/mesher.py index 261d0d1..e8ffa84 100644 --- a/amworkflow/src/geometries/mesher.py +++ b/amworkflow/src/geometries/mesher.py @@ -1,10 +1,11 @@ import gmsh import math as m -from OCC.Core.TopoDS import TopoDS_Shape +from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Solid from amworkflow.src.geometries.operator import split from amworkflow.src.geometries.operator import get_occ_bounding_box from amworkflow.src.constants.exceptions import GmshUseBeforeInitializedException import logging +import psutil def gmsh_switch(s: bool) -> None: if s: @@ -28,6 +29,8 @@ def mesher(item: TopoDS_Shape, gmsh.is_initialized() except: raise GmshUseBeforeInitializedException() + if not isinstance(item, TopoDS_Solid): + raise Exception("Must be Solid object to mesh.") if layer_type: geo = split(item=item, split_z=True, @@ -37,9 +40,12 @@ def mesher(item: TopoDS_Shape, split_z=True, nz = layer_param) model = gmsh.model() + threads_count = psutil.cpu_count() + gmsh.option.setNumber("General.NumThreads",threads_count) model.add(model_name) v = get_geom_pointer(model, geo) model.occ.synchronize() + gmsh.fltk.run() for layer in v: model.add_physical_group(3,[layer[1]], name=f"layer{layer[1]}") phy_gp = model.getPhysicalGroups() diff --git a/amworkflow/src/geometries/operator.py b/amworkflow/src/geometries/operator.py index 6bb68f2..45dd4d9 100644 --- a/amworkflow/src/geometries/operator.py +++ b/amworkflow/src/geometries/operator.py @@ -1,13 +1,13 @@ from OCC.Core.gp import gp_Pnt, gp_Trsf, gp_Vec from OCC.Core.TopLoc import TopLoc_Location from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Copy -from OCC.Core.TopoDS import TopoDS_Shape +from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Wire import numpy as np import OCC.Core.BRepBuilderAPI as BRepBuilderAPI import OCC.Core.gp as gp from OCC.Core.gp import gp_Pln from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Ax1, gp_Dir -from OCC.Core.BOPAlgo import BOPAlgo_Builder +from OCC.Core.BOPAlgo import BOPAlgo_Builder, BOPAlgo_MakerVolume, BOPAlgo_Splitter from OCCUtils.Topology import Topo from OCCUtils.Construct import make_face from OCCUtils.Construct import vec_to_dir @@ -20,8 +20,10 @@ from amworkflow.src.geometries.property import get_face_center_of_mass from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform, BRepBuilderAPI_Sewing -from amworkflow.src.geometries.property import get_occ_bounding_box - +from amworkflow.src.geometries.property import get_occ_bounding_box, topo_explorer +from amworkflow.src.geometries.builder import geometry_builder +from amworkflow.src.utils.writer import stl_writer +from OCC.Core.TopTools import TopTools_ListOfShape def translate(item: TopoDS_Shape, vector: list): """ @@ -79,15 +81,18 @@ def split(item: TopoDS_Shape, plan_len = 1.2 * max(abs(xmin - xmax), abs(ymin - ymax)) z = zmax - zmin if nz != None: - z_list = np.linspace(0, z, nz) + z_list = np.linspace(zmin, z, nz) if layer_thickness != None: - z_list = np.arange(0, z, layer_thickness) - bo = BOPAlgo_Builder() + z_list = np.arange(zmin, z, layer_thickness) + z_list = np.concatenate((z_list,np.array([z]))) + # bo = BOPAlgo_Builder() + # bo = BOPAlgo_MakerVolume() + bo = BOPAlgo_Splitter() bo.AddArgument(item) for i in z_list: - p1, v1 = gp_Pnt(0,0,i), gp_Vec(0, 0, -1) + p1, v1 = gp_Pnt(0,0,i), gp_Vec(0, 0, 1) fc1 = make_face(gp_Pln(p1, vec_to_dir(v1)), -plan_len, plan_len, -plan_len, plan_len) - bo.AddArgument(fc1) + bo.AddTool(fc1) if ny!= None: y = ymax - ymin y_list = np.linspace(0, y, ny) @@ -157,7 +162,7 @@ def hollow_carver(face: TopoDS_Shape, factor: float): cut = BRepAlgoAPI_Cut(face, cutter).Shape() return cut -def rotate_face(shape: TopoDS_Shape, angle: float, axis: str = "z"): +def rotate_face(shape: TopoDS_Shape, angle: float, axis: str = "z", cnt: tuple = None): """ @brief Rotate the topography by the given angle around the center of mass of the face. @param shape TopoDS_Shape to be rotated. @@ -166,7 +171,8 @@ def rotate_face(shape: TopoDS_Shape, angle: float, axis: str = "z"): @return the rotated shape. """ transform = gp_Trsf() - cnt = get_face_center_of_mass(shape, gp_pnt=True) + if cnt is None: + cnt = get_face_center_of_mass(shape, gp_pnt=True) match axis: case "z": ax = gp_Ax1(cnt, gp_Dir(0,0,1)) @@ -198,6 +204,16 @@ def cutter3D(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: comm = BRepAlgoAPI_Cut(shape1, shape2) return comm.Shape() +def split2(item: TopoDS_Shape, *tools: TopoDS_Shape) -> TopoDS_Compound: + top_list = TopTools_ListOfShape() + for i in tools: + top_list.Append(i) + cut = BOPAlgo_Splitter() + print(tools) + cut.SetArguments(top_list) + cut.Perform() + return cut.Shape() + def common(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: """ @brief Common between two TopoDS_Shapes. The result is a shape that has all components of shape1 and shape2 @@ -206,4 +222,52 @@ def common(shape1: TopoDS_Shape, shape2: TopoDS_Shape) -> TopoDS_Shape: @return the common shape or None if there is no common shape between shape1 and shape2 in the sense that both shapes are """ comm = BRepAlgoAPI_Common(shape1, shape2) - return comm.Shape() \ No newline at end of file + return comm.Shape() + +def get_boundary(item: TopoDS_Shape) -> TopoDS_Wire: + bbox = get_occ_bounding_box(item) + edge = topo_explorer(item, "edge") #get all edges from imported model. + xx = [] + yy = [] + #select all edges on the boundary. + for e in edge: + xmin, ymin, zmin, xmax, ymax, zmax = get_occ_bounding_box(e) # get bounding box of an edge + if (ymin + ymax < 1e-3) or (abs((ymin + ymax)*0.5 - bbox[4]) < 1e-3): # if the edge is either + xx.append(e) + if (xmin + xmax < 1e-3) or (abs((xmin + xmax)*0.5 - bbox[3]) < 1e-3): + yy.append(e) + edges = xx + yy + #build a compound of all edges + wire = geometry_builder(edges) + return wire + +def bender(point_cordinates, radius: float = None, mx_pt: np.ndarray = None, mn_pt: np.ndarray = None): + coord_t = np.array(point_cordinates).T + if mx_pt is None: + mx_pt = np.max(coord_t,1) + if mn_pt is None: + mn_pt = np.min(coord_t,1) + cnt = 0.5 * (mn_pt + mx_pt) + scale = np.abs(mn_pt-mx_pt) + if radius is None: + radius = scale[1] * 2 + o_y = scale[1]*0.5 + radius + for pt in point_cordinates: + xp = pt[0] + yp = pt[1] + ratio_l = xp / scale[0] + ypr = scale[1] * 0.5 - yp + Rp = radius + ypr + ly = scale[0] * (1 + ypr / radius) + lp = ratio_l * ly + thetp = lp / (Rp) + thetp = lp / (Rp) + pt[0] = Rp * np.sin(thetp) + pt[1] = o_y - Rp*np.cos(thetp) + +def array_project(array: np.ndarray, direct: np.ndarray) -> np.ndarray: + ''' + Project an array to the specified direction. + ''' + direct = direct / np.linalg.norm(direct) + return np.dot(array, direct)*direct \ No newline at end of file diff --git a/amworkflow/src/geometries/property.py b/amworkflow/src/geometries/property.py index 5a13d0d..6bf145f 100644 --- a/amworkflow/src/geometries/property.py +++ b/amworkflow/src/geometries/property.py @@ -9,6 +9,8 @@ from OCC.Core.gp import gp_Pnt from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_EDGE, TopAbs_FACE, TopAbs_WIRE, TopAbs_SHELL, TopAbs_FORWARD, TopAbs_SOLID, TopAbs_COMPOUND +from OCCUtils.Topology import Topo +import numpy as np def get_face_center_of_mass(face: TopoDS_Face, gp_pnt: bool = False) -> tuple: """ @@ -108,4 +110,14 @@ def topo_explorer(shape: TopoDS_Shape, shape_type: str) -> list: while explorer.More(): result.append(explorer.Current()) explorer.Next() - return result \ No newline at end of file + return result + +def traverser(item: TopoDS_Shape) -> Topo: + return Topo(item) + +def p_bounding_box(pts: list): + pts = np.array(pts) + coord_t = np.array(pts).T + mx_pt = np.max(coord_t,1) + mn_pt = np.min(coord_t,1) + return mx_pt, mn_pt \ No newline at end of file diff --git a/amworkflow/src/geometries/simple_geometry.py b/amworkflow/src/geometries/simple_geometry.py index 0552736..c4a7c14 100644 --- a/amworkflow/src/geometries/simple_geometry.py +++ b/amworkflow/src/geometries/simple_geometry.py @@ -1,10 +1,11 @@ from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Wire, TopoDS_Shell, TopoDS_Solid, TopoDS_Face, TopoDS_Edge, topods_Compound from OCC.Core.Geom import Geom_TrimmedCurve -from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Ax2, gp_Dir -from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeFace, BRepBuilderAPI_Sewing, BRepBuilderAPI_MakeSolid, BRepBuilderAPI_MakeShell, brepbuilderapi_Precision,BRepBuilderAPI_MakePolygon +from OCC.Core.gp import gp_Pnt, gp_Vec, gp_Ax2, gp_Dir, gp_Pln +from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeWire, BRepBuilderAPI_MakeFace, BRepBuilderAPI_Sewing, BRepBuilderAPI_MakeSolid, BRepBuilderAPI_MakeShell, brepbuilderapi_Precision, BRepBuilderAPI_MakePolygon from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakePrism, BRepPrimAPI_MakeCylinder from amworkflow.src.geometries.operator import geom_copy, translate, reverse +from OCCUtils.Construct import make_face from amworkflow.src.geometries.builder import geometry_builder, sewer from OCC.Core.GC import GC_MakeArcOfCircle @@ -13,9 +14,10 @@ from OCCUtils.Topology import Topo import numpy as np -def create_box(length: float, - width: float, - height: float, + +def create_box(length: float, + width: float, + height: float, radius: float = None, alpha: float = None, shell: bool = False) -> TopoDS_Shape: @@ -36,14 +38,14 @@ def create_box(length: float, print(isinstance(faces[0], TopoDS_Shape)) sewed_face = sewer(faces) # make_shell = BRepBuilderAPI_MakeShell(sewed_face, False).Shell() - + return sewed_face else: return BRepPrimAPI_MakeBox(length, width, height).Shape() else: # The alpha of the circle. if alpha == None: - alpha = (length / radius)% (m.pi * 2) + alpha = (length / radius) % (m.pi * 2) R = radius + (width / 2) r = radius - (width / 2) p1 = gp_Pnt(0, 0, 0) @@ -58,7 +60,8 @@ def create_box(length: float, arch_edge3_4 = BRepBuilderAPI_MakeEdge(arch3_4.Value()).Edge() edge2 = BRepBuilderAPI_MakeEdge(p2, p3).Edge() edge4 = BRepBuilderAPI_MakeEdge(p4, p1).Edge() - wire = BRepBuilderAPI_MakeWire(arch_edge1_2, edge2, arch_edge3_4, edge4).Wire() + wire = BRepBuilderAPI_MakeWire( + arch_edge1_2, edge2, arch_edge3_4, edge4).Wire() wire_top = geom_copy(wire) translate(wire_top, [0, 0, height]) prism = create_prism(wire, [0, 0, height], True) @@ -80,6 +83,7 @@ def create_box(length: float, else: return solid + def create_cylinder(radius: float, length: float) -> TopoDS_Shape: """ @brief Create a cylinder shape. This is a convenience function for BRepPrimAPI_MakeCylinder @@ -89,6 +93,7 @@ def create_cylinder(radius: float, length: float) -> TopoDS_Shape: """ return BRepPrimAPI_MakeCylinder(radius, length).Shape() + def create_prism(shape: TopoDS_Shape, vector: list, copy: bool = True) -> TopoDS_Shell: @@ -100,10 +105,15 @@ def create_prism(shape: TopoDS_Shape, @return return the prism """ return BRepPrimAPI_MakePrism(shape, gp_Vec(vector[0], - vector[1], - vector[2]), + vector[1], + vector[2]), copy).Shape() + +def create_prism_by_curve(shape: TopoDS_Shape, curve: TopoDS_Wire): + return BRepPrimAPI_MakePrism(shape, curve).Shape() + + def create_face(wire: TopoDS_Wire) -> TopoDS_Face: """ @brief Create a BRep face from a TopoDS_Wire. This is a convenience function to use : func : ` BRepBuilderAPI_MakeFace ` and @@ -112,6 +122,7 @@ def create_face(wire: TopoDS_Wire) -> TopoDS_Face: """ return BRepBuilderAPI_MakeFace(wire).Face() + def create_wire(*edge) -> TopoDS_Wire: """ @brief Create a wire. Input at least one edge to build a wire. This is a convenience function to call BRepBuilderAPI_MakeWire with the given edge and return a wire. @@ -119,6 +130,7 @@ def create_wire(*edge) -> TopoDS_Wire: """ return BRepBuilderAPI_MakeWire(*edge).Wire() + def create_edge(pnt1: gp_Pnt = None, pnt2: gp_Pnt = None, arch: Geom_TrimmedCurve = None) -> TopoDS_Edge: """ @brief Create an edge between two points. This is a convenience function to be used in conjunction with : func : ` BRepBuilderAPI_MakeEdge ` @@ -132,7 +144,8 @@ def create_edge(pnt1: gp_Pnt = None, pnt2: gp_Pnt = None, arch: Geom_TrimmedCurv elif isinstance(arch, Geom_TrimmedCurve): edge = BRepBuilderAPI_MakeEdge(arch).Edge() return edge - + + def create_arch(pnt1, pnt2, pnt1_2, make_edge: bool = True) -> TopoDS_Edge: """ @brief Create an arc of circle. If make_edge is True the arc is created in TopoDS_Edge. @@ -145,10 +158,11 @@ def create_arch(pnt1, pnt2, pnt1_2, make_edge: bool = True) -> TopoDS_Edge: arch = GC_MakeArcOfCircle(pnt1, pnt1_2, pnt2).Value() # Create an edge if make_edge is true. if make_edge: - return create_edge(arch) + return create_edge(arch) else: return arch + def create_wire_by_points(points: list): """ @brief Create a closed wire (loop) by points. The wire is defined by a list of points which are connected by an edge. @@ -160,18 +174,19 @@ def create_wire_by_points(points: list): for i in range(len(pts)): # Create a wire for the i th point. if i == 0: - edge = create_edge(pts[i],pts[i+1]) + edge = create_edge(pts[i], pts[i+1]) wire = create_wire(edge) # Create a wire for the given points. if i != len(pts)-1: - edge = create_edge(pts[i],pts[i+1]) + edge = create_edge(pts[i], pts[i+1]) wire = create_wire(wire, edge) else: - edge = create_edge(pts[i],pts[0]) + edge = create_edge(pts[i], pts[0]) wire = create_wire(wire, edge) return wire -def random_polygon_constructor(points:list, isface: bool = True) -> TopoDS_Face or TopoDS_Wire: + +def random_polygon_constructor(points: list, isface: bool = True) -> TopoDS_Face or TopoDS_Wire: """ @brief Creates a polygon in any shape. If isface is True the polygon is made face - oriented otherwise it is wires @param points List of points defining the polygon @@ -190,7 +205,8 @@ def random_polygon_constructor(points:list, isface: bool = True) -> TopoDS_Face else: return pb.Wire() -def angle_of_two_arrays(a1:np.ndarray, a2:np.ndarray, rad: bool = True) -> float: + +def angle_of_two_arrays(a1: np.ndarray, a2: np.ndarray, rad: bool = True) -> float: """ @brief Returns the angle between two vectors. This is useful for calculating the rotation angle between a vector and another vector @param a1 1D array of shape ( n_features ) @@ -200,28 +216,31 @@ def angle_of_two_arrays(a1:np.ndarray, a2:np.ndarray, rad: bool = True) -> float """ dot = np.dot(a1, a2) norm = np.linalg.norm(a1)*np.linalg.norm(a2) + cos_value = np.round(dot / norm, 15) if rad: - return np.arccos(dot / norm) + return np.arccos(cos_value) else: - return np.rad2deg(np.arccos(dot / norm)) + return np.rad2deg(np.arccos(cos_value)) + -def laterality_indicator(a: np.ndarray, d:bool): +def laterality_indicator(a: np.ndarray, d: bool): """ @brief Compute laterality indicator of a vector. This is used to create a vector which is perpendicular to the based vector on its left side ( d = True ) or right side ( d = False ) @param a vector ( a ) @param d True if on left or False if on right @return A vector. """ - z = np.array([0,0,1]) + z = np.array([0, 0, 1]) # cross product of z and a if d: - na = np.cross(z,a) + na = np.cross(z, a) else: - na = np.cross(-z,a) + na = np.cross(-z, a) norm = np.linalg.norm(na, na.shape[0]) return na / norm -def angular_bisector(a1:np.ndarray, a2:np.ndarray) -> np.ndarray: + +def angular_bisector(a1: np.ndarray, a2: np.ndarray) -> np.ndarray: """ @brief Angular bisector between two vectors. The result is a vector splitting the angle between two vectors uniformly. @param a1 1xN numpy array @@ -234,7 +253,55 @@ def angular_bisector(a1:np.ndarray, a2:np.ndarray) -> np.ndarray: norm3 = np.linalg.norm(bst) # The laterality indicator a2 norm3 norm3 if norm3 == 0: - opt = laterality_indicator(a2,True) + opt = laterality_indicator(a2, True) else: opt = bst / norm3 return opt + + +def p_translate(pts: np.ndarray, direct: np.ndarray) -> np.ndarray: + pts = np.array([np.array(list(i.Coord())) if isinstance( + i, gp_Pnt) else np.array(i) for i in pts]) + pts = [i + direct for i in pts] + return list(pts) + + +def p_center_of_mass(pts: np.ndarray) -> np.ndarray: + pts = np.array([np.array(list(i.Coord())) if isinstance( + i, gp_Pnt) else np.array(i) for i in pts]) + + return np.mean(pts.T, axis=1) + + +def p_rotate(pts: np.ndarray, angle_x: float = 0, angle_y: float = 0, angle_z: float = 0, cnt: np.ndarray = None) -> np.ndarray: + pts = np.array([np.array(list(i.Coord())) if isinstance( + i, gp_Pnt) else np.array(i) for i in pts]) + com = p_center_of_mass(pts) + if cnt is None: + cnt = np.array([0, 0, 0]) + t_vec = cnt - com + pts += t_vec + rot_x = np.array([[1, 0, 0], + [0, np.cos(angle_x), -np.sin(angle_x)], + [0, np.sin(angle_x), np.cos(angle_x)]]) + rot_y = np.array([[np.cos(angle_y), 0, np.sin(angle_y)], + [0, 1, 0], + [-np.sin(angle_y), np.cos(angle_y), 0]]) + rot_z = np.array([[np.cos(angle_z), -np.sin(angle_z), 0], + [np.sin(angle_z), np.cos(angle_z), 0], + [0, 0, 1]]) + R = rot_x@rot_y@rot_z + rt_pts = pts@R + r_pts = rt_pts - t_vec + return r_pts + +def create_face_by_plane(pln: gp_Pln, *vt: gp_Pnt) -> TopoDS_Face: + return make_face(pln, *vt) + +def linear_interpolate(pts: np.ndarray, num: int): + for i, pt in enumerate(pts): + if i == len(pts)-1: + break + else: + interpolated_points = np.linspace(pt, pts[i+1], num=num+2)[1:-1] + return interpolated_points \ No newline at end of file diff --git a/amworkflow/src/interface/cli/cli_workflow.py b/amworkflow/src/interface/cli/cli_workflow.py index da3f4ee..a43dbb5 100644 --- a/amworkflow/src/interface/cli/cli_workflow.py +++ b/amworkflow/src/interface/cli/cli_workflow.py @@ -4,7 +4,7 @@ def cli(): parser.add_argument("-gp", "--geom_param", nargs="+") parser.add_argument("-gpv", "--geom_param_value", nargs="+", type=float) parser.add_argument("-ip", "--iter_param", nargs="*", type=int) - parser.add_argument("-mbl", "--mesh_by_layer", nargs="?", type=float) + parser.add_argument("-mbl", "--mesh_by_layer", nargs="?", type=int) parser.add_argument("-mbt", "--mesh_by_thickness", nargs="?", type=float) parser.add_argument("-msf", "--mesh_size_factor", nargs="?", type=float) parser.add_argument("-stlad", "--stl_angular_deflect", nargs="?", type=float) diff --git a/amworkflow/src/interface/gui/Qt/draft_ui.py b/amworkflow/src/interface/gui/Qt/draft_ui.py new file mode 100644 index 0000000..415261b --- /dev/null +++ b/amworkflow/src/interface/gui/Qt/draft_ui.py @@ -0,0 +1,105 @@ +##Author github user @Tanneguydv, 2021 + +import os +import sys +from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox +from PyQt5.QtCore import QObject, QThread, pyqtSignal +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QPushButton, + QHBoxLayout, + QGroupBox, + QDialog, + QVBoxLayout, +) +from OCC.Display.backend import load_backend +import importlib +load_backend("qt-pyqt5") +import OCC.Display.qtDisplay as qtDisplay + +class Worker(QObject): + def __init__(self, geometry): + super().__init__() + self.geometry = geometry + + finished = pyqtSignal() + reload_flow = pyqtSignal() + + def run(self): + """Reload geom function from script.""" + self.geometry.create_draft() + self.reload_flow.emit(self.geometry.draft) + self.finished.emit() + +class App(QDialog): + def __init__(self, geometry, workflow): + super().__init__() + self.title = "PyQt5 / pythonOCC" + self.left = 300 + self.top = 300 + self.width = 1500 + self.height = 800 + self.geometry = geometry + self.workflow = workflow + self.initUI() + + def initUI(self): + self.setWindowTitle(self.title) + self.setGeometry(self.left, self.top, self.width, self.height) + self.createVerticalLayout() + + windowLayout = QVBoxLayout() + windowLayout.addWidget(self.verticalGroupBox) + self.setLayout(windowLayout) + self.show() + + def createVerticalLayout(self): + self.verticalGroupBox = QGroupBox("Display PythonOCC") + layout = QVBoxLayout() + + disp = QPushButton("Display Geometry", self) + disp.clicked.connect(self.displayGEOM) + disp.setGeometry(50, 10, 50, 30) + layout.addWidget(disp) + + eras = QPushButton("Erase Geometry", self) + eras.clicked.connect(self.eraseGEOM) + layout.addWidget(eras) + + self.canvas = qtDisplay.qtViewer3d(self) + self.canvas.resize(1400,700) + layout.addWidget(self.canvas) + + parent_rect = self.rect() + canvas_rect = self.canvas.rect() + + # Calculate the center position within the parent's dimensions + center_x = (parent_rect.width() - canvas_rect.width()) // 2 + center_y = (parent_rect.height() - canvas_rect.height()) // 2 + + # Set the center position for the canvas + self.canvas.move(center_x, center_y) + self.canvas.InitDriver() + self.display = self.canvas._display + self.verticalGroupBox.setLayout(layout) + + def displayGEOM(self): + self.workflow.geometry_spawn = self.geometry + self.workflow.create_draft() + self.draft = self.workflow.draft + self.geom_display = self.display.DisplayShape(self.draft)[0] + self.display.FitAll() + + def eraseGEOM(self): + if hasattr(self, "geom_display"): + self.display.Context.Erase(self.geom_display, True) + +def draft_ui(func: callable, workflow: callable): + app = QApplication(sys.argv) + gui = App(func,workflow) + sys.exit(app.exec_()) +# if __name__ == "__main__": +# app = QApplication(sys.argv) +# ex = App() +# sys.exit(app.exec_()) \ No newline at end of file diff --git a/amworkflow/src/interface/gui/Qt/main_window.py b/amworkflow/src/interface/gui/Qt/main_window.py deleted file mode 100644 index e69de29..0000000 diff --git a/dodo.py b/dodo.py index ada8087..4d3f27d 100644 --- a/dodo.py +++ b/dodo.py @@ -46,7 +46,7 @@ def scpt_ctr(case_name): n_dir = os.path.join(ucs, case_name) os.mkdir(n_dir) with open(f"{n_dir}/{case_name}.py", "w") as opt: - opt.write("from amworkflow.api import amWorkflow as aw\n@aw.engine.amworkflow()\ndef geometry_spawn(pm):\n#This is where to define your model.\n\nreturn #TopoDS_Shape") + opt.write("from amworkflow.api import amWorkflow as aw\n@aw.engine.amworkflow()\ndef geometry_spawn(pm):\n#This is where to define your model.\n\n return #TopoDS_Shape") else: print(f"{case_name} already exists, please input a new name.") diff --git a/examples/pwall/pwall.py b/examples/pwall/pwall.py new file mode 100644 index 0000000..f030489 --- /dev/null +++ b/examples/pwall/pwall.py @@ -0,0 +1,54 @@ +from amworkflow.api import amWorkflow as aw +import numpy as np +@aw.engine.amworkflow("draft") +def geometry_spawn(pm): +#This is where to define your model. + th = pm.thickness + l = pm.length + height = pm.height*3 + g = aw.geom + hth = th * 0.5 + display = True + p0 = g.pnt(0, hth, 0) + p1 = g.pnt(l * 0.5, hth) + p2 = g.pnt(l, (np.sqrt(3) * l) * 0.5 + hth) + p3 = g.pnt(2 * l, (np.sqrt(3) * l) * 0.5 + hth) + p4 = g.pnt(5 * l * 0.5, hth) + pu = [p0, p1, p2, p3, p4] #one unit of the points _/-\ + alist = np.array([list(i.Coord()) for i in pu]) # get the coord from the points + put1 = g.p_translate(pu, [3 * l, 0, 0]) # Translate the unit _/-\_/-\ + end_p = np.copy(put1[-1]) + end_p[0] += l * 0.5 # Add one point to make half of the infill _/-\_/-\_ + pm = pu + put1 # integrate the points together + pm.append(end_p) # add the point + # pm_cnt = g.p_center_of_mass(pm) + # pm_cnt[0] -=hth + pmr = g.p_rotate(pm, angle_z=np.pi) # Rotate the half infill to make it upside down + # pmr = g.p_translate(pmr, np.array([-th,0,0])) + cnt2 = g.p_center_of_mass(pmr) #Get the center of mass of all points + t_len = cnt2[1] * 2 #2 times the y coord would be the length for translation + pmrt = g.p_translate(pmr, [0, -t_len, 0]) + pm_lt = np.vstack((alist, put1)) + pm_lt = np.vstack((pm_lt, np.array(end_p))) + pmf = np.vstack((pm_lt, pmrt)) + p5 = g.pnt(0, -(1.5*th + (np.sqrt(3) * l) * 0.5)) # create points for the outerline + p6 = g.pnt(6 * l + th, -(1.5*th + (np.sqrt(3) * l) * 0.5)) + p7 = g.pnt(6 * l + th, (1.5*th + (np.sqrt(3) * l) * 0.5)) + p8 = g.pnt(0, (1.5*th + (np.sqrt(3) * l) * 0.5)) + pout = [p5, p6, p7, p8] + pout_nd = [i.Coord() for i in pout] + pmfo = np.vstack((pmf, pout_nd)) + pmfo_cnt = g.p_center_of_mass(pmfo) + # pmfo = g.p_rotate(pmfo, angle_z=np.pi, cnt = pmfo_cnt) + # pmfo = g.p_translate(pmfo, np.array([6 * l + th,0,0])) + wall_maker = g.CreateWallByPoints(pmfo, th, height) + wall_maker.is_close = True + # wall_maker.visualize("linear") + p = wall_maker.Shape() + return p#TopoDS_Shape + +# Info : 1st: [3536, 12, 23374] #15 +# Info : 2nd: [3536, 12, 13031] #8 +# Info : The dihedral angle between them is 6.0792e-05 degree. +# Info : Hint: You may use Mesh.AngleToleranceFacetOverlap to decrease the dihedral angle tolerance 0.1 (degree) +# Error : Invalid boundary mesh (overlapping facets) on surface 15 surface 8 \ No newline at end of file diff --git a/examples/pwall/tips.txt b/examples/pwall/tips.txt new file mode 100644 index 0000000..ca20f60 --- /dev/null +++ b/examples/pwall/tips.txt @@ -0,0 +1,6 @@ +You could use the command +python pwall.py -n pwall -gp thickness length height -gpv 8 20 30 +You may also notice that there is a parameter "draft" in the decorator @aw.engine.amworkflow("draft") +This will trigger a QT5 window and you can preview your design after clicking "Display Geometry". +In draft mode no data will be stored in db. +To create a real model, simply delete the string "draft". \ No newline at end of file