Estructura inicial del proyecto
Contrato entre motor y aplicación en motor/api.py, motor simulado con CUBE01, pruebas del contrato, prototipo de la prueba técnica, documentación de trabajo y plantillas de Gitea.
This commit is contained in:
commit
4503df96bd
|
|
@ -0,0 +1,10 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Planos y modelos: Git no debe tocar sus finales de línea ni mostrarlos como diff
|
||||||
|
*.dxf binary
|
||||||
|
*.dwg binary
|
||||||
|
*.pdf binary
|
||||||
|
*.step binary
|
||||||
|
*.stp binary
|
||||||
|
*.stl binary
|
||||||
|
*.png binary
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
name: "Error"
|
||||||
|
about: "Algo no funciona como debería"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Qué pasa
|
||||||
|
|
||||||
|
## Qué debería pasar
|
||||||
|
|
||||||
|
## Cómo reproducirlo
|
||||||
|
|
||||||
|
- Plano usado (adjuntar o ruta en `pruebas/catalogo/`):
|
||||||
|
- Pasos:
|
||||||
|
|
||||||
|
## Gravedad
|
||||||
|
|
||||||
|
bloqueante / alta / media / baja
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
name: "Tarea"
|
||||||
|
about: "Trabajo planificado del backlog"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
## Criterio de terminado
|
||||||
|
|
||||||
|
- [ ]
|
||||||
|
|
||||||
|
## Área
|
||||||
|
|
||||||
|
motor / app / pruebas / docs
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
## Qué cambia
|
||||||
|
|
||||||
|
## Cómo se ha probado
|
||||||
|
|
||||||
|
- [ ] `uv run pytest` pasa
|
||||||
|
- [ ] Probado a mano (explicar):
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
|
||||||
|
Closes #
|
||||||
|
|
||||||
|
## ¿Cambia el contrato `motor/api.py`?
|
||||||
|
|
||||||
|
- [ ] No
|
||||||
|
- [ ] Sí, acordado y anotado en `docs/decisiones.md`
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.venv/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Empaquetado
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Resultados generados al probar (el catálogo sí se versiona)
|
||||||
|
salidas/
|
||||||
|
*_informe.md
|
||||||
|
*_3d.png
|
||||||
|
|
||||||
|
# Sistema y editores
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Cómo trabajamos
|
||||||
|
|
||||||
|
## Ramas
|
||||||
|
|
||||||
|
- `main` siempre funciona: nadie sube directamente a ella. Todo entra mediante *pull request* revisado por la otra persona.
|
||||||
|
- Una rama por tarea, creada desde `main` actualizada, con el prefijo del área:
|
||||||
|
- `motor/...`: procesado (p. ej. `motor/lectura-dwg`)
|
||||||
|
- `app/...`: interfaz y visor (p. ej. `app/visor-ejes`)
|
||||||
|
- `pruebas/...`, `docs/...`
|
||||||
|
- Ramas cortas: mejor varios PR pequeños que uno enorme. Antes de abrir el PR, actualizar la rama con `main`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
git switch -c app/visor-ejes
|
||||||
|
# ... trabajo y commits ...
|
||||||
|
git push -u origin app/visor-ejes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
En español, en imperativo y diciendo qué cambia: `Añade lectura de capas ocultas`, `Corrige escala en planos sin unidades`. Un commit por cambio con sentido propio.
|
||||||
|
|
||||||
|
## Issues y tablero
|
||||||
|
|
||||||
|
- Cada tarea es un *issue* con etiqueta de área (`motor`, `app`, `pruebas`, `docs`) y asignado a quien la hace.
|
||||||
|
- El tablero del proyecto en Gitea tiene las columnas **Pendiente · En curso · En revisión · Hecho**.
|
||||||
|
- Los errores se registran como *issue* con la plantilla de error: son evidencia para el informe de calidad.
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
- La descripción dice qué cambia, cómo se ha probado y qué *issue* cierra (`Closes #12`).
|
||||||
|
- `uv run pytest` debe pasar antes de pedir revisión.
|
||||||
|
- La otra persona revisa y aprueba. La revisión cruzada es obligatoria: los dos tenemos que entender todo el código para la defensa.
|
||||||
|
|
||||||
|
## Contrato motor/aplicación
|
||||||
|
|
||||||
|
`motor/api.py` es lo único que comparten las dos partes. Cualquier cambio en él se acuerda antes, se anota en `docs/decisiones.md` y va en un PR propio.
|
||||||
|
|
||||||
|
## Documentación continua
|
||||||
|
|
||||||
|
- **Decisiones** (`docs/decisiones.md`): cada decisión de diseño o de alcance, con motivo.
|
||||||
|
- **Bitácora** (`docs/bitacora.md`): qué ha hecho cada uno cada semana.
|
||||||
|
- **Actas** (`docs/actas/`): una por reunión semanal.
|
||||||
|
- **Uso de IA** (`docs/uso-ia.md`): cada uso relevante de herramientas de IA.
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Equipo Plano3D
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Plano3D
|
||||||
|
|
||||||
|
Aplicación de escritorio que reconstruye automáticamente una pieza mecánica 3D (STEP/STL) a partir de su plano técnico 2D con alzado, planta y perfil (DXF, DWG o PDF vectorial), y verifica el plano: escala, cotas, coherencia entre vistas y ambigüedades.
|
||||||
|
|
||||||
|
Proyecto intermodular de 2.º DAM, IES La Mar.
|
||||||
|
|
||||||
|
## Estructura
|
||||||
|
|
||||||
|
| Carpeta | Contenido | Responsable |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `motor/` | Procesado: lectura del plano, vistas, reconstrucción, verificación, exportación | Motor |
|
||||||
|
| `motor/api.py` | **Contrato** entre motor y aplicación: lo único que la aplicación importa | Los dos |
|
||||||
|
| `app/` | Aplicación: interfaz Qt, visor 3D, historial, instalador | Aplicación |
|
||||||
|
| `pruebas/` | Pruebas automáticas (`pytest`) y catálogo de piezas con su resultado esperado | Los dos |
|
||||||
|
| `prototipo/` | Prueba técnica inicial (no se modifica; solo de referencia) | Los dos |
|
||||||
|
| `docs/` | Guía técnica, registro de decisiones, bitácora, actas y declaración de uso de IA | Los dos |
|
||||||
|
|
||||||
|
## Puesta en marcha
|
||||||
|
|
||||||
|
Requisitos: [uv](https://docs.astral.sh/uv/) y Git. CadQuery necesita Python 3.12 o 3.13; uv lo descarga solo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formas de trabajo
|
||||||
|
|
||||||
|
Ver [CONTRIBUTING.md](CONTRIBUTING.md): ramas, *pull requests*, revisiones e *issues*.
|
||||||
|
|
||||||
|
## Licencia
|
||||||
|
|
||||||
|
MIT. Ver [LICENSE](LICENSE).
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""Aplicación de escritorio de Plano3D: interfaz (PySide6), visor 3D (VTK), historial e instalador.
|
||||||
|
|
||||||
|
Solo usa el motor a través de motor.api (procesar, exportar y las clases del contrato).
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Acta de reunión AAAA-MM-DD
|
||||||
|
|
||||||
|
- **Asistentes:**
|
||||||
|
- **Modera / toma notas:** (rotatorio)
|
||||||
|
- **Duración:**
|
||||||
|
|
||||||
|
## Revisión de la semana anterior
|
||||||
|
|
||||||
|
| Tarea (issue) | Responsable | Estado |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
## Temas tratados
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## Acuerdos
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## Tareas para la próxima semana
|
||||||
|
|
||||||
|
| Tarea | Responsable | Issue |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| | | |
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Bitácora
|
||||||
|
|
||||||
|
Una entrada por semana. Cada persona escribe lo suyo.
|
||||||
|
|
||||||
|
## Semana del AAAA-MM-DD
|
||||||
|
|
||||||
|
### Joel
|
||||||
|
- Hecho:
|
||||||
|
- Problemas:
|
||||||
|
- Próxima semana:
|
||||||
|
- Horas (clase / autónomas):
|
||||||
|
|
||||||
|
### (compañera)
|
||||||
|
- Hecho:
|
||||||
|
- Problemas:
|
||||||
|
- Próxima semana:
|
||||||
|
- Horas (clase / autónomas):
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Registro de decisiones
|
||||||
|
|
||||||
|
Cada decisión de alcance o de diseño, con su motivo. La más reciente, arriba.
|
||||||
|
|
||||||
|
| Fecha | Decisión | Motivo |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 2026-10-22 | Reparto: Joel, aplicación (interfaz y visor 3D); compañera, motor de procesado. Contrato común en `motor/api.py` | Partes separables que pueden avanzar en paralelo con un motor simulado |
|
||||||
|
| 2026-10-22 | Repositorio en la plataforma Gitea del instituto, con ramas por tarea y PR revisados | Requisito del módulo; trazabilidad de la contribución individual |
|
||||||
|
| 2026-10-14 | Hitos H0 (fin de SA2) y H1 (fin de SA3), con plan B de piezas planas | Gestionar el riesgo técnico de la reconstrucción |
|
||||||
|
| 2026-10-14 | Aplicación de escritorio, no web | Cálculo pesado con OpenCASCADE; privacidad de los planos; sin servidor ni costes |
|
||||||
|
| 2026-10-14 | Alcance por prioridades; roscas como agujero + nota; revolución como importante | Equilibrio entre ambición y las horas del módulo |
|
||||||
|
| 2026-10-14 | Lo no soportado se detecta y se avisa, nunca se reconstruye mal en silencio | Fiabilidad: una pieza errónea sin aviso es peor que ninguna |
|
||||||
|
| 2026-10-14 | Reconstrucción automática desde 3 vistas, con verificación del plano | Es lo que Fusion 360 no hace; la versión "DXF + espesor" no aportaba valor |
|
||||||
|
| 2026-10-14 | Prueba técnica con CUBE01.dxf: reconstrucción correcta y detección de proyección mixta | Comprobar la viabilidad antes de comprometer el alcance |
|
||||||
|
| 2026-10-08 | Piezas mecánicas (no arquitectura); entradas DXF, DWG y PDF | Propuesta inicial del equipo |
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Guía técnica
|
||||||
|
|
||||||
|
Resumen de las decisiones técnicas vigentes y de las reglas del código. El detalle y la fecha de cada decisión están en [decisiones.md](decisiones.md).
|
||||||
|
|
||||||
|
## Producto
|
||||||
|
|
||||||
|
Aplicación de escritorio que reconstruye piezas mecánicas 3D a partir de planos de tres vistas y verifica el plano. Todo se procesa en el equipo del usuario: no hay servidor ni cuentas.
|
||||||
|
|
||||||
|
## Tecnologías
|
||||||
|
|
||||||
|
| Tecnología | Uso |
|
||||||
|
| --- | --- |
|
||||||
|
| Python 3.12 | Todo el proyecto |
|
||||||
|
| CadQuery (OpenCASCADE) | Sólidos 3D y exportación STEP/STL |
|
||||||
|
| ezdxf | Lectura de DXF |
|
||||||
|
| shapely | Geometría 2D de las vistas |
|
||||||
|
| OR-Tools CP-SAT | Búsqueda de la pieza que reproduce el plano |
|
||||||
|
| PySide6 y VTK | Interfaz y visor 3D |
|
||||||
|
| SQLite | Historial local de conversiones |
|
||||||
|
| PyInstaller | Instalador |
|
||||||
|
| pytest | Pruebas automáticas |
|
||||||
|
|
||||||
|
## Alcance
|
||||||
|
|
||||||
|
- Entradas: DXF (imprescindible), DWG mediante conversor externo, PDF vectorial (importante).
|
||||||
|
- Geometría imprescindible: tres vistas, caras planas, chaflanes, agujeros pasantes y roscas (agujero más nota).
|
||||||
|
- Geometría importante: agujeros ciegos, avellanados y cajeados; piezas de revolución.
|
||||||
|
- Sistemas de proyección europeo (ISO-E) y americano (ISO-A); se avisa si un plano los mezcla.
|
||||||
|
- Lo que no se soporta se detecta y se avisa. Nunca se entrega una pieza que no reproduce el plano sin advertirlo.
|
||||||
|
|
||||||
|
## Reglas del código
|
||||||
|
|
||||||
|
- `motor/api.py` es el contrato entre motor y aplicación. La aplicación solo importa desde ahí, y cualquier cambio se acuerda entre los dos.
|
||||||
|
- `motor/simulado.py` devuelve la pieza CUBE01 mientras el motor real no esté terminado.
|
||||||
|
- `prototipo/reconstruir.py` es la prueba técnica original. Se conserva como referencia y no se modifica.
|
||||||
|
- Cada pieza del catálogo tiene su carpeta en `pruebas/catalogo/` con `plano.dxf`, `esperado.step` e `info.md`.
|
||||||
|
- Ramas, commits y revisiones según [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Declaración de uso de herramientas de IA
|
||||||
|
|
||||||
|
Registro de los usos relevantes de IA en el proyecto, para la declaración de la memoria final.
|
||||||
|
|
||||||
|
| Fecha | Herramienta | Quién | Para qué | Cómo se revisó |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 2026-10-08 | Claude (Claude Code) | Joel | Análisis de viabilidad de la idea y primer anteproyecto | Discutido y corregido en equipo |
|
||||||
|
| 2026-10-14 | Claude (Claude Code) | Joel | Prototipo de reconstrucción (`prototipo/reconstruir.py`) a partir de CUBE01.dxf | Resultado abierto y comprobado en Fusion 360 |
|
||||||
|
| 2026-10-14 | Claude (Claude Code) | Joel | Revisión de decisiones técnicas y redacción de la propuesta | Revisado por el equipo antes de entregarla |
|
||||||
|
| 2026-10-22 | Claude (Claude Code) | Joel | Estructura del repositorio, contrato `motor/api.py`, motor simulado y plantillas | Revisado por el equipo |
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""Motor de procesado de Plano3D: de plano 2D (DXF, DWG, PDF) a pieza 3D con informe de verificación.
|
||||||
|
|
||||||
|
La aplicación solo debe importar desde motor.api.
|
||||||
|
"""
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
"""Contrato entre el motor de procesado y la aplicación.
|
||||||
|
|
||||||
|
Este módulo es lo único que la aplicación (interfaz y visor) conoce del motor.
|
||||||
|
Si hay que cambiar algo de aquí, se acuerda entre los dos y se anota en docs/decisiones.md,
|
||||||
|
porque rompe el trabajo del otro.
|
||||||
|
|
||||||
|
Uso desde la aplicación:
|
||||||
|
|
||||||
|
from motor.api import procesar, exportar, Opciones
|
||||||
|
|
||||||
|
resultado = procesar("pieza.dxf", Opciones())
|
||||||
|
if resultado.solido is not None:
|
||||||
|
exportar(resultado.solido, "pieza.step")
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cadquery as cq
|
||||||
|
|
||||||
|
|
||||||
|
class Estado(Enum):
|
||||||
|
OK = "ok" # pieza reconstruida y sin problemas
|
||||||
|
CON_AVISOS = "con_avisos" # pieza reconstruida, pero hay cosas que revisar
|
||||||
|
IMPOSIBLE = "imposible" # no se ha podido reconstruir; ver los avisos bloqueantes
|
||||||
|
|
||||||
|
|
||||||
|
class Gravedad(Enum):
|
||||||
|
INFO = "info" # dato informativo (p. ej. escala detectada)
|
||||||
|
AVISO = "aviso" # algo no cuadra, pero hay pieza
|
||||||
|
BLOQUEANTE = "bloqueante" # impide reconstruir; puede requerir un dato del usuario
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Segmento2D:
|
||||||
|
"""Línea de una vista, en coordenadas reales de la pieza (mm)."""
|
||||||
|
x1: float
|
||||||
|
y1: float
|
||||||
|
x2: float
|
||||||
|
y2: float
|
||||||
|
oculta: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Vista:
|
||||||
|
"""Geometría 2D de una vista, para mostrarla junto al modelo 3D."""
|
||||||
|
nombre: str # "alzado", "planta" o "perfil"
|
||||||
|
segmentos: list[Segmento2D] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Referencia:
|
||||||
|
"""A qué parte del plano o de la pieza se refiere un aviso, para que el visor la resalte."""
|
||||||
|
vista: str | None = None # vista afectada, si la hay
|
||||||
|
segmento: Segmento2D | None = None # línea del plano afectada
|
||||||
|
punto: tuple[float, float, float] | None = None # punto de la pieza 3D (mm)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Aviso:
|
||||||
|
codigo: str # identificador estable, p. ej. "SIN_COTAS", "PROYECCION_MIXTA"
|
||||||
|
mensaje: str # texto para el usuario, en español
|
||||||
|
gravedad: Gravedad
|
||||||
|
referencia: Referencia | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Opciones:
|
||||||
|
"""Datos que el usuario puede aportar cuando el motor los pide."""
|
||||||
|
escala: float | None = None # factor real/dibujo si el plano no trae escala (1:2 -> 2.0)
|
||||||
|
tolerancia: float = 1.0 # en unidades de dibujo
|
||||||
|
alternativa: int | None = None # si el plano es ambiguo, cuál de las piezas posibles se elige
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Resultado:
|
||||||
|
estado: Estado
|
||||||
|
solido: cq.Shape | None # pieza 3D en mm; None si el estado es IMPOSIBLE
|
||||||
|
avisos: list[Aviso] = field(default_factory=list)
|
||||||
|
vistas: dict[str, Vista] = field(default_factory=dict)
|
||||||
|
escala: float = 1.0 # factor aplicado (real/dibujo)
|
||||||
|
alternativas: int = 1 # número de piezas compatibles con el plano
|
||||||
|
|
||||||
|
|
||||||
|
def procesar(ruta_plano: str | Path, opciones: Opciones | None = None) -> Resultado:
|
||||||
|
"""Punto de entrada único del motor.
|
||||||
|
|
||||||
|
Mientras el motor real no esté listo devuelve un resultado simulado (motor/simulado.py),
|
||||||
|
para que la aplicación se pueda desarrollar en paralelo.
|
||||||
|
"""
|
||||||
|
from motor import simulado
|
||||||
|
return simulado.procesar(Path(ruta_plano), opciones or Opciones())
|
||||||
|
|
||||||
|
|
||||||
|
def exportar(solido: cq.Shape, ruta_salida: str | Path) -> None:
|
||||||
|
"""Guarda la pieza en STEP o STL según la extensión del archivo."""
|
||||||
|
ruta_salida = Path(ruta_salida)
|
||||||
|
if ruta_salida.suffix.lower() not in (".step", ".stp", ".stl"):
|
||||||
|
raise ValueError(f"Formato no admitido: {ruta_salida.suffix} (usa .step, .stp o .stl)")
|
||||||
|
cq.exporters.export(cq.Workplane().add(solido), str(ruta_salida))
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""Motor simulado: devuelve siempre la pieza de CUBE01 con sus avisos reales.
|
||||||
|
|
||||||
|
Sirve para desarrollar la aplicación (interfaz y visor) antes de que exista el motor real.
|
||||||
|
Las líneas del plano sí se leen del archivo recibido, para poder dibujarlas en 2D.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cadquery as cq
|
||||||
|
import ezdxf
|
||||||
|
|
||||||
|
from motor.api import Aviso, Estado, Gravedad, Opciones, Referencia, Resultado, Segmento2D, Vista
|
||||||
|
|
||||||
|
RAIZ = Path(__file__).resolve().parent.parent
|
||||||
|
PIEZA_CUBE01 = RAIZ / "pruebas" / "catalogo" / "CUBE01" / "esperado.step"
|
||||||
|
|
||||||
|
|
||||||
|
def _leer_lineas(ruta: Path) -> list[Segmento2D]:
|
||||||
|
if ruta.suffix.lower() != ".dxf":
|
||||||
|
return []
|
||||||
|
doc = ezdxf.readfile(ruta)
|
||||||
|
segmentos = []
|
||||||
|
for e in doc.modelspace().query("LINE"):
|
||||||
|
tipo = e.dxf.get("linetype", "ByLayer")
|
||||||
|
if tipo.lower() == "bylayer":
|
||||||
|
tipo = doc.layers.get(e.dxf.layer).dxf.linetype
|
||||||
|
oculta = "HIDDEN" in tipo.upper() or "HIDDEN" in e.dxf.layer.upper()
|
||||||
|
segmentos.append(Segmento2D(e.dxf.start.x, e.dxf.start.y, e.dxf.end.x, e.dxf.end.y, oculta))
|
||||||
|
return segmentos
|
||||||
|
|
||||||
|
|
||||||
|
def procesar(ruta: Path, opciones: Opciones) -> Resultado:
|
||||||
|
solido = cq.importers.importStep(str(PIEZA_CUBE01)).val()
|
||||||
|
avisos = [
|
||||||
|
Aviso("ESCALA_DETECTADA", "Escala 1:2 leída del cajetín.", Gravedad.INFO),
|
||||||
|
Aviso("SIN_COTAS", "El plano no tiene cotas: las medidas se deducen de la geometría y la escala.",
|
||||||
|
Gravedad.AVISO),
|
||||||
|
Aviso("PROYECCION_MIXTA",
|
||||||
|
"Sistemas de proyección mezclados: la planta sigue el europeo (ISO-E) y el perfil el americano (ISO-A).",
|
||||||
|
Gravedad.AVISO, Referencia(vista="perfil")),
|
||||||
|
]
|
||||||
|
# Simulado: todas las líneas van en una sola "vista"; el motor real las separará en alzado, planta y perfil.
|
||||||
|
vistas = {"plano": Vista("plano", _leer_lineas(ruta))}
|
||||||
|
return Resultado(Estado.CON_AVISOS, solido, avisos, vistas, escala=opciones.escala or 2.0)
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Prototipo (prueba técnica)
|
||||||
|
|
||||||
|
`reconstruir.py` es la prueba técnica que demostró la viabilidad del proyecto con CUBE01.dxf: reconstrucción por descomposición en celdas, búsqueda por fuerza bruta y detección del sistema de proyección.
|
||||||
|
|
||||||
|
Se conserva como referencia y no se modifica. El motor definitivo se desarrolla en `motor/`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python prototipo/reconstruir.py pruebas/catalogo/CUBE01/plano.dxf
|
||||||
|
```
|
||||||
|
|
||||||
|
Limitaciones conocidas: solo líneas rectas (sin arcos ni círculos) y búsqueda exhaustiva que no escala a más de unas 18 celdas.
|
||||||
|
|
@ -0,0 +1,423 @@
|
||||||
|
"""Prueba técnica: reconstrucción automática de una pieza 3D a partir de un plano DXF
|
||||||
|
con tres vistas (alzado, planta y perfil).
|
||||||
|
|
||||||
|
Método (descomposición en celdas):
|
||||||
|
1. Leer las líneas visibles y ocultas; ignorar marco, cajetín, textos y cotas.
|
||||||
|
2. Buscar la escala en los textos del plano ("ESCALA 1:2").
|
||||||
|
3. Agrupar las líneas en vistas e identificar alzado, planta y perfil por su alineación.
|
||||||
|
4. Para cada convención de proyección posible (europea/americana en planta y perfil):
|
||||||
|
a. dividir el volumen en celdas con todos los planos que sugieren las vistas
|
||||||
|
(incluidos los planos inclinados de las líneas diagonales);
|
||||||
|
b. descartar celdas que se salen de la silueta de alguna vista;
|
||||||
|
c. probar combinaciones de celdas cuya silueta coincide con las tres vistas;
|
||||||
|
d. proyectar las aristas del sólido (visibles y ocultas) y compararlas con el plano.
|
||||||
|
5. Quedarse con el sólido que reproduce el plano y exportarlo a STEP y STL.
|
||||||
|
|
||||||
|
Uso: python reconstruir.py CUBE01.dxf
|
||||||
|
"""
|
||||||
|
import itertools
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cadquery as cq
|
||||||
|
import ezdxf
|
||||||
|
import numpy as np
|
||||||
|
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
||||||
|
from OCP.BRepIntCurveSurface import BRepIntCurveSurface_Inter
|
||||||
|
from OCP.gp import gp_Dir, gp_Lin, gp_Pnt
|
||||||
|
from OCP.TopAbs import TopAbs_IN
|
||||||
|
from shapely.geometry import LineString, MultiPoint
|
||||||
|
from shapely.ops import polygonize, unary_union
|
||||||
|
|
||||||
|
TOL = 1.0 # tolerancia en unidades de dibujo (las vistas no están perfectamente alineadas)
|
||||||
|
BIG = 1e4
|
||||||
|
MAX_CELDAS = 18 # límite de la búsqueda exhaustiva (2^18 combinaciones)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 1. lectura
|
||||||
|
def leer_dxf(ruta):
|
||||||
|
doc = ezdxf.readfile(ruta)
|
||||||
|
msp = doc.modelspace()
|
||||||
|
avisos = []
|
||||||
|
|
||||||
|
def es_oculta(e):
|
||||||
|
lt = e.dxf.get("linetype", "ByLayer")
|
||||||
|
if lt.lower() in ("bylayer", "byblock"):
|
||||||
|
lt = doc.layers.get(e.dxf.layer).dxf.linetype
|
||||||
|
nombre = (lt + " " + e.dxf.layer).upper()
|
||||||
|
return any(k in nombre for k in ("HIDDEN", "DASH", "OCULT", "ISO02"))
|
||||||
|
|
||||||
|
segs = []
|
||||||
|
for e in msp:
|
||||||
|
if e.dxftype() == "LINE":
|
||||||
|
partes = [(e.dxf.start, e.dxf.end)]
|
||||||
|
elif e.dxftype() == "LWPOLYLINE":
|
||||||
|
pts = [p[:2] for p in e.get_points()]
|
||||||
|
if e.closed:
|
||||||
|
pts.append(pts[0])
|
||||||
|
partes = list(zip(pts, pts[1:]))
|
||||||
|
else:
|
||||||
|
continue # marco y cajetín (INSERT), textos, cotas, etc.
|
||||||
|
for a, b in partes:
|
||||||
|
segs.append((float(a[0]), float(a[1]), float(b[0]), float(b[1]), es_oculta(e)))
|
||||||
|
|
||||||
|
textos = [t.dxf.text for t in msp.query("TEXT")] + [t.text for t in msp.query("MTEXT")]
|
||||||
|
for ins in msp.query("INSERT"):
|
||||||
|
textos += [a.dxf.text for a in ins.attribs]
|
||||||
|
escala = None
|
||||||
|
for t in textos:
|
||||||
|
m = re.search(r"(?:ESCALA|SCALE)\s*:?\s*(\d+(?:[.,]\d+)?)\s*[:/]\s*(\d+(?:[.,]\d+)?)", t, re.I)
|
||||||
|
if m:
|
||||||
|
a, b = (float(x.replace(",", ".")) for x in m.groups())
|
||||||
|
escala = (a, b)
|
||||||
|
break
|
||||||
|
if escala is None:
|
||||||
|
avisos.append("No se ha encontrado la escala en el plano: se supone 1:1.")
|
||||||
|
escala = (1.0, 1.0)
|
||||||
|
|
||||||
|
n_cotas = len(msp.query("DIMENSION"))
|
||||||
|
if n_cotas == 0:
|
||||||
|
avisos.append("El plano no tiene cotas: las medidas se deducen de la geometría y la escala.")
|
||||||
|
unidades = {0: "sin unidades", 1: "pulgadas", 4: "mm", 5: "cm", 6: "m"}.get(doc.header.get("$INSUNITS", 0), "otras")
|
||||||
|
return segs, escala, n_cotas, unidades, avisos
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 2. vistas
|
||||||
|
def agrupar_vistas(segs):
|
||||||
|
n = len(segs)
|
||||||
|
padre = list(range(n))
|
||||||
|
|
||||||
|
def raiz(i):
|
||||||
|
while padre[i] != i:
|
||||||
|
padre[i] = padre[padre[i]]
|
||||||
|
i = padre[i]
|
||||||
|
return i
|
||||||
|
|
||||||
|
cajas = [(min(s[0], s[2]) - TOL, min(s[1], s[3]) - TOL, max(s[0], s[2]) + TOL, max(s[1], s[3]) + TOL) for s in segs]
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(i + 1, n):
|
||||||
|
a, b = cajas[i], cajas[j]
|
||||||
|
if a[0] <= b[2] and b[0] <= a[2] and a[1] <= b[3] and b[1] <= a[3]:
|
||||||
|
padre[raiz(i)] = raiz(j)
|
||||||
|
grupos = {}
|
||||||
|
for i in range(n):
|
||||||
|
grupos.setdefault(raiz(i), []).append(segs[i])
|
||||||
|
vistas = []
|
||||||
|
for g in grupos.values():
|
||||||
|
xs = [c for s in g for c in (s[0], s[2])]
|
||||||
|
ys = [c for s in g for c in (s[1], s[3])]
|
||||||
|
vistas.append({"segs": g, "u0": min(xs), "u1": max(xs), "v0": min(ys), "v1": max(ys)})
|
||||||
|
return vistas
|
||||||
|
|
||||||
|
|
||||||
|
def identificar_vistas(vistas):
|
||||||
|
def mismo_ancho(a, b):
|
||||||
|
return abs(a["u0"] - b["u0"]) < 2 * TOL and abs(a["u1"] - b["u1"]) < 2 * TOL
|
||||||
|
|
||||||
|
def mismo_alto(a, b):
|
||||||
|
return abs(a["v0"] - b["v0"]) < 2 * TOL and abs(a["v1"] - b["v1"]) < 2 * TOL
|
||||||
|
|
||||||
|
for alz in vistas:
|
||||||
|
plantas = [v for v in vistas if v is not alz and mismo_ancho(alz, v)]
|
||||||
|
perfiles = [v for v in vistas if v is not alz and mismo_alto(alz, v)]
|
||||||
|
if plantas and perfiles:
|
||||||
|
pl, pf = plantas[0], perfiles[0]
|
||||||
|
return alz, pl, pf, ("debajo" if pl["v1"] < alz["v0"] else "encima"), ("derecha" if pf["u0"] > alz["u1"] else "izquierda")
|
||||||
|
raise SystemExit("No se han podido identificar alzado, planta y perfil alineados.")
|
||||||
|
|
||||||
|
|
||||||
|
def a_coordenadas(alz, pl, pf, pos_pl, pos_pf, conv_pl, conv_pf):
|
||||||
|
"""Pasa cada vista a coordenadas de la pieza: alzado (X,Z), planta (X,Y), perfil (Y,Z).
|
||||||
|
Y = 0 es la cara delantera. Devuelve también desde dónde mira el observador de cada vista."""
|
||||||
|
D = pl["v1"] - pl["v0"]
|
||||||
|
|
||||||
|
def map_pl(u, v):
|
||||||
|
d = (pl["v1"] - v) if pos_pl == "debajo" else (v - pl["v0"]) # distancia al borde junto al alzado
|
||||||
|
return (u - pl["u0"], D - d if conv_pl == "E" else d)
|
||||||
|
|
||||||
|
def map_pf(u, v):
|
||||||
|
d = (u - pf["u0"]) if pos_pf == "derecha" else (pf["u1"] - u)
|
||||||
|
return (D - d if conv_pf == "E" else d, v - pf["v0"])
|
||||||
|
|
||||||
|
def map_alz(u, v):
|
||||||
|
return (u - alz["u0"], v - alz["v0"])
|
||||||
|
|
||||||
|
out = {}
|
||||||
|
for nombre, vista, f in (("alzado", alz, map_alz), ("planta", pl, map_pl), ("perfil", pf, map_pf)):
|
||||||
|
out[nombre] = [(*f(s[0], s[1]), *f(s[2], s[3]), s[4]) for s in vista["segs"]]
|
||||||
|
|
||||||
|
# Observador: en el sistema europeo la vista de debajo es la superior; en el americano, al revés.
|
||||||
|
desde_arriba = (pos_pl == "debajo") == (conv_pl == "E")
|
||||||
|
desde_izquierda = (pos_pf == "derecha") == (conv_pf == "E")
|
||||||
|
observador = {
|
||||||
|
"alzado": (0, -1, 0),
|
||||||
|
"planta": (0, 0, 1) if desde_arriba else (0, 0, -1),
|
||||||
|
"perfil": (-1, 0, 0) if desde_izquierda else (1, 0, 0),
|
||||||
|
}
|
||||||
|
return out, observador, D
|
||||||
|
|
||||||
|
|
||||||
|
PROY = {"alzado": (0, 2), "planta": (0, 1), "perfil": (1, 2)} # ejes 3D que se ven en cada vista
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 3. celdas
|
||||||
|
def agrupar_valores(vals):
|
||||||
|
vals = sorted(vals)
|
||||||
|
out = []
|
||||||
|
for v in vals:
|
||||||
|
if not out or v - out[-1] > TOL:
|
||||||
|
out.append(v)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def region(segs):
|
||||||
|
lineas = [LineString([(s[0], s[1]), (s[2], s[3])]) for s in segs if not s[4]]
|
||||||
|
return unary_union(list(polygonize(unary_union(lineas))))
|
||||||
|
|
||||||
|
|
||||||
|
def semiespacio(p, n):
|
||||||
|
n = cq.Vector(*n).normalized()
|
||||||
|
aux = cq.Vector(1, 0, 0) if abs(n.x) < 0.9 else cq.Vector(0, 1, 0)
|
||||||
|
plano = cq.Plane(origin=cq.Vector(*p), xDir=aux.cross(n).normalized(), normal=n)
|
||||||
|
return cq.Workplane(plano).rect(BIG, BIG).extrude(BIG).val()
|
||||||
|
|
||||||
|
|
||||||
|
def crear_celdas(coords):
|
||||||
|
rejilla = {0: [], 1: [], 2: []}
|
||||||
|
inclinados = []
|
||||||
|
for nombre, segs in coords.items():
|
||||||
|
a, b = PROY[nombre]
|
||||||
|
for x1, y1, x2, y2, _ in segs:
|
||||||
|
rejilla[a] += [x1, x2]
|
||||||
|
rejilla[b] += [y1, y2]
|
||||||
|
if abs(x1 - x2) > TOL and abs(y1 - y2) > TOL: # línea diagonal -> plano inclinado
|
||||||
|
n = [0.0, 0.0, 0.0]
|
||||||
|
n[a], n[b] = -(y2 - y1), (x2 - x1)
|
||||||
|
p = [0.0, 0.0, 0.0]
|
||||||
|
p[a], p[b] = x1, y1
|
||||||
|
inclinados.append((tuple(p), tuple(n)))
|
||||||
|
ejes = [agrupar_valores(rejilla[i]) for i in range(3)]
|
||||||
|
|
||||||
|
celdas = []
|
||||||
|
for i, j, k in itertools.product(range(len(ejes[0]) - 1), range(len(ejes[1]) - 1), range(len(ejes[2]) - 1)):
|
||||||
|
x0, x1 = ejes[0][i], ejes[0][i + 1]
|
||||||
|
y0, y1 = ejes[1][j], ejes[1][j + 1]
|
||||||
|
z0, z1 = ejes[2][k], ejes[2][k + 1]
|
||||||
|
trozos = [cq.Solid.makeBox(x1 - x0, y1 - y0, z1 - z0, pnt=cq.Vector(x0, y0, z0))]
|
||||||
|
esquinas = np.array(list(itertools.product((x0, x1), (y0, y1), (z0, z1))))
|
||||||
|
for p, n in inclinados:
|
||||||
|
dist = (esquinas - np.array(p)) @ np.array(n)
|
||||||
|
if dist.min() < -1e-6 and dist.max() > 1e-6: # el plano atraviesa la caja
|
||||||
|
hs = semiespacio(p, n)
|
||||||
|
nuevos = []
|
||||||
|
for t in trozos:
|
||||||
|
for parte in (t.intersect(hs), t.cut(hs)):
|
||||||
|
nuevos += [s for s in parte.Solids() if s.Volume() > 1e-6]
|
||||||
|
trozos = nuevos
|
||||||
|
celdas += trozos
|
||||||
|
return celdas, ejes, inclinados
|
||||||
|
|
||||||
|
|
||||||
|
def proyeccion(solido, nombre):
|
||||||
|
a, b = PROY[nombre]
|
||||||
|
pts = [(v.toTuple()[a], v.toTuple()[b]) for v in solido.Vertices()]
|
||||||
|
return MultiPoint(pts).convex_hull
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 4. comparación
|
||||||
|
def oculto(forma, p, d):
|
||||||
|
inter = BRepIntCurveSurface_Inter()
|
||||||
|
inter.Init(forma.wrapped, gp_Lin(gp_Pnt(*p), gp_Dir(*d)), 1e-7)
|
||||||
|
ws = []
|
||||||
|
while inter.More():
|
||||||
|
if inter.W() > 1e-5:
|
||||||
|
ws.append(inter.W())
|
||||||
|
inter.Next()
|
||||||
|
params = [0.0] + sorted(set(round(w, 6) for w in ws))
|
||||||
|
for t0, t1 in zip(params, params[1:]):
|
||||||
|
q = np.array(p) + np.array(d) * (t0 + t1) / 2
|
||||||
|
if BRepClass3d_SolidClassifier(forma.wrapped, gp_Pnt(*q), 1e-6).State() == TopAbs_IN:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def aristas_proyectadas(forma, nombre, obs):
|
||||||
|
a, b = PROY[nombre]
|
||||||
|
vis, ocu = [], []
|
||||||
|
for e in forma.Edges():
|
||||||
|
p0, p1 = np.array(e.startPoint().toTuple()), np.array(e.endPoint().toTuple())
|
||||||
|
if math.dist((p0[a], p0[b]), (p1[a], p1[b])) < TOL / 2:
|
||||||
|
continue # arista perpendicular a la vista
|
||||||
|
n = 5
|
||||||
|
for i in range(n):
|
||||||
|
q0, q1 = p0 + (p1 - p0) * i / n, p0 + (p1 - p0) * (i + 1) / n
|
||||||
|
seg = (q0[a], q0[b], q1[a], q1[b])
|
||||||
|
(ocu if oculto(forma, (q0 + q1) / 2, obs) else vis).append(seg)
|
||||||
|
return vis, ocu
|
||||||
|
|
||||||
|
|
||||||
|
def muestras(segs, paso=TOL / 2):
|
||||||
|
pts = []
|
||||||
|
for x1, y1, x2, y2 in segs:
|
||||||
|
n = max(1, int(math.dist((x1, y1), (x2, y2)) / paso))
|
||||||
|
for i in range(n):
|
||||||
|
t = (i + 0.5) / n
|
||||||
|
pts.append((x1 + (x2 - x1) * t, y1 + (y2 - y1) * t))
|
||||||
|
return np.array(pts).reshape(-1, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def distancia(pts, segs):
|
||||||
|
if len(pts) == 0:
|
||||||
|
return np.zeros(0)
|
||||||
|
if not segs:
|
||||||
|
return np.full(len(pts), np.inf)
|
||||||
|
s = np.array(segs, dtype=float)
|
||||||
|
a, b = s[:, :2], s[:, 2:]
|
||||||
|
ab = b - a
|
||||||
|
t = np.clip(((pts[:, None, :] - a) * ab).sum(-1) / np.maximum((ab * ab).sum(-1), 1e-12), 0, 1)
|
||||||
|
proj = a + t[..., None] * ab
|
||||||
|
return np.linalg.norm(pts[:, None, :] - proj, axis=-1).min(axis=1)
|
||||||
|
|
||||||
|
|
||||||
|
def errores_vista(forma, nombre, obs, dibujo):
|
||||||
|
cvis, cocu = aristas_proyectadas(forma, nombre, obs)
|
||||||
|
dvis = [s[:4] for s in dibujo if not s[4]]
|
||||||
|
docu = [s[:4] for s in dibujo if s[4]]
|
||||||
|
err = 0
|
||||||
|
err += int((distancia(muestras(cvis), dvis) > TOL).sum()) # aristas que el plano no tiene
|
||||||
|
m = muestras(cocu)
|
||||||
|
m = m[distancia(m, cvis) > TOL] # lo visible tapa lo oculto
|
||||||
|
err += int((distancia(m, docu) > TOL).sum())
|
||||||
|
err += int((distancia(muestras(dvis), cvis) > TOL).sum()) # líneas del plano que faltan
|
||||||
|
err += int((distancia(muestras(docu), cocu) > TOL).sum())
|
||||||
|
return err
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- 5. búsqueda
|
||||||
|
def reconstruir(coords, observador):
|
||||||
|
regiones = {n: region(s) for n, s in coords.items()}
|
||||||
|
celdas, ejes, inclinados = crear_celdas(coords)
|
||||||
|
proy = [{n: proyeccion(c, n) for n in coords} for c in celdas]
|
||||||
|
|
||||||
|
# Una celda que se sale de la silueta de alguna vista no puede formar parte de la pieza.
|
||||||
|
validas = [i for i in range(len(celdas))
|
||||||
|
if all(proy[i][n].difference(regiones[n].buffer(TOL / 2)).area < 1e-3 for n in coords)]
|
||||||
|
if len(validas) > MAX_CELDAS:
|
||||||
|
raise SystemExit(f"Demasiadas celdas ({len(validas)}) para la búsqueda exhaustiva de la prueba.")
|
||||||
|
|
||||||
|
candidatos = []
|
||||||
|
for r in range(1, len(validas) + 1):
|
||||||
|
for combo in itertools.combinations(validas, r):
|
||||||
|
ok = all(unary_union([proy[i][n] for i in combo]).symmetric_difference(regiones[n]).area
|
||||||
|
< 0.005 * regiones[n].area for n in coords)
|
||||||
|
if ok:
|
||||||
|
candidatos.append(combo)
|
||||||
|
|
||||||
|
resultados = []
|
||||||
|
for combo in candidatos:
|
||||||
|
forma = celdas[combo[0]]
|
||||||
|
if len(combo) > 1:
|
||||||
|
forma = forma.fuse(*[celdas[i] for i in combo[1:]])
|
||||||
|
forma = forma.clean()
|
||||||
|
if len(forma.Solids()) != 1:
|
||||||
|
continue # piezas sueltas: no es una pieza única
|
||||||
|
forma = forma.Solids()[0]
|
||||||
|
err = sum(errores_vista(forma, n, observador[n], coords[n]) for n in coords)
|
||||||
|
resultados.append((err, combo, forma))
|
||||||
|
if err == 0 and len([r for r in resultados if r[0] == 0]) > 3:
|
||||||
|
break
|
||||||
|
resultados.sort(key=lambda r: r[0])
|
||||||
|
return resultados, len(celdas), len(validas), len(candidatos), ejes, inclinados
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- salida
|
||||||
|
def vista_previa(forma, ruta_png):
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
||||||
|
|
||||||
|
verts, tris = forma.tessellate(0.1)
|
||||||
|
v = np.array([p.toTuple() for p in verts])
|
||||||
|
caras = v[np.array(tris)]
|
||||||
|
normales = np.cross(caras[:, 1] - caras[:, 0], caras[:, 2] - caras[:, 0])
|
||||||
|
normales /= np.linalg.norm(normales, axis=1, keepdims=True) + 1e-12
|
||||||
|
luz = np.clip(normales @ np.array([0.4, -0.6, 0.7]), 0.15, 1)
|
||||||
|
fig = plt.figure(figsize=(6, 6))
|
||||||
|
ax = fig.add_subplot(projection="3d")
|
||||||
|
ax.add_collection3d(Poly3DCollection(caras, facecolors=plt.cm.Blues(0.35 + 0.55 * luz), edgecolor="none"))
|
||||||
|
for e in forma.Edges():
|
||||||
|
a, b = e.startPoint().toTuple(), e.endPoint().toTuple()
|
||||||
|
ax.plot(*zip(a, b), color="k", lw=0.8)
|
||||||
|
lo, hi = v.min(0), v.max(0)
|
||||||
|
ax.set_xlim(lo[0], hi[0]); ax.set_ylim(lo[1], hi[1]); ax.set_zlim(lo[2], hi[2])
|
||||||
|
ax.set_box_aspect(hi - lo)
|
||||||
|
ax.set_xlabel("X (mm)"); ax.set_ylabel("Y (mm, 0 = frente)"); ax.set_zlabel("Z (mm)")
|
||||||
|
ax.view_init(elev=25, azim=-60)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(ruta_png, dpi=110)
|
||||||
|
|
||||||
|
|
||||||
|
def main(ruta):
|
||||||
|
ruta = Path(ruta)
|
||||||
|
segs, (ea, eb), n_cotas, unidades, avisos = leer_dxf(ruta)
|
||||||
|
factor = eb / ea
|
||||||
|
vistas = agrupar_vistas(segs)
|
||||||
|
alz, pl, pf, pos_pl, pos_pf = identificar_vistas(vistas)
|
||||||
|
|
||||||
|
inf = [f"# Informe de reconstrucción: {ruta.name}", "",
|
||||||
|
f"- Líneas leídas: {len(segs)} ({sum(s[4] for s in segs)} ocultas); unidades del archivo: {unidades}",
|
||||||
|
f"- Escala: {ea:g}:{eb:g} -> factor x{factor:g}; cotas en el plano: {n_cotas}",
|
||||||
|
f"- Vistas encontradas: {len(vistas)}; planta {pos_pl} del alzado, perfil a la {pos_pf}",
|
||||||
|
f"- Desalineación planta/alzado: {abs(pl['u0'] - alz['u0']):.2f}; perfil/alzado: {abs(pf['v0'] - alz['v0']):.2f} (unidades de dibujo)"]
|
||||||
|
D_pf = pf["u1"] - pf["u0"]
|
||||||
|
D_pl = pl["v1"] - pl["v0"]
|
||||||
|
if abs(D_pf - D_pl) > 2 * TOL:
|
||||||
|
avisos.append(f"La profundidad no coincide entre planta ({D_pl:.1f}) y perfil ({D_pf:.1f}).")
|
||||||
|
|
||||||
|
nombres = {"E": "europeo (ISO-E)", "A": "americano (ISO-A)"}
|
||||||
|
mejor = None
|
||||||
|
inf += ["", "## Hipótesis de proyección", "", "| Planta | Perfil | Celdas (válidas) | Candidatos por silueta | Error mínimo |", "|---|---|---|---|---|"]
|
||||||
|
for conv_pl, conv_pf in itertools.product("EA", repeat=2):
|
||||||
|
coords, obs, D = a_coordenadas(alz, pl, pf, pos_pl, pos_pf, conv_pl, conv_pf)
|
||||||
|
res, n_c, n_v, n_cand, ejes, incl = reconstruir(coords, obs)
|
||||||
|
e_min = res[0][0] if res else "-"
|
||||||
|
inf.append(f"| {nombres[conv_pl]} | {nombres[conv_pf]} | {n_c} ({n_v}) | {n_cand} | {e_min} |")
|
||||||
|
print(f"planta {conv_pl} / perfil {conv_pf}: celdas {n_c}, validas {n_v}, candidatos {n_cand}, error {e_min}")
|
||||||
|
if res and (mejor is None or res[0][0] < mejor[0][0][0]):
|
||||||
|
mejor = (res, conv_pl, conv_pf)
|
||||||
|
|
||||||
|
if mejor is None:
|
||||||
|
raise SystemExit("No se ha encontrado ninguna pieza compatible con las vistas.")
|
||||||
|
res, conv_pl, conv_pf = mejor
|
||||||
|
err, combo, forma = res[0]
|
||||||
|
exactas = [r for r in res if r[0] == 0]
|
||||||
|
if conv_pl != conv_pf:
|
||||||
|
avisos.append(f"Sistemas de proyección mezclados: la planta sigue el sistema {nombres[conv_pl]} "
|
||||||
|
f"y el perfil el {nombres[conv_pf]}.")
|
||||||
|
if err > 0:
|
||||||
|
avisos.append(f"Ninguna pieza reproduce exactamente el plano (error mínimo {err}): revisar manualmente.")
|
||||||
|
if len(exactas) > 1:
|
||||||
|
avisos.append(f"Hay {len(exactas)} piezas distintas que encajan con las vistas: el plano es ambiguo.")
|
||||||
|
|
||||||
|
forma = forma.scale(factor)
|
||||||
|
bb = forma.BoundingBox()
|
||||||
|
inf += ["", "## Resultado", "",
|
||||||
|
f"- Convención elegida: planta {nombres[conv_pl]}, perfil {nombres[conv_pf]}",
|
||||||
|
f"- Celdas usadas: {len(combo)}; error de reproducción del plano: {err}",
|
||||||
|
f"- Tamaño real: {bb.xlen:.1f} x {bb.ylen:.1f} x {bb.zlen:.1f} mm; volumen {forma.Volume():.0f} mm³",
|
||||||
|
f"- Caras: {len(forma.Faces())}, aristas: {len(forma.Edges())}",
|
||||||
|
"", "## Avisos", ""] + [f"- {a}" for a in avisos]
|
||||||
|
|
||||||
|
base = ruta.with_suffix("")
|
||||||
|
cq.exporters.export(cq.Workplane().add(forma), str(base) + ".step")
|
||||||
|
cq.exporters.export(cq.Workplane().add(forma), str(base) + ".stl")
|
||||||
|
vista_previa(forma, str(base) + "_3d.png")
|
||||||
|
Path(str(base) + "_informe.md").write_text("\n".join(inf) + "\n", encoding="utf-8")
|
||||||
|
print("\n".join(inf))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1] if len(sys.argv) > 1 else "CUBE01.dxf")
|
||||||
|
|
@ -0,0 +1,721 @@
|
||||||
|
ISO-10303-21;
|
||||||
|
HEADER;
|
||||||
|
FILE_DESCRIPTION(('Open CASCADE Model'),'2;1');
|
||||||
|
FILE_NAME('Open CASCADE Shape Model','2026-10-08T19:20:47',('Author'),(
|
||||||
|
'Open CASCADE'),'Open CASCADE STEP processor 7.9','Open CASCADE 7.9'
|
||||||
|
,'Unknown');
|
||||||
|
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
|
||||||
|
ENDSEC;
|
||||||
|
DATA;
|
||||||
|
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
|
||||||
|
'automotive_design',2000,#2);
|
||||||
|
#2 = APPLICATION_CONTEXT(
|
||||||
|
'core data for automotive mechanical design processes');
|
||||||
|
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
|
||||||
|
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
|
||||||
|
#5 = PRODUCT_DEFINITION('design','',#6,#9);
|
||||||
|
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
|
||||||
|
#7 = PRODUCT('Open CASCADE STEP translator 7.9 1',
|
||||||
|
'Open CASCADE STEP translator 7.9 1','',(#8));
|
||||||
|
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
|
||||||
|
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
|
||||||
|
#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#609);
|
||||||
|
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
|
||||||
|
#12 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#13 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#14 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#15 = MANIFOLD_SOLID_BREP('',#16);
|
||||||
|
#16 = CLOSED_SHELL('',(#17,#193,#269,#322,#398,#492,#519,#547,#574,#601)
|
||||||
|
);
|
||||||
|
#17 = ADVANCED_FACE('',(#18),#32,.F.);
|
||||||
|
#18 = FACE_BOUND('',#19,.F.);
|
||||||
|
#19 = EDGE_LOOP('',(#20,#55,#83,#111,#139,#167));
|
||||||
|
#20 = ORIENTED_EDGE('',*,*,#21,.F.);
|
||||||
|
#21 = EDGE_CURVE('',#22,#24,#26,.T.);
|
||||||
|
#22 = VERTEX_POINT('',#23);
|
||||||
|
#23 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#24 = VERTEX_POINT('',#25);
|
||||||
|
#25 = CARTESIAN_POINT('',(0.,0.,100.));
|
||||||
|
#26 = SURFACE_CURVE('',#27,(#31,#43),.PCURVE_S1.);
|
||||||
|
#27 = LINE('',#28,#29);
|
||||||
|
#28 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#29 = VECTOR('',#30,1.);
|
||||||
|
#30 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#31 = PCURVE('',#32,#37);
|
||||||
|
#32 = PLANE('',#33);
|
||||||
|
#33 = AXIS2_PLACEMENT_3D('',#34,#35,#36);
|
||||||
|
#34 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#35 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#36 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#37 = DEFINITIONAL_REPRESENTATION('',(#38),#42);
|
||||||
|
#38 = LINE('',#39,#40);
|
||||||
|
#39 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#40 = VECTOR('',#41,1.);
|
||||||
|
#41 = DIRECTION('',(1.,0.));
|
||||||
|
#42 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#43 = PCURVE('',#44,#49);
|
||||||
|
#44 = PLANE('',#45);
|
||||||
|
#45 = AXIS2_PLACEMENT_3D('',#46,#47,#48);
|
||||||
|
#46 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#47 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#48 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#49 = DEFINITIONAL_REPRESENTATION('',(#50),#54);
|
||||||
|
#50 = LINE('',#51,#52);
|
||||||
|
#51 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#52 = VECTOR('',#53,1.);
|
||||||
|
#53 = DIRECTION('',(1.,0.));
|
||||||
|
#54 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#55 = ORIENTED_EDGE('',*,*,#56,.F.);
|
||||||
|
#56 = EDGE_CURVE('',#57,#22,#59,.T.);
|
||||||
|
#57 = VERTEX_POINT('',#58);
|
||||||
|
#58 = CARTESIAN_POINT('',(0.,200.,0.));
|
||||||
|
#59 = SURFACE_CURVE('',#60,(#64,#71),.PCURVE_S1.);
|
||||||
|
#60 = LINE('',#61,#62);
|
||||||
|
#61 = CARTESIAN_POINT('',(0.,200.,0.));
|
||||||
|
#62 = VECTOR('',#63,1.);
|
||||||
|
#63 = DIRECTION('',(0.,-1.,0.));
|
||||||
|
#64 = PCURVE('',#32,#65);
|
||||||
|
#65 = DEFINITIONAL_REPRESENTATION('',(#66),#70);
|
||||||
|
#66 = LINE('',#67,#68);
|
||||||
|
#67 = CARTESIAN_POINT('',(0.,-200.));
|
||||||
|
#68 = VECTOR('',#69,1.);
|
||||||
|
#69 = DIRECTION('',(0.,1.));
|
||||||
|
#70 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#71 = PCURVE('',#72,#77);
|
||||||
|
#72 = PLANE('',#73);
|
||||||
|
#73 = AXIS2_PLACEMENT_3D('',#74,#75,#76);
|
||||||
|
#74 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#75 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#76 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#77 = DEFINITIONAL_REPRESENTATION('',(#78),#82);
|
||||||
|
#78 = LINE('',#79,#80);
|
||||||
|
#79 = CARTESIAN_POINT('',(0.,200.));
|
||||||
|
#80 = VECTOR('',#81,1.);
|
||||||
|
#81 = DIRECTION('',(0.,-1.));
|
||||||
|
#82 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#83 = ORIENTED_EDGE('',*,*,#84,.F.);
|
||||||
|
#84 = EDGE_CURVE('',#85,#57,#87,.T.);
|
||||||
|
#85 = VERTEX_POINT('',#86);
|
||||||
|
#86 = CARTESIAN_POINT('',(0.,200.,200.));
|
||||||
|
#87 = SURFACE_CURVE('',#88,(#92,#99),.PCURVE_S1.);
|
||||||
|
#88 = LINE('',#89,#90);
|
||||||
|
#89 = CARTESIAN_POINT('',(0.,200.,200.));
|
||||||
|
#90 = VECTOR('',#91,1.);
|
||||||
|
#91 = DIRECTION('',(0.,0.,-1.));
|
||||||
|
#92 = PCURVE('',#32,#93);
|
||||||
|
#93 = DEFINITIONAL_REPRESENTATION('',(#94),#98);
|
||||||
|
#94 = LINE('',#95,#96);
|
||||||
|
#95 = CARTESIAN_POINT('',(200.,-200.));
|
||||||
|
#96 = VECTOR('',#97,1.);
|
||||||
|
#97 = DIRECTION('',(-1.,0.));
|
||||||
|
#98 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#99 = PCURVE('',#100,#105);
|
||||||
|
#100 = PLANE('',#101);
|
||||||
|
#101 = AXIS2_PLACEMENT_3D('',#102,#103,#104);
|
||||||
|
#102 = CARTESIAN_POINT('',(0.,200.,0.));
|
||||||
|
#103 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#104 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#105 = DEFINITIONAL_REPRESENTATION('',(#106),#110);
|
||||||
|
#106 = LINE('',#107,#108);
|
||||||
|
#107 = CARTESIAN_POINT('',(200.,0.));
|
||||||
|
#108 = VECTOR('',#109,1.);
|
||||||
|
#109 = DIRECTION('',(-1.,0.));
|
||||||
|
#110 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#111 = ORIENTED_EDGE('',*,*,#112,.F.);
|
||||||
|
#112 = EDGE_CURVE('',#113,#85,#115,.T.);
|
||||||
|
#113 = VERTEX_POINT('',#114);
|
||||||
|
#114 = CARTESIAN_POINT('',(0.,100.,200.));
|
||||||
|
#115 = SURFACE_CURVE('',#116,(#120,#127),.PCURVE_S1.);
|
||||||
|
#116 = LINE('',#117,#118);
|
||||||
|
#117 = CARTESIAN_POINT('',(0.,100.,200.));
|
||||||
|
#118 = VECTOR('',#119,1.);
|
||||||
|
#119 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#120 = PCURVE('',#32,#121);
|
||||||
|
#121 = DEFINITIONAL_REPRESENTATION('',(#122),#126);
|
||||||
|
#122 = LINE('',#123,#124);
|
||||||
|
#123 = CARTESIAN_POINT('',(200.,-100.));
|
||||||
|
#124 = VECTOR('',#125,1.);
|
||||||
|
#125 = DIRECTION('',(0.,-1.));
|
||||||
|
#126 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#127 = PCURVE('',#128,#133);
|
||||||
|
#128 = PLANE('',#129);
|
||||||
|
#129 = AXIS2_PLACEMENT_3D('',#130,#131,#132);
|
||||||
|
#130 = CARTESIAN_POINT('',(0.,100.,200.));
|
||||||
|
#131 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#132 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#133 = DEFINITIONAL_REPRESENTATION('',(#134),#138);
|
||||||
|
#134 = LINE('',#135,#136);
|
||||||
|
#135 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#136 = VECTOR('',#137,1.);
|
||||||
|
#137 = DIRECTION('',(0.,1.));
|
||||||
|
#138 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#139 = ORIENTED_EDGE('',*,*,#140,.F.);
|
||||||
|
#140 = EDGE_CURVE('',#141,#113,#143,.T.);
|
||||||
|
#141 = VERTEX_POINT('',#142);
|
||||||
|
#142 = CARTESIAN_POINT('',(0.,100.,100.));
|
||||||
|
#143 = SURFACE_CURVE('',#144,(#148,#155),.PCURVE_S1.);
|
||||||
|
#144 = LINE('',#145,#146);
|
||||||
|
#145 = CARTESIAN_POINT('',(0.,100.,100.));
|
||||||
|
#146 = VECTOR('',#147,1.);
|
||||||
|
#147 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#148 = PCURVE('',#32,#149);
|
||||||
|
#149 = DEFINITIONAL_REPRESENTATION('',(#150),#154);
|
||||||
|
#150 = LINE('',#151,#152);
|
||||||
|
#151 = CARTESIAN_POINT('',(100.,-100.));
|
||||||
|
#152 = VECTOR('',#153,1.);
|
||||||
|
#153 = DIRECTION('',(1.,0.));
|
||||||
|
#154 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#155 = PCURVE('',#156,#161);
|
||||||
|
#156 = PLANE('',#157);
|
||||||
|
#157 = AXIS2_PLACEMENT_3D('',#158,#159,#160);
|
||||||
|
#158 = CARTESIAN_POINT('',(0.,100.,100.));
|
||||||
|
#159 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#160 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#161 = DEFINITIONAL_REPRESENTATION('',(#162),#166);
|
||||||
|
#162 = LINE('',#163,#164);
|
||||||
|
#163 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#164 = VECTOR('',#165,1.);
|
||||||
|
#165 = DIRECTION('',(1.,0.));
|
||||||
|
#166 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#167 = ORIENTED_EDGE('',*,*,#168,.F.);
|
||||||
|
#168 = EDGE_CURVE('',#24,#141,#169,.T.);
|
||||||
|
#169 = SURFACE_CURVE('',#170,(#174,#181),.PCURVE_S1.);
|
||||||
|
#170 = LINE('',#171,#172);
|
||||||
|
#171 = CARTESIAN_POINT('',(0.,0.,100.));
|
||||||
|
#172 = VECTOR('',#173,1.);
|
||||||
|
#173 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#174 = PCURVE('',#32,#175);
|
||||||
|
#175 = DEFINITIONAL_REPRESENTATION('',(#176),#180);
|
||||||
|
#176 = LINE('',#177,#178);
|
||||||
|
#177 = CARTESIAN_POINT('',(100.,0.));
|
||||||
|
#178 = VECTOR('',#179,1.);
|
||||||
|
#179 = DIRECTION('',(0.,-1.));
|
||||||
|
#180 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#181 = PCURVE('',#182,#187);
|
||||||
|
#182 = PLANE('',#183);
|
||||||
|
#183 = AXIS2_PLACEMENT_3D('',#184,#185,#186);
|
||||||
|
#184 = CARTESIAN_POINT('',(0.,0.,100.));
|
||||||
|
#185 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#186 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#187 = DEFINITIONAL_REPRESENTATION('',(#188),#192);
|
||||||
|
#188 = LINE('',#189,#190);
|
||||||
|
#189 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#190 = VECTOR('',#191,1.);
|
||||||
|
#191 = DIRECTION('',(0.,1.));
|
||||||
|
#192 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#193 = ADVANCED_FACE('',(#194),#182,.T.);
|
||||||
|
#194 = FACE_BOUND('',#195,.T.);
|
||||||
|
#195 = EDGE_LOOP('',(#196,#197,#220,#248));
|
||||||
|
#196 = ORIENTED_EDGE('',*,*,#168,.F.);
|
||||||
|
#197 = ORIENTED_EDGE('',*,*,#198,.T.);
|
||||||
|
#198 = EDGE_CURVE('',#24,#199,#201,.T.);
|
||||||
|
#199 = VERTEX_POINT('',#200);
|
||||||
|
#200 = CARTESIAN_POINT('',(100.,0.,100.));
|
||||||
|
#201 = SURFACE_CURVE('',#202,(#206,#213),.PCURVE_S1.);
|
||||||
|
#202 = LINE('',#203,#204);
|
||||||
|
#203 = CARTESIAN_POINT('',(0.,0.,100.));
|
||||||
|
#204 = VECTOR('',#205,1.);
|
||||||
|
#205 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#206 = PCURVE('',#182,#207);
|
||||||
|
#207 = DEFINITIONAL_REPRESENTATION('',(#208),#212);
|
||||||
|
#208 = LINE('',#209,#210);
|
||||||
|
#209 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#210 = VECTOR('',#211,1.);
|
||||||
|
#211 = DIRECTION('',(1.,0.));
|
||||||
|
#212 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#213 = PCURVE('',#44,#214);
|
||||||
|
#214 = DEFINITIONAL_REPRESENTATION('',(#215),#219);
|
||||||
|
#215 = LINE('',#216,#217);
|
||||||
|
#216 = CARTESIAN_POINT('',(100.,0.));
|
||||||
|
#217 = VECTOR('',#218,1.);
|
||||||
|
#218 = DIRECTION('',(0.,1.));
|
||||||
|
#219 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#220 = ORIENTED_EDGE('',*,*,#221,.T.);
|
||||||
|
#221 = EDGE_CURVE('',#199,#222,#224,.T.);
|
||||||
|
#222 = VERTEX_POINT('',#223);
|
||||||
|
#223 = CARTESIAN_POINT('',(100.,100.,100.));
|
||||||
|
#224 = SURFACE_CURVE('',#225,(#229,#236),.PCURVE_S1.);
|
||||||
|
#225 = LINE('',#226,#227);
|
||||||
|
#226 = CARTESIAN_POINT('',(100.,0.,100.));
|
||||||
|
#227 = VECTOR('',#228,1.);
|
||||||
|
#228 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#229 = PCURVE('',#182,#230);
|
||||||
|
#230 = DEFINITIONAL_REPRESENTATION('',(#231),#235);
|
||||||
|
#231 = LINE('',#232,#233);
|
||||||
|
#232 = CARTESIAN_POINT('',(100.,0.));
|
||||||
|
#233 = VECTOR('',#234,1.);
|
||||||
|
#234 = DIRECTION('',(0.,1.));
|
||||||
|
#235 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#236 = PCURVE('',#237,#242);
|
||||||
|
#237 = PLANE('',#238);
|
||||||
|
#238 = AXIS2_PLACEMENT_3D('',#239,#240,#241);
|
||||||
|
#239 = CARTESIAN_POINT('',(100.,0.,100.));
|
||||||
|
#240 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#241 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#242 = DEFINITIONAL_REPRESENTATION('',(#243),#247);
|
||||||
|
#243 = LINE('',#244,#245);
|
||||||
|
#244 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#245 = VECTOR('',#246,1.);
|
||||||
|
#246 = DIRECTION('',(0.,-1.));
|
||||||
|
#247 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#248 = ORIENTED_EDGE('',*,*,#249,.F.);
|
||||||
|
#249 = EDGE_CURVE('',#141,#222,#250,.T.);
|
||||||
|
#250 = SURFACE_CURVE('',#251,(#255,#262),.PCURVE_S1.);
|
||||||
|
#251 = LINE('',#252,#253);
|
||||||
|
#252 = CARTESIAN_POINT('',(0.,100.,100.));
|
||||||
|
#253 = VECTOR('',#254,1.);
|
||||||
|
#254 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#255 = PCURVE('',#182,#256);
|
||||||
|
#256 = DEFINITIONAL_REPRESENTATION('',(#257),#261);
|
||||||
|
#257 = LINE('',#258,#259);
|
||||||
|
#258 = CARTESIAN_POINT('',(0.,100.));
|
||||||
|
#259 = VECTOR('',#260,1.);
|
||||||
|
#260 = DIRECTION('',(1.,0.));
|
||||||
|
#261 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#262 = PCURVE('',#156,#263);
|
||||||
|
#263 = DEFINITIONAL_REPRESENTATION('',(#264),#268);
|
||||||
|
#264 = LINE('',#265,#266);
|
||||||
|
#265 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#266 = VECTOR('',#267,1.);
|
||||||
|
#267 = DIRECTION('',(0.,1.));
|
||||||
|
#268 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#269 = ADVANCED_FACE('',(#270),#237,.F.);
|
||||||
|
#270 = FACE_BOUND('',#271,.F.);
|
||||||
|
#271 = EDGE_LOOP('',(#272,#300,#301));
|
||||||
|
#272 = ORIENTED_EDGE('',*,*,#273,.F.);
|
||||||
|
#273 = EDGE_CURVE('',#199,#274,#276,.T.);
|
||||||
|
#274 = VERTEX_POINT('',#275);
|
||||||
|
#275 = CARTESIAN_POINT('',(100.,100.,200.));
|
||||||
|
#276 = SURFACE_CURVE('',#277,(#281,#288),.PCURVE_S1.);
|
||||||
|
#277 = LINE('',#278,#279);
|
||||||
|
#278 = CARTESIAN_POINT('',(100.,50.,150.));
|
||||||
|
#279 = VECTOR('',#280,1.);
|
||||||
|
#280 = DIRECTION('',(0.,0.707106781187,0.707106781187));
|
||||||
|
#281 = PCURVE('',#237,#282);
|
||||||
|
#282 = DEFINITIONAL_REPRESENTATION('',(#283),#287);
|
||||||
|
#283 = LINE('',#284,#285);
|
||||||
|
#284 = CARTESIAN_POINT('',(50.,-50.));
|
||||||
|
#285 = VECTOR('',#286,1.);
|
||||||
|
#286 = DIRECTION('',(0.707106781187,-0.707106781187));
|
||||||
|
#287 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#288 = PCURVE('',#289,#294);
|
||||||
|
#289 = PLANE('',#290);
|
||||||
|
#290 = AXIS2_PLACEMENT_3D('',#291,#292,#293);
|
||||||
|
#291 = CARTESIAN_POINT('',(0.,100.,200.));
|
||||||
|
#292 = DIRECTION('',(0.,0.707106781187,-0.707106781187));
|
||||||
|
#293 = DIRECTION('',(0.,0.707106781187,0.707106781187));
|
||||||
|
#294 = DEFINITIONAL_REPRESENTATION('',(#295),#299);
|
||||||
|
#295 = LINE('',#296,#297);
|
||||||
|
#296 = CARTESIAN_POINT('',(-70.71067811865,100.));
|
||||||
|
#297 = VECTOR('',#298,1.);
|
||||||
|
#298 = DIRECTION('',(1.,0.));
|
||||||
|
#299 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#300 = ORIENTED_EDGE('',*,*,#221,.T.);
|
||||||
|
#301 = ORIENTED_EDGE('',*,*,#302,.T.);
|
||||||
|
#302 = EDGE_CURVE('',#222,#274,#303,.T.);
|
||||||
|
#303 = SURFACE_CURVE('',#304,(#308,#315),.PCURVE_S1.);
|
||||||
|
#304 = LINE('',#305,#306);
|
||||||
|
#305 = CARTESIAN_POINT('',(100.,100.,100.));
|
||||||
|
#306 = VECTOR('',#307,1.);
|
||||||
|
#307 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#308 = PCURVE('',#237,#309);
|
||||||
|
#309 = DEFINITIONAL_REPRESENTATION('',(#310),#314);
|
||||||
|
#310 = LINE('',#311,#312);
|
||||||
|
#311 = CARTESIAN_POINT('',(0.,-100.));
|
||||||
|
#312 = VECTOR('',#313,1.);
|
||||||
|
#313 = DIRECTION('',(1.,0.));
|
||||||
|
#314 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#315 = PCURVE('',#156,#316);
|
||||||
|
#316 = DEFINITIONAL_REPRESENTATION('',(#317),#321);
|
||||||
|
#317 = LINE('',#318,#319);
|
||||||
|
#318 = CARTESIAN_POINT('',(0.,100.));
|
||||||
|
#319 = VECTOR('',#320,1.);
|
||||||
|
#320 = DIRECTION('',(1.,0.));
|
||||||
|
#321 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#322 = ADVANCED_FACE('',(#323),#289,.F.);
|
||||||
|
#323 = FACE_BOUND('',#324,.F.);
|
||||||
|
#324 = EDGE_LOOP('',(#325,#355,#376,#377));
|
||||||
|
#325 = ORIENTED_EDGE('',*,*,#326,.F.);
|
||||||
|
#326 = EDGE_CURVE('',#327,#329,#331,.T.);
|
||||||
|
#327 = VERTEX_POINT('',#328);
|
||||||
|
#328 = CARTESIAN_POINT('',(200.,0.,100.));
|
||||||
|
#329 = VERTEX_POINT('',#330);
|
||||||
|
#330 = CARTESIAN_POINT('',(200.,100.,200.));
|
||||||
|
#331 = SURFACE_CURVE('',#332,(#336,#343),.PCURVE_S1.);
|
||||||
|
#332 = LINE('',#333,#334);
|
||||||
|
#333 = CARTESIAN_POINT('',(200.,50.,150.));
|
||||||
|
#334 = VECTOR('',#335,1.);
|
||||||
|
#335 = DIRECTION('',(0.,0.707106781187,0.707106781187));
|
||||||
|
#336 = PCURVE('',#289,#337);
|
||||||
|
#337 = DEFINITIONAL_REPRESENTATION('',(#338),#342);
|
||||||
|
#338 = LINE('',#339,#340);
|
||||||
|
#339 = CARTESIAN_POINT('',(-70.71067811865,200.));
|
||||||
|
#340 = VECTOR('',#341,1.);
|
||||||
|
#341 = DIRECTION('',(1.,0.));
|
||||||
|
#342 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#343 = PCURVE('',#344,#349);
|
||||||
|
#344 = PLANE('',#345);
|
||||||
|
#345 = AXIS2_PLACEMENT_3D('',#346,#347,#348);
|
||||||
|
#346 = CARTESIAN_POINT('',(200.,0.,0.));
|
||||||
|
#347 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#348 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#349 = DEFINITIONAL_REPRESENTATION('',(#350),#354);
|
||||||
|
#350 = LINE('',#351,#352);
|
||||||
|
#351 = CARTESIAN_POINT('',(150.,-50.));
|
||||||
|
#352 = VECTOR('',#353,1.);
|
||||||
|
#353 = DIRECTION('',(0.707106781187,-0.707106781187));
|
||||||
|
#354 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#355 = ORIENTED_EDGE('',*,*,#356,.F.);
|
||||||
|
#356 = EDGE_CURVE('',#199,#327,#357,.T.);
|
||||||
|
#357 = SURFACE_CURVE('',#358,(#362,#369),.PCURVE_S1.);
|
||||||
|
#358 = LINE('',#359,#360);
|
||||||
|
#359 = CARTESIAN_POINT('',(100.,0.,100.));
|
||||||
|
#360 = VECTOR('',#361,1.);
|
||||||
|
#361 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#362 = PCURVE('',#289,#363);
|
||||||
|
#363 = DEFINITIONAL_REPRESENTATION('',(#364),#368);
|
||||||
|
#364 = LINE('',#365,#366);
|
||||||
|
#365 = CARTESIAN_POINT('',(-141.4213562373,100.));
|
||||||
|
#366 = VECTOR('',#367,1.);
|
||||||
|
#367 = DIRECTION('',(0.,1.));
|
||||||
|
#368 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#369 = PCURVE('',#44,#370);
|
||||||
|
#370 = DEFINITIONAL_REPRESENTATION('',(#371),#375);
|
||||||
|
#371 = LINE('',#372,#373);
|
||||||
|
#372 = CARTESIAN_POINT('',(100.,100.));
|
||||||
|
#373 = VECTOR('',#374,1.);
|
||||||
|
#374 = DIRECTION('',(0.,1.));
|
||||||
|
#375 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#376 = ORIENTED_EDGE('',*,*,#273,.T.);
|
||||||
|
#377 = ORIENTED_EDGE('',*,*,#378,.T.);
|
||||||
|
#378 = EDGE_CURVE('',#274,#329,#379,.T.);
|
||||||
|
#379 = SURFACE_CURVE('',#380,(#384,#391),.PCURVE_S1.);
|
||||||
|
#380 = LINE('',#381,#382);
|
||||||
|
#381 = CARTESIAN_POINT('',(100.,100.,200.));
|
||||||
|
#382 = VECTOR('',#383,1.);
|
||||||
|
#383 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#384 = PCURVE('',#289,#385);
|
||||||
|
#385 = DEFINITIONAL_REPRESENTATION('',(#386),#390);
|
||||||
|
#386 = LINE('',#387,#388);
|
||||||
|
#387 = CARTESIAN_POINT('',(0.,100.));
|
||||||
|
#388 = VECTOR('',#389,1.);
|
||||||
|
#389 = DIRECTION('',(0.,1.));
|
||||||
|
#390 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#391 = PCURVE('',#128,#392);
|
||||||
|
#392 = DEFINITIONAL_REPRESENTATION('',(#393),#397);
|
||||||
|
#393 = LINE('',#394,#395);
|
||||||
|
#394 = CARTESIAN_POINT('',(100.,0.));
|
||||||
|
#395 = VECTOR('',#396,1.);
|
||||||
|
#396 = DIRECTION('',(1.,0.));
|
||||||
|
#397 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#398 = ADVANCED_FACE('',(#399),#344,.T.);
|
||||||
|
#399 = FACE_BOUND('',#400,.T.);
|
||||||
|
#400 = EDGE_LOOP('',(#401,#424,#447,#470,#491));
|
||||||
|
#401 = ORIENTED_EDGE('',*,*,#402,.F.);
|
||||||
|
#402 = EDGE_CURVE('',#403,#327,#405,.T.);
|
||||||
|
#403 = VERTEX_POINT('',#404);
|
||||||
|
#404 = CARTESIAN_POINT('',(200.,0.,0.));
|
||||||
|
#405 = SURFACE_CURVE('',#406,(#410,#417),.PCURVE_S1.);
|
||||||
|
#406 = LINE('',#407,#408);
|
||||||
|
#407 = CARTESIAN_POINT('',(200.,0.,0.));
|
||||||
|
#408 = VECTOR('',#409,1.);
|
||||||
|
#409 = DIRECTION('',(0.,0.,1.));
|
||||||
|
#410 = PCURVE('',#344,#411);
|
||||||
|
#411 = DEFINITIONAL_REPRESENTATION('',(#412),#416);
|
||||||
|
#412 = LINE('',#413,#414);
|
||||||
|
#413 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#414 = VECTOR('',#415,1.);
|
||||||
|
#415 = DIRECTION('',(1.,0.));
|
||||||
|
#416 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#417 = PCURVE('',#44,#418);
|
||||||
|
#418 = DEFINITIONAL_REPRESENTATION('',(#419),#423);
|
||||||
|
#419 = LINE('',#420,#421);
|
||||||
|
#420 = CARTESIAN_POINT('',(0.,200.));
|
||||||
|
#421 = VECTOR('',#422,1.);
|
||||||
|
#422 = DIRECTION('',(1.,0.));
|
||||||
|
#423 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#424 = ORIENTED_EDGE('',*,*,#425,.F.);
|
||||||
|
#425 = EDGE_CURVE('',#426,#403,#428,.T.);
|
||||||
|
#426 = VERTEX_POINT('',#427);
|
||||||
|
#427 = CARTESIAN_POINT('',(200.,200.,0.));
|
||||||
|
#428 = SURFACE_CURVE('',#429,(#433,#440),.PCURVE_S1.);
|
||||||
|
#429 = LINE('',#430,#431);
|
||||||
|
#430 = CARTESIAN_POINT('',(200.,200.,0.));
|
||||||
|
#431 = VECTOR('',#432,1.);
|
||||||
|
#432 = DIRECTION('',(0.,-1.,0.));
|
||||||
|
#433 = PCURVE('',#344,#434);
|
||||||
|
#434 = DEFINITIONAL_REPRESENTATION('',(#435),#439);
|
||||||
|
#435 = LINE('',#436,#437);
|
||||||
|
#436 = CARTESIAN_POINT('',(0.,-200.));
|
||||||
|
#437 = VECTOR('',#438,1.);
|
||||||
|
#438 = DIRECTION('',(0.,1.));
|
||||||
|
#439 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#440 = PCURVE('',#72,#441);
|
||||||
|
#441 = DEFINITIONAL_REPRESENTATION('',(#442),#446);
|
||||||
|
#442 = LINE('',#443,#444);
|
||||||
|
#443 = CARTESIAN_POINT('',(200.,200.));
|
||||||
|
#444 = VECTOR('',#445,1.);
|
||||||
|
#445 = DIRECTION('',(0.,-1.));
|
||||||
|
#446 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#447 = ORIENTED_EDGE('',*,*,#448,.F.);
|
||||||
|
#448 = EDGE_CURVE('',#449,#426,#451,.T.);
|
||||||
|
#449 = VERTEX_POINT('',#450);
|
||||||
|
#450 = CARTESIAN_POINT('',(200.,200.,200.));
|
||||||
|
#451 = SURFACE_CURVE('',#452,(#456,#463),.PCURVE_S1.);
|
||||||
|
#452 = LINE('',#453,#454);
|
||||||
|
#453 = CARTESIAN_POINT('',(200.,200.,200.));
|
||||||
|
#454 = VECTOR('',#455,1.);
|
||||||
|
#455 = DIRECTION('',(0.,0.,-1.));
|
||||||
|
#456 = PCURVE('',#344,#457);
|
||||||
|
#457 = DEFINITIONAL_REPRESENTATION('',(#458),#462);
|
||||||
|
#458 = LINE('',#459,#460);
|
||||||
|
#459 = CARTESIAN_POINT('',(200.,-200.));
|
||||||
|
#460 = VECTOR('',#461,1.);
|
||||||
|
#461 = DIRECTION('',(-1.,0.));
|
||||||
|
#462 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#463 = PCURVE('',#100,#464);
|
||||||
|
#464 = DEFINITIONAL_REPRESENTATION('',(#465),#469);
|
||||||
|
#465 = LINE('',#466,#467);
|
||||||
|
#466 = CARTESIAN_POINT('',(200.,200.));
|
||||||
|
#467 = VECTOR('',#468,1.);
|
||||||
|
#468 = DIRECTION('',(-1.,0.));
|
||||||
|
#469 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#470 = ORIENTED_EDGE('',*,*,#471,.F.);
|
||||||
|
#471 = EDGE_CURVE('',#329,#449,#472,.T.);
|
||||||
|
#472 = SURFACE_CURVE('',#473,(#477,#484),.PCURVE_S1.);
|
||||||
|
#473 = LINE('',#474,#475);
|
||||||
|
#474 = CARTESIAN_POINT('',(200.,100.,200.));
|
||||||
|
#475 = VECTOR('',#476,1.);
|
||||||
|
#476 = DIRECTION('',(-0.,1.,0.));
|
||||||
|
#477 = PCURVE('',#344,#478);
|
||||||
|
#478 = DEFINITIONAL_REPRESENTATION('',(#479),#483);
|
||||||
|
#479 = LINE('',#480,#481);
|
||||||
|
#480 = CARTESIAN_POINT('',(200.,-100.));
|
||||||
|
#481 = VECTOR('',#482,1.);
|
||||||
|
#482 = DIRECTION('',(0.,-1.));
|
||||||
|
#483 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#484 = PCURVE('',#128,#485);
|
||||||
|
#485 = DEFINITIONAL_REPRESENTATION('',(#486),#490);
|
||||||
|
#486 = LINE('',#487,#488);
|
||||||
|
#487 = CARTESIAN_POINT('',(200.,0.));
|
||||||
|
#488 = VECTOR('',#489,1.);
|
||||||
|
#489 = DIRECTION('',(0.,1.));
|
||||||
|
#490 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#491 = ORIENTED_EDGE('',*,*,#326,.F.);
|
||||||
|
#492 = ADVANCED_FACE('',(#493),#156,.F.);
|
||||||
|
#493 = FACE_BOUND('',#494,.F.);
|
||||||
|
#494 = EDGE_LOOP('',(#495,#496,#497,#518));
|
||||||
|
#495 = ORIENTED_EDGE('',*,*,#249,.F.);
|
||||||
|
#496 = ORIENTED_EDGE('',*,*,#140,.T.);
|
||||||
|
#497 = ORIENTED_EDGE('',*,*,#498,.T.);
|
||||||
|
#498 = EDGE_CURVE('',#113,#274,#499,.T.);
|
||||||
|
#499 = SURFACE_CURVE('',#500,(#504,#511),.PCURVE_S1.);
|
||||||
|
#500 = LINE('',#501,#502);
|
||||||
|
#501 = CARTESIAN_POINT('',(0.,100.,200.));
|
||||||
|
#502 = VECTOR('',#503,1.);
|
||||||
|
#503 = DIRECTION('',(1.,0.,-0.));
|
||||||
|
#504 = PCURVE('',#156,#505);
|
||||||
|
#505 = DEFINITIONAL_REPRESENTATION('',(#506),#510);
|
||||||
|
#506 = LINE('',#507,#508);
|
||||||
|
#507 = CARTESIAN_POINT('',(100.,0.));
|
||||||
|
#508 = VECTOR('',#509,1.);
|
||||||
|
#509 = DIRECTION('',(0.,1.));
|
||||||
|
#510 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#511 = PCURVE('',#128,#512);
|
||||||
|
#512 = DEFINITIONAL_REPRESENTATION('',(#513),#517);
|
||||||
|
#513 = LINE('',#514,#515);
|
||||||
|
#514 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#515 = VECTOR('',#516,1.);
|
||||||
|
#516 = DIRECTION('',(1.,0.));
|
||||||
|
#517 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#518 = ORIENTED_EDGE('',*,*,#302,.F.);
|
||||||
|
#519 = ADVANCED_FACE('',(#520),#128,.T.);
|
||||||
|
#520 = FACE_BOUND('',#521,.T.);
|
||||||
|
#521 = EDGE_LOOP('',(#522,#523,#524,#525,#526));
|
||||||
|
#522 = ORIENTED_EDGE('',*,*,#112,.F.);
|
||||||
|
#523 = ORIENTED_EDGE('',*,*,#498,.T.);
|
||||||
|
#524 = ORIENTED_EDGE('',*,*,#378,.T.);
|
||||||
|
#525 = ORIENTED_EDGE('',*,*,#471,.T.);
|
||||||
|
#526 = ORIENTED_EDGE('',*,*,#527,.F.);
|
||||||
|
#527 = EDGE_CURVE('',#85,#449,#528,.T.);
|
||||||
|
#528 = SURFACE_CURVE('',#529,(#533,#540),.PCURVE_S1.);
|
||||||
|
#529 = LINE('',#530,#531);
|
||||||
|
#530 = CARTESIAN_POINT('',(0.,200.,200.));
|
||||||
|
#531 = VECTOR('',#532,1.);
|
||||||
|
#532 = DIRECTION('',(1.,0.,0.));
|
||||||
|
#533 = PCURVE('',#128,#534);
|
||||||
|
#534 = DEFINITIONAL_REPRESENTATION('',(#535),#539);
|
||||||
|
#535 = LINE('',#536,#537);
|
||||||
|
#536 = CARTESIAN_POINT('',(0.,100.));
|
||||||
|
#537 = VECTOR('',#538,1.);
|
||||||
|
#538 = DIRECTION('',(1.,0.));
|
||||||
|
#539 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#540 = PCURVE('',#100,#541);
|
||||||
|
#541 = DEFINITIONAL_REPRESENTATION('',(#542),#546);
|
||||||
|
#542 = LINE('',#543,#544);
|
||||||
|
#543 = CARTESIAN_POINT('',(200.,0.));
|
||||||
|
#544 = VECTOR('',#545,1.);
|
||||||
|
#545 = DIRECTION('',(0.,1.));
|
||||||
|
#546 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#547 = ADVANCED_FACE('',(#548),#100,.T.);
|
||||||
|
#548 = FACE_BOUND('',#549,.T.);
|
||||||
|
#549 = EDGE_LOOP('',(#550,#571,#572,#573));
|
||||||
|
#550 = ORIENTED_EDGE('',*,*,#551,.F.);
|
||||||
|
#551 = EDGE_CURVE('',#57,#426,#552,.T.);
|
||||||
|
#552 = SURFACE_CURVE('',#553,(#557,#564),.PCURVE_S1.);
|
||||||
|
#553 = LINE('',#554,#555);
|
||||||
|
#554 = CARTESIAN_POINT('',(0.,200.,0.));
|
||||||
|
#555 = VECTOR('',#556,1.);
|
||||||
|
#556 = DIRECTION('',(1.,0.,0.));
|
||||||
|
#557 = PCURVE('',#100,#558);
|
||||||
|
#558 = DEFINITIONAL_REPRESENTATION('',(#559),#563);
|
||||||
|
#559 = LINE('',#560,#561);
|
||||||
|
#560 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#561 = VECTOR('',#562,1.);
|
||||||
|
#562 = DIRECTION('',(0.,1.));
|
||||||
|
#563 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#564 = PCURVE('',#72,#565);
|
||||||
|
#565 = DEFINITIONAL_REPRESENTATION('',(#566),#570);
|
||||||
|
#566 = LINE('',#567,#568);
|
||||||
|
#567 = CARTESIAN_POINT('',(0.,200.));
|
||||||
|
#568 = VECTOR('',#569,1.);
|
||||||
|
#569 = DIRECTION('',(1.,0.));
|
||||||
|
#570 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#571 = ORIENTED_EDGE('',*,*,#84,.F.);
|
||||||
|
#572 = ORIENTED_EDGE('',*,*,#527,.T.);
|
||||||
|
#573 = ORIENTED_EDGE('',*,*,#448,.T.);
|
||||||
|
#574 = ADVANCED_FACE('',(#575),#72,.F.);
|
||||||
|
#575 = FACE_BOUND('',#576,.F.);
|
||||||
|
#576 = EDGE_LOOP('',(#577,#598,#599,#600));
|
||||||
|
#577 = ORIENTED_EDGE('',*,*,#578,.T.);
|
||||||
|
#578 = EDGE_CURVE('',#22,#403,#579,.T.);
|
||||||
|
#579 = SURFACE_CURVE('',#580,(#584,#591),.PCURVE_S1.);
|
||||||
|
#580 = LINE('',#581,#582);
|
||||||
|
#581 = CARTESIAN_POINT('',(0.,0.,0.));
|
||||||
|
#582 = VECTOR('',#583,1.);
|
||||||
|
#583 = DIRECTION('',(1.,0.,0.));
|
||||||
|
#584 = PCURVE('',#72,#585);
|
||||||
|
#585 = DEFINITIONAL_REPRESENTATION('',(#586),#590);
|
||||||
|
#586 = LINE('',#587,#588);
|
||||||
|
#587 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#588 = VECTOR('',#589,1.);
|
||||||
|
#589 = DIRECTION('',(1.,0.));
|
||||||
|
#590 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#591 = PCURVE('',#44,#592);
|
||||||
|
#592 = DEFINITIONAL_REPRESENTATION('',(#593),#597);
|
||||||
|
#593 = LINE('',#594,#595);
|
||||||
|
#594 = CARTESIAN_POINT('',(0.,0.));
|
||||||
|
#595 = VECTOR('',#596,1.);
|
||||||
|
#596 = DIRECTION('',(0.,1.));
|
||||||
|
#597 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||||
|
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
|
||||||
|
) );
|
||||||
|
#598 = ORIENTED_EDGE('',*,*,#425,.F.);
|
||||||
|
#599 = ORIENTED_EDGE('',*,*,#551,.F.);
|
||||||
|
#600 = ORIENTED_EDGE('',*,*,#56,.T.);
|
||||||
|
#601 = ADVANCED_FACE('',(#602),#44,.F.);
|
||||||
|
#602 = FACE_BOUND('',#603,.F.);
|
||||||
|
#603 = EDGE_LOOP('',(#604,#605,#606,#607,#608));
|
||||||
|
#604 = ORIENTED_EDGE('',*,*,#578,.F.);
|
||||||
|
#605 = ORIENTED_EDGE('',*,*,#21,.T.);
|
||||||
|
#606 = ORIENTED_EDGE('',*,*,#198,.T.);
|
||||||
|
#607 = ORIENTED_EDGE('',*,*,#356,.T.);
|
||||||
|
#608 = ORIENTED_EDGE('',*,*,#402,.F.);
|
||||||
|
#609 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
|
||||||
|
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#613)) GLOBAL_UNIT_ASSIGNED_CONTEXT
|
||||||
|
((#610,#611,#612)) REPRESENTATION_CONTEXT('Context #1',
|
||||||
|
'3D Context with UNIT and UNCERTAINTY') );
|
||||||
|
#610 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
|
||||||
|
#611 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
|
||||||
|
#612 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
|
||||||
|
#613 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(2.E-07),#610,
|
||||||
|
'distance_accuracy_value','confusion accuracy');
|
||||||
|
#614 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
|
||||||
|
ENDSEC;
|
||||||
|
END-ISO-10303-21;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# CUBE01
|
||||||
|
|
||||||
|
Caso de prueba del profesorado. Cubo de 200 mm con un escalón en una mitad y un chaflán a 45° en la otra.
|
||||||
|
|
||||||
|
- Escala: 1:2 (en el cajetín, campo *Title*)
|
||||||
|
- Cotas: ninguna
|
||||||
|
- Particularidad: planta en sistema europeo y perfil en sistema americano (mezcla intencionada)
|
||||||
|
- Volumen esperado: 6 500 000 mm³
|
||||||
|
- Avisos esperados: `SIN_COTAS`, `PROYECCION_MIXTA`
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,30 @@
|
||||||
|
"""Comprueba que motor.api cumple el contrato que usa la aplicación."""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import cadquery as cq
|
||||||
|
|
||||||
|
from motor.api import Estado, Gravedad, Resultado, exportar, procesar
|
||||||
|
|
||||||
|
CATALOGO = Path(__file__).parent / "catalogo"
|
||||||
|
|
||||||
|
|
||||||
|
def test_procesar_devuelve_un_resultado_valido():
|
||||||
|
r = procesar(CATALOGO / "CUBE01" / "plano.dxf")
|
||||||
|
assert isinstance(r, Resultado)
|
||||||
|
assert r.estado in Estado
|
||||||
|
assert all(a.gravedad in Gravedad for a in r.avisos)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cube01_tiene_el_volumen_esperado():
|
||||||
|
r = procesar(CATALOGO / "CUBE01" / "plano.dxf")
|
||||||
|
# Cubo de 200 mm menos el escalón (100^3) y el chaflán (100^3 / 2)
|
||||||
|
assert abs(r.solido.Volume() - 6_500_000) < 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_exportar_step_y_stl(tmp_path):
|
||||||
|
r = procesar(CATALOGO / "CUBE01" / "plano.dxf")
|
||||||
|
for ext in (".step", ".stl"):
|
||||||
|
exportar(r.solido, tmp_path / f"pieza{ext}")
|
||||||
|
assert (tmp_path / f"pieza{ext}").stat().st_size > 0
|
||||||
|
releida = cq.importers.importStep(str(tmp_path / "pieza.step")).val()
|
||||||
|
assert releida.isValid()
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
[project]
|
||||||
|
name = "plano3d"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Reconstrucción automática de piezas 3D a partir de planos técnicos 2D, con verificación del plano"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
requires-python = ">=3.12,<3.14"
|
||||||
|
dependencies = [
|
||||||
|
"cadquery>=2.8",
|
||||||
|
"ezdxf>=1.4",
|
||||||
|
"shapely>=2.0",
|
||||||
|
"numpy>=2.0",
|
||||||
|
"ortools>=9.10",
|
||||||
|
"PySide6>=6.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8",
|
||||||
|
"pyinstaller>=6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["pruebas"]
|
||||||
|
pythonpath = ["."]
|
||||||
Loading…
Reference in New Issue