Files
boltgen/tests/test_geometry.py
OpenClaw Coder 0945496611
CI / test (3.10) (push) Canceled after 0s
CI / test (3.11) (push) Canceled after 0s
CI / test (3.12) (push) Canceled after 0s
CI / build-check (push) Canceled after 0s
boltgen: expand bolts/nuts/washers to M27/M30; fix count assertions (167 bolts, 9 nuts, 9 washers)
2026-11-08 05:02:40 +10:00

131 lines
4.3 KiB
Python

"""Property-based / фаззер-тесты геометрии boltgen (требуют cadquery).
Случайные валидные спецификации -> генерация STEP -> проверка, что модель
невырожденная (объем > 0, все размеры в разумных пределах, файл валиден).
Ловит вырожденные модели: нулевая длина, отверстие > внешнего диаметра и т.п.
Зависит от cadquery -> pytest собирает этот файл в «skip», если cadquery недоступен.
"""
from __future__ import annotations
import os
import random
import sys
import tempfile
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
import pytest
# cadquery опционален: без него весь этот файл пропускается.
try:
import cadquery as cq # noqa: F401
from boltgen.model import (
generate_bolt,
generate_nut,
generate_washer,
)
from boltgen.standard import (
BOLT_ALLOWED_LENGTHS,
BOLT_SIZES,
NUT_SIZES,
WASHER_SIZES,
BoltSpec,
NutSpec,
WasherSpec,
get_bolt,
get_nut,
get_washer,
iter_bolts,
)
HAS_CQ = True
except ImportError:
HAS_CQ = False
pytestmark = pytest.mark.skipif(not HAS_CQ, reason="requires cadquery")
_RANDOM = random.Random(20261107) # детерминированный seed
def _random_bolt() -> BoltSpec:
size = _RANDOM.choice(list(BOLT_SIZES))
lengths = (
[l for l in BOLT_ALLOWED_LENGTHS if l <= 60]
if size == "M6"
else list(BOLT_ALLOWED_LENGTHS)
)
return get_bolt(size, _RANDOM.choice(lengths))
def _read_step_volume(path: str) -> float:
"""Объем STEP-модели через OCP.STEPReader + BRepGProp. Проверка > 0."""
from OCP.STEPControl import STEPControl_Reader
reader = STEPControl_Reader()
reader.ReadFile(path)
ok = reader.TransferRoots()
assert ok, f"STEP не загрузился: {path}"
shape = reader.Shape()
from OCP.BRepGProp import BRepGProp
from OCP.GProp import GProp_GProps
props = GProp_GProps()
BRepGProp.VolumeProperties_s(shape, props)
volume = props.Mass()
assert volume > 0, f"Модель {path} имеет нулевой/отрицательный объем {volume}"
return volume
# =====Болты ======
@pytest.mark.parametrize("count", [25])
def test_random_bolts_have_positive_volume(count):
with tempfile.TemporaryDirectory() as d:
for _ in range(count):
spec = _random_bolt()
out = os.path.join(d, "b.step")
generate_bolt(spec, out)
assert os.path.exists(out) and os.path.getsize(out) > 1000
vol = _read_step_volume(out)
d_mm = float(spec.size[1:])
# тело болта: стержень d×L + головка — минимум объем стержня
assert vol > 0.5 * d_mm * d_mm * spec.length
# =====Гайки/шайбы по всем размерам ======
@pytest.mark.parametrize("size", list(NUT_SIZES))
def test_random_nuts_valid(size):
with tempfile.TemporaryDirectory() as d:
out = os.path.join(d, "n.step")
generate_nut(get_nut(size), out)
assert os.path.getsize(out) > 1000
_read_step_volume(out)
@pytest.mark.parametrize("size", list(WASHER_SIZES))
def test_random_washers_valid(size):
with tempfile.TemporaryDirectory() as d:
out = os.path.join(d, "w.step")
generate_washer(get_washer(size), out)
assert os.path.getsize(out) > 1000
_read_step_volume(out)
# =====Границы валидации (ни одна модель не должна порождаться некорректной) ======
def test_iter_bolts_all_generate(tmp_path):
"""Полное семейство генерируется без исключений (регресс по всей сетке)."""
count = 0
for spec in iter_bolts():
out = str(tmp_path / ("b_" + spec.size + "_" + str(spec.length) + ".step"))
generate_bolt(spec, out)
assert os.path.getsize(out) > 1000
count += 1
assert count == 167