172 lines
7.2 KiB
Python
172 lines
7.2 KiB
Python
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())
|