from __future__ import annotations import argparse from datetime import datetime from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent DEFAULT_OUTPUT = PROJECT_ROOT / "assets" / "models" / "cube_10mm.step" def write_cube_step(path: Path, size: float = 10.0) -> None: entities: list[str] = [] def add(text: str) -> int: index = len(entities) + 1 entities.append(f"#{index}={text};") return index def ref(index: int) -> str: return f"#{index}" def refs(indices) -> str: return ",".join(ref(index) for index in indices) app_ctx = add("APPLICATION_CONTEXT('automotive_design')") add(f"APPLICATION_PROTOCOL_DEFINITION('international standard','automotive_design',2000,{ref(app_ctx)})") prod_ctx = add(f"PRODUCT_CONTEXT('',{ref(app_ctx)},'mechanical')") product = add( "PRODUCT('CUBE_10MM','CUBE_10MM','Simple 10 mm cube generated for edge editing tests'," f"({ref(prod_ctx)}))" ) formation = add( f"PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE('1','generated',{ref(product)},.NOT_KNOWN.)" ) pd_ctx = add(f"PRODUCT_DEFINITION_CONTEXT('part definition',{ref(app_ctx)},'design')") product_def = add(f"PRODUCT_DEFINITION('design','',{ref(formation)},{ref(pd_ctx)})") product_shape = add(f"PRODUCT_DEFINITION_SHAPE('','',{ref(product_def)})") length_unit = add("(LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.))") angle_unit = add("(NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.))") solid_angle_unit = add("(NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT())") uncertainty = add( f"UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-6),{ref(length_unit)}," "'distance_accuracy_value','')" ) geom_context = add( "(GEOMETRIC_REPRESENTATION_CONTEXT(3) " f"GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT(({ref(uncertainty)})) " f"GLOBAL_UNIT_ASSIGNED_CONTEXT(({refs([length_unit, angle_unit, solid_angle_unit])})) " "REPRESENTATION_CONTEXT('3D Context',''))" ) point_ids: dict[str, int] = {} for name, xyz in { "P1": (0.0, 0.0, 0.0), "P2": (size, 0.0, 0.0), "P3": (size, size, 0.0), "P4": (0.0, size, 0.0), "P5": (0.0, 0.0, size), "P6": (size, 0.0, size), "P7": (size, size, size), "P8": (0.0, size, size), }.items(): point_ids[name] = add("CARTESIAN_POINT('',({:.6f},{:.6f},{:.6f}))".format(*xyz)) directions = { "+X": add("DIRECTION('',(1.000000,0.000000,0.000000))"), "-X": add("DIRECTION('',(-1.000000,0.000000,0.000000))"), "+Y": add("DIRECTION('',(0.000000,1.000000,0.000000))"), "-Y": add("DIRECTION('',(0.000000,-1.000000,0.000000))"), "+Z": add("DIRECTION('',(0.000000,0.000000,1.000000))"), "-Z": add("DIRECTION('',(0.000000,0.000000,-1.000000))"), } world_axis = add( f"AXIS2_PLACEMENT_3D('',{ref(point_ids['P1'])},{ref(directions['+Z'])},{ref(directions['+X'])})" ) vertex_ids = { name.replace("P", "V"): add(f"VERTEX_POINT('',{ref(point_id)})") for name, point_id in point_ids.items() } edge_specs = { "E1": ("V1", "V2", "P1", "+X"), "E2": ("V2", "V3", "P2", "+Y"), "E3": ("V3", "V4", "P3", "-X"), "E4": ("V4", "V1", "P4", "-Y"), "E5": ("V5", "V6", "P5", "+X"), "E6": ("V6", "V7", "P6", "+Y"), "E7": ("V7", "V8", "P7", "-X"), "E8": ("V8", "V5", "P8", "-Y"), "E9": ("V1", "V5", "P1", "+Z"), "E10": ("V2", "V6", "P2", "+Z"), "E11": ("V3", "V7", "P3", "+Z"), "E12": ("V4", "V8", "P4", "+Z"), } edge_ids: dict[str, int] = {} for name, (start, end, start_point, direction) in edge_specs.items(): vector = add(f"VECTOR('',{ref(directions[direction])},1.000000)") line = add(f"LINE('',{ref(point_ids[start_point])},{ref(vector)})") edge_ids[name] = add( f"EDGE_CURVE('{name}',{ref(vertex_ids[start])},{ref(vertex_ids[end])},{ref(line)},.T.)" ) def oriented(edge_name: str, same: bool) -> int: orientation = ".T." if same else ".F." return add(f"ORIENTED_EDGE('',*,*,{ref(edge_ids[edge_name])},{orientation})") def make_plane(origin_point: str, normal: str, ref_dir: str) -> int: axis = add( f"AXIS2_PLACEMENT_3D('',{ref(point_ids[origin_point])}," f"{ref(directions[normal])},{ref(directions[ref_dir])})" ) return add(f"PLANE('',{ref(axis)})") faces: list[int] = [] face_specs = [ ("BOTTOM_Z0", [("E4", False), ("E3", False), ("E2", False), ("E1", False)], "P1", "-Z", "+X"), ("TOP_Z10", [("E5", True), ("E6", True), ("E7", True), ("E8", True)], "P5", "+Z", "+X"), ("FRONT_Y0", [("E1", True), ("E10", True), ("E5", False), ("E9", False)], "P1", "-Y", "+X"), ("BACK_Y10", [("E12", True), ("E7", False), ("E11", False), ("E3", True)], "P4", "+Y", "+X"), ("LEFT_X0", [("E9", True), ("E8", False), ("E12", False), ("E4", True)], "P1", "-X", "+Y"), ("RIGHT_X10", [("E2", True), ("E11", True), ("E6", False), ("E10", False)], "P2", "+X", "+Y"), ] for name, loop_edges, plane_origin, normal, ref_dir in face_specs: loop = add(f"EDGE_LOOP('',({refs(oriented(edge, same) for edge, same in loop_edges)}))") bound = add(f"FACE_OUTER_BOUND('',{ref(loop)},.T.)") plane = make_plane(plane_origin, normal, ref_dir) faces.append(add(f"ADVANCED_FACE('{name}',({ref(bound)}),{ref(plane)},.T.)")) closed_shell = add(f"CLOSED_SHELL('',({refs(faces)}))") solid = add(f"MANIFOLD_SOLID_BREP('CUBE_10MM',{ref(closed_shell)})") shape_rep = add( f"ADVANCED_BREP_SHAPE_REPRESENTATION('',({refs([world_axis, solid])}),{ref(geom_context)})" ) add(f"SHAPE_DEFINITION_REPRESENTATION({ref(product_shape)},{ref(shape_rep)})") add(f"PRODUCT_RELATED_PRODUCT_CATEGORY('part','',({ref(product)}))") header = "\n".join( [ "ISO-10303-21;", "HEADER;", "FILE_DESCRIPTION(('Simple 10 mm cube generated by Python for edge edit tests'),'2;1');", ( f"FILE_NAME('{path.name}','{datetime.now().isoformat(timespec='seconds')}'," "('Codex'),('OpenAI'),'Python ASCII STEP generator','python-occt','');" ), "FILE_SCHEMA(('AUTOMOTIVE_DESIGN_CC2'));", "ENDSEC;", "DATA;", ] ) path.write_text(header + "\n" + "\n".join(entities) + "\nENDSEC;\nEND-ISO-10303-21;\n", encoding="utf-8") def main() -> int: parser = argparse.ArgumentParser(description="Generate a simple STEP cube for editor tests.") parser.add_argument("output", nargs="?", default=str(DEFAULT_OUTPUT), help="Output STEP file path.") parser.add_argument("--size", type=float, default=10.0, help="Cube edge length in millimeters.") args = parser.parse_args() output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) write_cube_step(output, args.size) print(f"wrote {output.resolve()} ({output.stat().st_size} bytes)") print(f"cube bounds: 0,0,0 to {args.size:g},{args.size:g},{args.size:g} mm") return 0 if __name__ == "__main__": raise SystemExit(main())