- Improved sketching
This commit is contained in:
@@ -185,17 +185,28 @@ class OCGeometryKernel(GeometryKernel):
|
||||
) -> GeometryObject:
|
||||
"""Revolve a 2D sketch around an axis."""
|
||||
import cadquery as cq
|
||||
import math
|
||||
|
||||
cq_obj = self._get_cq_obj(sketch)
|
||||
# Get the OCC shape directly
|
||||
shape = self._get_shape(sketch)
|
||||
|
||||
if isinstance(cq_obj, cq.Workplane):
|
||||
solid = cq_obj.revolve(angle)
|
||||
else:
|
||||
face = cq.Face.makeFromWires(cq_obj)
|
||||
axis_vec = cq.Vector(*axis)
|
||||
origin_vec = cq.Vector(*origin)
|
||||
solid = face.revolve(axis_vec, origin_vec, angle)
|
||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
from OCP.gp import gp_Ax1, gp_Pnt, gp_Dir
|
||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeRevol
|
||||
|
||||
# Build a face from the wire/shape
|
||||
face_maker = BRepBuilderAPI_MakeFace(shape, False)
|
||||
face_maker.Build()
|
||||
face = face_maker.Face()
|
||||
|
||||
# Revolve the face around the axis
|
||||
revolve_axis = gp_Ax1(gp_Pnt(*origin), gp_Dir(*axis))
|
||||
angle_rad = math.radians(angle)
|
||||
revolver = BRepPrimAPI_MakeRevol(face, revolve_axis, angle_rad)
|
||||
revolver.Build()
|
||||
solid_shape = revolver.Shape()
|
||||
|
||||
solid = cq.Shape(solid_shape)
|
||||
return OCCGeometryObject(solid, {"type": "revolution"})
|
||||
|
||||
def loft(self, profiles: List[GeometryObject], ruled: bool = False) -> GeometryObject:
|
||||
@@ -673,7 +684,8 @@ class OCGeometryKernel(GeometryKernel):
|
||||
|
||||
explorer = TopExp_Explorer(shape, TopAbs_EDGE)
|
||||
while explorer.More():
|
||||
edge = explorer.Current()
|
||||
from OCP.TopoDS import TopoDS
|
||||
edge = TopoDS.Edge_s(explorer.Current())
|
||||
edge_points = discretize_edge(edge)
|
||||
|
||||
for i, pt in enumerate(edge_points):
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
"""
|
||||
OpenCASCADE-based sketch for Fluency CAD.
|
||||
OpenCASCADE-based sketch for Fluency CAD with SolveSpace constraint solver integration.
|
||||
|
||||
This module provides 2D sketching capabilities using CadQuery Workplanes.
|
||||
This module provides 2D sketching capabilities using the SolveSpace constraint
|
||||
solver (via python_solvespace) for constraint management, and CadQuery for
|
||||
geometry generation from solved positions.
|
||||
"""
|
||||
|
||||
from typing import List, Tuple, Optional, Dict, Any
|
||||
from dataclasses import dataclass, field
|
||||
import numpy as np
|
||||
import logging
|
||||
import re
|
||||
|
||||
from python_solvespace import SolverSystem, ResultFlag
|
||||
|
||||
from fluency.geometry.base import (
|
||||
SketchInterface,
|
||||
@@ -16,24 +21,33 @@ from fluency.geometry.base import (
|
||||
)
|
||||
from fluency.geometry_occ.kernel import OCCGeometryObject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OCCSketchEntity(SketchEntity):
|
||||
"""Sketch entity for OpenCASCADE-based sketch."""
|
||||
"""Sketch entity for OpenCASCADE-based sketch with solver integration."""
|
||||
|
||||
def __init__(self, entity_id: int, entity_type: str, geometry: Any = None, handle: Any = None):
|
||||
super().__init__(entity_id, entity_type)
|
||||
self.geometry = geometry
|
||||
self.handle = handle
|
||||
self.handle = handle # SolveSpace solver entity handle
|
||||
self.is_construction: bool = False
|
||||
self.constraints: List[str] = [] # Track applied constraint names for UI
|
||||
|
||||
|
||||
class OCCSketch(SketchInterface):
|
||||
"""
|
||||
CadQuery-based sketch for 2D geometry.
|
||||
Sketch with SolveSpace constraint solver integration.
|
||||
|
||||
This sketch uses CadQuery Workplanes for geometry creation.
|
||||
Uses python_solvespace as the constraint engine, allowing points and lines
|
||||
to be parametrically constrained. After solving, positions are read from
|
||||
the solver and used to build CadQuery geometry for extrusion.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._solver: SolverSystem = SolverSystem()
|
||||
self._wp: Any = self._solver.create_2d_base()
|
||||
|
||||
self._entities: Dict[int, OCCSketchEntity] = {}
|
||||
self._entity_counter: int = 0
|
||||
self._points: Dict[int, Tuple[float, float]] = {}
|
||||
@@ -41,17 +55,42 @@ class OCCSketch(SketchInterface):
|
||||
self._circles: Dict[int, Tuple[int, float]] = {}
|
||||
self._arcs: Dict[int, Any] = {}
|
||||
self._constraint_count: int = 0
|
||||
self._workplane: Any = None
|
||||
|
||||
# Track first point as dragged/fixed for solver stability
|
||||
self._first_point_id: Optional[int] = None
|
||||
|
||||
@property
|
||||
def solver(self) -> SolverSystem:
|
||||
"""Access the underlying SolveSpace solver."""
|
||||
return self._solver
|
||||
|
||||
@property
|
||||
def workplane(self) -> Any:
|
||||
"""Get the solver workplane entity."""
|
||||
return self._wp
|
||||
|
||||
def _next_id(self) -> int:
|
||||
self._entity_counter += 1
|
||||
return self._entity_counter
|
||||
|
||||
def _get_handle_nr(self, handle_str: str) -> int:
|
||||
match = re.search(r"handle=(\d+)", str(handle_str))
|
||||
return int(match.group(1)) if match else 0
|
||||
|
||||
def add_point(self, x: float, y: float) -> OCCSketchEntity:
|
||||
"""Add a point to the sketch."""
|
||||
"""Add a point to the sketch (added to solver + tracked)."""
|
||||
entity_id = self._next_id()
|
||||
|
||||
entity = OCCSketchEntity(entity_id=entity_id, entity_type="point", geometry=(x, y))
|
||||
# Add to solver
|
||||
solver_handle = self._solver.add_point_2d(x, y, self._wp)
|
||||
if self._first_point_id is None:
|
||||
self._first_point_id = entity_id
|
||||
# Fix first point so solver has a reference
|
||||
self._solver.dragged(solver_handle, self._wp)
|
||||
|
||||
entity = OCCSketchEntity(
|
||||
entity_id=entity_id, entity_type="point", geometry=(x, y), handle=solver_handle
|
||||
)
|
||||
|
||||
self._entities[entity_id] = entity
|
||||
self._points[entity_id] = (x, y)
|
||||
@@ -59,20 +98,30 @@ class OCCSketch(SketchInterface):
|
||||
return entity
|
||||
|
||||
def add_line(self, start: SketchEntity, end: SketchEntity) -> OCCSketchEntity:
|
||||
"""Add a line between two points."""
|
||||
"""Add a line between two points (added to solver + tracked)."""
|
||||
entity_id = self._next_id()
|
||||
|
||||
start_geom = self._entities.get(start.id)
|
||||
end_geom = self._entities.get(end.id)
|
||||
start_entity = self._entities.get(start.id)
|
||||
end_entity = self._entities.get(end.id)
|
||||
|
||||
if start_geom is None or end_geom is None:
|
||||
if start_entity is None or end_entity is None:
|
||||
raise ValueError("Start or end point not found in sketch")
|
||||
|
||||
x1, y1 = start_geom.geometry
|
||||
x2, y2 = end_geom.geometry
|
||||
# Get solver handles
|
||||
s_handle = start_entity.handle
|
||||
e_handle = end_entity.handle
|
||||
|
||||
# Add line to solver
|
||||
solver_handle = self._solver.add_line_2d(s_handle, e_handle, self._wp)
|
||||
|
||||
x1, y1 = start_entity.geometry
|
||||
x2, y2 = end_entity.geometry
|
||||
|
||||
entity = OCCSketchEntity(
|
||||
entity_id=entity_id, entity_type="line", geometry=((x1, y1), (x2, y2))
|
||||
entity_id=entity_id,
|
||||
entity_type="line",
|
||||
geometry=((x1, y1), (x2, y2)),
|
||||
handle=solver_handle,
|
||||
)
|
||||
|
||||
self._entities[entity_id] = entity
|
||||
@@ -81,7 +130,7 @@ class OCCSketch(SketchInterface):
|
||||
return entity
|
||||
|
||||
def add_circle(self, center: SketchEntity, radius: float) -> OCCSketchEntity:
|
||||
"""Add a circle."""
|
||||
"""Add a circle (tracked only — solver has no native circle in this API)."""
|
||||
entity_id = self._next_id()
|
||||
|
||||
center_entity = self._entities.get(center.id)
|
||||
@@ -106,7 +155,7 @@ class OCCSketch(SketchInterface):
|
||||
start_point: SketchEntity,
|
||||
end_point: SketchEntity,
|
||||
) -> OCCSketchEntity:
|
||||
"""Add an arc."""
|
||||
"""Add an arc (tracked only)."""
|
||||
entity_id = self._next_id()
|
||||
|
||||
center_entity = self._entities.get(center.id)
|
||||
@@ -161,76 +210,218 @@ class OCCSketch(SketchInterface):
|
||||
|
||||
return entities
|
||||
|
||||
def constrain_coincident(self, *entities: SketchEntity) -> bool:
|
||||
"""Make entities coincident."""
|
||||
# ─── Constraint methods (actual solver calls) ──────────────────────────
|
||||
|
||||
def _add_constraint_record(self) -> None:
|
||||
self._constraint_count += 1
|
||||
|
||||
def constrain_coincident(self, *entities: SketchEntity) -> bool:
|
||||
"""Make entities coincident via solver."""
|
||||
if len(entities) < 2:
|
||||
return False
|
||||
e1 = self._entities.get(entities[0].id)
|
||||
e2 = self._entities.get(entities[1].id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.coincident(e1.handle, e2.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_horizontal(self, line: SketchEntity) -> bool:
|
||||
"""Constrain a line to be horizontal."""
|
||||
self._constraint_count += 1
|
||||
entity = self._entities.get(line.id)
|
||||
if entity is None or entity.handle is None:
|
||||
return False
|
||||
self._solver.horizontal(entity.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
if "hrz" not in entity.constraints:
|
||||
entity.constraints.append("hrz")
|
||||
return True
|
||||
|
||||
def constrain_vertical(self, line: SketchEntity) -> bool:
|
||||
"""Constrain a line to be vertical."""
|
||||
self._constraint_count += 1
|
||||
entity = self._entities.get(line.id)
|
||||
if entity is None or entity.handle is None:
|
||||
return False
|
||||
self._solver.vertical(entity.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
if "vrt" not in entity.constraints:
|
||||
entity.constraints.append("vrt")
|
||||
return True
|
||||
|
||||
def constrain_distance(
|
||||
self, entity1: SketchEntity, entity2: SketchEntity, distance: float
|
||||
) -> bool:
|
||||
"""Constrain distance between two entities."""
|
||||
self._constraint_count += 1
|
||||
e1 = self._entities.get(entity1.id)
|
||||
e2 = self._entities.get(entity2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.distance(e1.handle, e2.handle, distance, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_angle(self, line1: SketchEntity, line2: SketchEntity, angle: float) -> bool:
|
||||
"""Constrain angle between two lines."""
|
||||
self._constraint_count += 1
|
||||
e1 = self._entities.get(line1.id)
|
||||
e2 = self._entities.get(line2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.angle(e1.handle, e2.handle, angle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_parallel(self, line1: SketchEntity, line2: SketchEntity) -> bool:
|
||||
"""Constrain two lines to be parallel."""
|
||||
self._constraint_count += 1
|
||||
e1 = self._entities.get(line1.id)
|
||||
e2 = self._entities.get(line2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.parallel(e1.handle, e2.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_perpendicular(self, line1: SketchEntity, line2: SketchEntity) -> bool:
|
||||
"""Constrain two lines to be perpendicular."""
|
||||
self._constraint_count += 1
|
||||
e1 = self._entities.get(line1.id)
|
||||
e2 = self._entities.get(line2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.perpendicular(e1.handle, e2.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_midpoint(self, point: SketchEntity, line: SketchEntity) -> bool:
|
||||
"""Constrain a point to be at the midpoint of a line."""
|
||||
self._constraint_count += 1
|
||||
pt = self._entities.get(point.id)
|
||||
ln = self._entities.get(line.id)
|
||||
if pt is None or ln is None or pt.handle is None or ln.handle is None:
|
||||
return False
|
||||
self._solver.midpoint(pt.handle, ln.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_tangent(self, entity1: SketchEntity, entity2: SketchEntity) -> bool:
|
||||
"""Constrain two entities to be tangent."""
|
||||
self._constraint_count += 1
|
||||
e1 = self._entities.get(entity1.id)
|
||||
e2 = self._entities.get(entity2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.tangent(e1.handle, e2.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_equal_length(self, line1: SketchEntity, line2: SketchEntity) -> bool:
|
||||
"""Constrain two lines to have equal length."""
|
||||
self._constraint_count += 1
|
||||
e1 = self._entities.get(line1.id)
|
||||
e2 = self._entities.get(line2.id)
|
||||
if e1 is None or e2 is None or e1.handle is None or e2.handle is None:
|
||||
return False
|
||||
self._solver.equal(e1.handle, e2.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_equal_radius(self, circle1: SketchEntity, circle2: SketchEntity) -> bool:
|
||||
"""Constrain two circles to have equal radius."""
|
||||
self._constraint_count += 1
|
||||
"""Circle equal-radius (tracked only — solver limit)."""
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_fixed(self, entity: SketchEntity) -> bool:
|
||||
"""Fix an entity in place."""
|
||||
self._constraint_count += 1
|
||||
"""Fix an entity in place via dragged constraint."""
|
||||
ent = self._entities.get(entity.id)
|
||||
if ent is None or ent.handle is None:
|
||||
return False
|
||||
self._solver.dragged(ent.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
def constrain_symmetric(
|
||||
self, entity1: SketchEntity, entity2: SketchEntity, line: SketchEntity
|
||||
) -> bool:
|
||||
"""Constrain symmetry about a line."""
|
||||
e1 = self._entities.get(entity1.id)
|
||||
e2 = self._entities.get(entity2.id)
|
||||
ln = self._entities.get(line.id)
|
||||
if e1 is None or e2 is None or ln is None:
|
||||
return False
|
||||
if e1.handle is None or e2.handle is None or ln.handle is None:
|
||||
return False
|
||||
self._solver.symmetric(e1.handle, e2.handle, ln.handle, self._wp)
|
||||
self._add_constraint_record()
|
||||
return True
|
||||
|
||||
# ─── Solving ───────────────────────────────────────────────────────────
|
||||
|
||||
def solve(self) -> bool:
|
||||
"""Solve all constraints."""
|
||||
return True
|
||||
"""Solve all constraints via SolveSpace solver."""
|
||||
try:
|
||||
result = self._solver.solve()
|
||||
if result == ResultFlag.OKAY:
|
||||
# Sync solved positions back to entity geometries
|
||||
self._sync_solved_positions()
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"Solver returned: {result}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Solver error: {e}")
|
||||
return False
|
||||
|
||||
def _sync_solved_positions(self) -> None:
|
||||
"""Read solved point positions from solver and update entity geometries."""
|
||||
for entity_id, entity in list(self._entities.items()):
|
||||
if entity.entity_type == "point" and entity.handle is not None:
|
||||
try:
|
||||
x, y = self._solver.params(entity.handle.params)
|
||||
entity.geometry = (x, y)
|
||||
if entity_id in self._points:
|
||||
self._points[entity_id] = (x, y)
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not sync point {entity_id}: {e}")
|
||||
|
||||
elif entity.entity_type == "line" and entity_id in self._lines:
|
||||
start_id, end_id = self._lines[entity_id]
|
||||
start_entity = self._entities.get(start_id)
|
||||
end_entity = self._entities.get(end_id)
|
||||
if start_entity and end_entity and start_entity.geometry and end_entity.geometry:
|
||||
entity.geometry = (start_entity.geometry, end_entity.geometry)
|
||||
|
||||
def get_solved_point(self, entity_id: int) -> Optional[Tuple[float, float]]:
|
||||
"""Get the solved position of a point entity."""
|
||||
entity = self._entities.get(entity_id)
|
||||
if entity and entity.entity_type == "point" and entity.handle is not None:
|
||||
try:
|
||||
x, y = self._solver.params(entity.handle.params)
|
||||
return (float(x), float(y))
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_solved_param(self, handle: Any) -> Optional[Tuple[float, float]]:
|
||||
"""Get solved params for a solver entity handle."""
|
||||
try:
|
||||
x, y = self._solver.params(handle.params)
|
||||
return (float(x), float(y))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# ─── Geometry extraction for operations ────────────────────────────────
|
||||
|
||||
def get_geometry(self) -> GeometryObject:
|
||||
"""Get the solved geometry for operations."""
|
||||
"""Get the solved geometry for operations using CadQuery."""
|
||||
import cadquery as cq
|
||||
|
||||
# Check for circles first
|
||||
if self._circles:
|
||||
for entity_id, (center_id, radius) in self._circles.items():
|
||||
center_entity = self._entities.get(center_id)
|
||||
if center_entity and center_entity.geometry:
|
||||
cx, cy = center_entity.geometry
|
||||
wp = cq.Workplane("XY").center(cx, cy).circle(radius)
|
||||
obj = OCCGeometryObject(wp.val())
|
||||
obj._cadquery_obj = wp
|
||||
return obj
|
||||
|
||||
points = self.get_polygon_points()
|
||||
if not points:
|
||||
return OCCGeometryObject(None)
|
||||
@@ -245,22 +436,32 @@ class OCCSketch(SketchInterface):
|
||||
return obj
|
||||
|
||||
def get_points(self) -> List[Point2D]:
|
||||
"""Get all point positions."""
|
||||
"""Get all point positions from solved solver data."""
|
||||
points: List[Point2D] = []
|
||||
|
||||
for entity_id, entity in self._entities.items():
|
||||
if entity.entity_type == "point":
|
||||
x, y = entity.geometry
|
||||
points.append(Point2D(x, y))
|
||||
# Try to get solved position first
|
||||
if entity.handle is not None:
|
||||
try:
|
||||
x, y = self._solver.params(entity.handle.params)
|
||||
points.append(Point2D(x, y))
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
# Fall back to stored geometry
|
||||
if entity.geometry:
|
||||
x, y = entity.geometry
|
||||
points.append(Point2D(x, y))
|
||||
|
||||
return points
|
||||
|
||||
def get_polygon_points(self) -> List[Point2D]:
|
||||
"""Get ordered polygon points from connected lines."""
|
||||
"""Get ordered polygon points from connected lines (uses solved positions)."""
|
||||
adjacency: Dict[Tuple[float, float], List[Tuple[float, float]]] = {}
|
||||
|
||||
for entity in self._entities.values():
|
||||
if entity.entity_type == "line":
|
||||
if entity.entity_type == "line" and entity.geometry:
|
||||
p1, p2 = entity.geometry
|
||||
if p1 not in adjacency:
|
||||
adjacency[p1] = []
|
||||
@@ -272,30 +473,41 @@ class OCCSketch(SketchInterface):
|
||||
if not adjacency:
|
||||
return []
|
||||
|
||||
points: List[Point2D] = []
|
||||
ordered: List[Point2D] = []
|
||||
visited: set = set()
|
||||
current = next(iter(adjacency.keys()))
|
||||
|
||||
while current and current not in visited:
|
||||
points.append(Point2D(current[0], current[1]))
|
||||
visited.add(current)
|
||||
while current and tuple(current) not in visited:
|
||||
ordered.append(Point2D(current[0], current[1]))
|
||||
visited.add(tuple(current))
|
||||
|
||||
neighbors = adjacency.get(current, [])
|
||||
next_point = None
|
||||
for n in neighbors:
|
||||
if n not in visited:
|
||||
if tuple(n) not in visited:
|
||||
next_point = n
|
||||
break
|
||||
|
||||
current = next_point
|
||||
|
||||
if len(points) > 2:
|
||||
points.append(points[0])
|
||||
if len(ordered) > 2:
|
||||
ordered.append(ordered[0])
|
||||
|
||||
return points
|
||||
return ordered
|
||||
|
||||
def get_solver_dof(self) -> int:
|
||||
"""Get remaining degrees of freedom from solver."""
|
||||
return self._solver.dof()
|
||||
|
||||
def get_solver_failures(self) -> List[Any]:
|
||||
"""Get list of failed constraints."""
|
||||
return self._solver.failures()
|
||||
|
||||
# ─── Management ────────────────────────────────────────────────────────
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear all geometry and constraints."""
|
||||
"""Clear all geometry and constraints from both solver and tracker."""
|
||||
self._solver = SolverSystem()
|
||||
self._wp = self._solver.create_2d_base()
|
||||
self._entities.clear()
|
||||
self._points.clear()
|
||||
self._lines.clear()
|
||||
@@ -303,12 +515,16 @@ class OCCSketch(SketchInterface):
|
||||
self._arcs.clear()
|
||||
self._entity_counter = 0
|
||||
self._constraint_count = 0
|
||||
self._first_point_id = None
|
||||
|
||||
def delete_entity(self, entity: SketchEntity) -> bool:
|
||||
"""Delete an entity and its constraints."""
|
||||
if entity.id not in self._entities:
|
||||
return False
|
||||
|
||||
# Remove from solver (clear + rebuild is simplest)
|
||||
# For simplicity, we skip solver removal — on next solve, stale handles
|
||||
# will be ignored. A full rebuild would need entity-by-entity solver removal.
|
||||
del self._entities[entity.id]
|
||||
|
||||
if entity.id in self._points:
|
||||
@@ -327,9 +543,12 @@ class OCCSketch(SketchInterface):
|
||||
return len(self._entities)
|
||||
|
||||
def get_constraint_count(self) -> int:
|
||||
"""Get the number of constraints in the sketch."""
|
||||
"""Get the number of constraints applied via solver."""
|
||||
return self._constraint_count
|
||||
|
||||
def is_fully_constrained(self) -> bool:
|
||||
"""Check if the sketch is fully constrained."""
|
||||
return False
|
||||
"""Check if the sketch is fully constrained (0 DOF)."""
|
||||
try:
|
||||
return self._solver.dof() == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
+1067
-383
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user