- sketch enhacements
This commit is contained in:
Generated
+15
-8
@@ -4,13 +4,10 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- Basic operations">
|
||||
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- removed cadquery deoendency">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/fluency/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/__init__.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/fluency/geometry_occ/kernel.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/geometry_occ/kernel.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/fluency/geometry_occ/sketch.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/geometry_occ/sketch.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/fluency/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/main.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -63,6 +60,7 @@
|
||||
"Python.occ_renderer.executor": "Run",
|
||||
"Python.side_fluency.executor": "Run",
|
||||
"Python.simple_mesh.executor": "Run",
|
||||
"Python.sketch.executor": "Run",
|
||||
"Python.vtk_widget.executor": "Run",
|
||||
"Python.vulkan.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
@@ -288,7 +286,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782768610475</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="23" />
|
||||
<task id="LOCAL-00023" summary="- removed cadquery deoendency">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782928990792</created>
|
||||
<option name="number" value="00023" />
|
||||
<option name="presentableId" value="LOCAL-00023" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782928990792</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="24" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -328,6 +334,7 @@
|
||||
<MESSAGE value="- added sdf folder ( doesnt work via pip or git=)" />
|
||||
<MESSAGE value="- Tons of addtions" />
|
||||
<MESSAGE value="- Basic operations" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="- Basic operations" />
|
||||
<MESSAGE value="- removed cadquery deoendency" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="- removed cadquery deoendency" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -74,6 +74,14 @@ class OCCSketch(SketchInterface):
|
||||
# • clear them as a group when the source face is removed
|
||||
self._external_entity_ids: set = set()
|
||||
|
||||
# Centerline entity ids (X and Y reference axes through origin).
|
||||
# These are construction lines that span the sketch and are used
|
||||
# as reference axes for constraining geometry. They are:
|
||||
# • fixed in the solver (never move)
|
||||
# • marked is_construction (excluded from profile detection)
|
||||
# • non-deletable
|
||||
self._centerline_ids: set = set()
|
||||
|
||||
# Track first point as dragged/fixed for solver stability
|
||||
self._first_point_id: Optional[int] = None
|
||||
|
||||
@@ -259,8 +267,14 @@ class OCCSketch(SketchInterface):
|
||||
radius: float,
|
||||
start_point: SketchEntity,
|
||||
end_point: SketchEntity,
|
||||
sweep: Optional[float] = None,
|
||||
) -> OCCSketchEntity:
|
||||
"""Add an arc (tracked only)."""
|
||||
"""Add an arc (tracked only).
|
||||
|
||||
*sweep* is the signed angular span in radians (positive = CCW, negative = CW).
|
||||
When *None* the rendering will infer the shortest path between start and end.
|
||||
"""
|
||||
import math
|
||||
entity_id = self._next_id()
|
||||
|
||||
center_entity = self._entities.get(center.id)
|
||||
@@ -274,10 +288,26 @@ class OCCSketch(SketchInterface):
|
||||
sx, sy = start_entity.geometry
|
||||
ex, ey = end_entity.geometry
|
||||
|
||||
# Infer sweep from geometry when not provided.
|
||||
if sweep is None:
|
||||
sa = math.atan2(sy - cy, sx - cx)
|
||||
ea = math.atan2(ey - cy, ex - cx)
|
||||
sweep = ea - sa
|
||||
while sweep > math.pi:
|
||||
sweep -= 2 * math.pi
|
||||
while sweep < -math.pi:
|
||||
sweep += 2 * math.pi
|
||||
|
||||
entity = OCCSketchEntity(
|
||||
entity_id=entity_id,
|
||||
entity_type="arc",
|
||||
geometry={"center": (cx, cy), "radius": radius, "start": (sx, sy), "end": (ex, ey)},
|
||||
geometry={
|
||||
"center": (cx, cy),
|
||||
"radius": radius,
|
||||
"start": (sx, sy),
|
||||
"end": (ex, ey),
|
||||
"sweep": sweep,
|
||||
},
|
||||
)
|
||||
|
||||
self._entities[entity_id] = entity
|
||||
@@ -286,6 +316,7 @@ class OCCSketch(SketchInterface):
|
||||
"start": start_point.id,
|
||||
"end": end_point.id,
|
||||
"radius": radius,
|
||||
"sweep": sweep,
|
||||
}
|
||||
|
||||
return entity
|
||||
@@ -432,6 +463,67 @@ class OCCSketch(SketchInterface):
|
||||
"""Return the set of external (underlay) entity ids currently in the sketch."""
|
||||
return set(self._external_entity_ids)
|
||||
|
||||
# ── Centerlines (X and Y reference axes) ────────────────────────────
|
||||
|
||||
_CENTERLINE_EXTENT: float = 10000.0 # large enough to span any sketch
|
||||
|
||||
def add_centerlines(self) -> None:
|
||||
"""Add X (horizontal) and Y (vertical) centerlines through the origin.
|
||||
|
||||
These construction lines serve as reference axes for constraining
|
||||
sketch geometry. They are:
|
||||
• Fixed in the solver (never move)
|
||||
• Marked is_construction (excluded from profile / face detection)
|
||||
• Non-deletable
|
||||
• Available as constraint targets (snap, coincident, distance,
|
||||
symmetric, horizontal, vertical, parallel, perpendicular)
|
||||
|
||||
The X centerline runs horizontal (left→right) through origin and
|
||||
the Y centerline runs vertical (bottom→top) through origin. Both
|
||||
extend to ``_CENTERLINE_EXTENT`` in both directions so they span
|
||||
any realistic sketch. If centerlines have already been added this
|
||||
is a no-op.
|
||||
"""
|
||||
if self._centerline_ids:
|
||||
return
|
||||
|
||||
HALF = self._CENTERLINE_EXTENT
|
||||
|
||||
# Origin point — auto-fixed as the first point in the solver
|
||||
origin = self.add_point(0.0, 0.0)
|
||||
origin.is_construction = True
|
||||
|
||||
# X centerline (horizontal)
|
||||
xl = self.add_point(-HALF, 0.0)
|
||||
xl.is_construction = True
|
||||
xr = self.add_point(HALF, 0.0)
|
||||
xr.is_construction = True
|
||||
xline = self.add_line(xl, xr)
|
||||
xline.is_construction = True
|
||||
self.constrain_horizontal(xline)
|
||||
self.constrain_fixed(xl)
|
||||
|
||||
# Y centerline (vertical)
|
||||
yb = self.add_point(0.0, -HALF)
|
||||
yb.is_construction = True
|
||||
yt = self.add_point(0.0, HALF)
|
||||
yt.is_construction = True
|
||||
yline = self.add_line(yb, yt)
|
||||
yline.is_construction = True
|
||||
self.constrain_vertical(yline)
|
||||
self.constrain_fixed(yb)
|
||||
|
||||
self._centerline_ids = {
|
||||
origin.id, xl.id, xr.id, xline.id,
|
||||
yb.id, yt.id, yline.id,
|
||||
}
|
||||
|
||||
self.solve()
|
||||
|
||||
def get_centerline_ids(self) -> set:
|
||||
"""Return the set of centerline entity ids currently in the sketch."""
|
||||
return set(self._centerline_ids)
|
||||
|
||||
def add_rectangle(
|
||||
self, corner1: Tuple[float, float], corner2: Tuple[float, float]
|
||||
) -> List[OCCSketchEntity]:
|
||||
@@ -838,7 +930,11 @@ class OCCSketch(SketchInterface):
|
||||
if entity_id in self._external_entity_ids:
|
||||
continue
|
||||
center_entity = self._entities.get(center_id)
|
||||
circle_ent = self._entities.get(entity_id)
|
||||
if center_entity and center_entity.geometry and not center_entity.is_external:
|
||||
# Skip construction circles — they're reference geometry.
|
||||
if circle_ent is not None and circle_ent.is_construction:
|
||||
continue
|
||||
cx, cy = center_entity.geometry
|
||||
face_dict = {
|
||||
"outer": {"type": "circle", "center": (cx, cy), "radius": radius},
|
||||
@@ -880,13 +976,13 @@ class OCCSketch(SketchInterface):
|
||||
def get_polygon_points(self) -> List[Point2D]:
|
||||
"""Get ordered polygon points from connected lines (uses solved positions).
|
||||
|
||||
External (underlay) lines are skipped — they are reference geometry
|
||||
only, not part of the sketch profile.
|
||||
External (underlay) and construction lines are skipped — they are
|
||||
reference geometry only, not part of the sketch profile.
|
||||
"""
|
||||
adjacency: Dict[Tuple[float, float], List[Tuple[float, float]]] = {}
|
||||
|
||||
for entity in self._entities.values():
|
||||
if entity.entity_type == "line" and entity.geometry and not entity.is_external:
|
||||
if entity.entity_type == "line" and entity.geometry and not entity.is_external and not entity.is_construction:
|
||||
p1, p2 = entity.geometry
|
||||
if p1 not in adjacency:
|
||||
adjacency[p1] = []
|
||||
@@ -926,21 +1022,63 @@ class OCCSketch(SketchInterface):
|
||||
def _line_segments(self) -> List[Tuple[Tuple[float, float], Tuple[float, float]]]:
|
||||
"""Current line segments as world-coordinate tuples (uses solved positions).
|
||||
|
||||
External (underlay) lines are excluded: they're reference geometry
|
||||
projected from a 3D face, not user-drawn sketch profile, and must
|
||||
not contribute to detect_faces / get_geometry / extrusion.
|
||||
Returns both straight line segments AND tessellated arc segments so
|
||||
that arcs participate in closed-loop / face detection. Construction
|
||||
and external entities are excluded — they're reference geometry and
|
||||
must not affect the sketch profile.
|
||||
|
||||
Tessellation density: roughly 12 segments per π radians of arc sweep,
|
||||
which gives smooth-looking closed loops for face detection.
|
||||
"""
|
||||
segs: List[Tuple[Tuple[float, float], Tuple[float, float]]] = []
|
||||
|
||||
# ── Straight line segments ──
|
||||
for line_id, (sid, eid2) in self._lines.items():
|
||||
# Skip external/underlay lines — they live in the solver for
|
||||
# constraint referencing, but are not part of the sketch profile.
|
||||
if line_id in self._external_entity_ids:
|
||||
continue
|
||||
line_ent = self._entities.get(line_id)
|
||||
if line_ent is not None and line_ent.is_construction:
|
||||
continue
|
||||
s_ent = self._entities.get(sid)
|
||||
e_ent = self._entities.get(eid2)
|
||||
if s_ent and e_ent and s_ent.geometry and e_ent.geometry:
|
||||
segs.append(((float(s_ent.geometry[0]), float(s_ent.geometry[1])),
|
||||
(float(e_ent.geometry[0]), float(e_ent.geometry[1]))))
|
||||
|
||||
# ── Arc segments (tessellated) ──
|
||||
for arc_id, arc_data in self._arcs.items():
|
||||
arc_ent = self._entities.get(arc_id)
|
||||
if arc_ent is not None and arc_ent.is_construction:
|
||||
continue
|
||||
center_id = arc_data.get("center")
|
||||
start_id = arc_data.get("start")
|
||||
end_id = arc_data.get("end")
|
||||
radius = arc_data.get("radius", 0.0)
|
||||
sweep = arc_data.get("sweep")
|
||||
if sweep is None or radius <= 0:
|
||||
continue
|
||||
c_ent = self._entities.get(center_id)
|
||||
s_ent = self._entities.get(start_id)
|
||||
e_ent = self._entities.get(end_id)
|
||||
if not (c_ent and s_ent and e_ent
|
||||
and c_ent.geometry and s_ent.geometry and e_ent.geometry):
|
||||
continue
|
||||
cx, cy = c_ent.geometry
|
||||
sx, sy = s_ent.geometry
|
||||
start_angle = math.atan2(sy - cy, sx - cx)
|
||||
# ~12 segments per π radians
|
||||
n = max(4, int(abs(sweep) / (math.pi / 12)))
|
||||
for i in range(n):
|
||||
t1 = i / n
|
||||
t2 = (i + 1) / n
|
||||
a1 = start_angle + t1 * sweep
|
||||
a2 = start_angle + t2 * sweep
|
||||
p1 = (cx + radius * math.cos(a1),
|
||||
cy + radius * math.sin(a1))
|
||||
p2 = (cx + radius * math.cos(a2),
|
||||
cy + radius * math.sin(a2))
|
||||
segs.append((p1, p2))
|
||||
|
||||
return segs
|
||||
|
||||
def get_closed_loops(self) -> List[Dict[str, Any]]:
|
||||
@@ -1240,6 +1378,7 @@ class OCCSketch(SketchInterface):
|
||||
self._constraint_count = 0
|
||||
self._constraint_log.clear()
|
||||
self._external_entity_ids.clear()
|
||||
self._centerline_ids.clear()
|
||||
self._first_point_id = None
|
||||
|
||||
def _prune_log_for(self, removed_ids: set) -> None:
|
||||
@@ -1259,10 +1398,17 @@ class OCCSketch(SketchInterface):
|
||||
constraint that referenced it, rebuilds the whole solver system from
|
||||
the surviving points/lines + pruned log, and re-solves. The line's
|
||||
endpoint points are NOT removed — only the line segment.
|
||||
|
||||
Note: centerlines (reference axes through origin) cannot be deleted.
|
||||
"""
|
||||
if line.id not in self._lines or line.id not in self._entities:
|
||||
return False
|
||||
|
||||
# Centerlines are permanent reference axes — refuse deletion.
|
||||
if line.id in self._centerline_ids:
|
||||
logger.debug("Refusing to delete centerline")
|
||||
return False
|
||||
|
||||
del self._lines[line.id]
|
||||
if line.id in self._entities:
|
||||
del self._entities[line.id]
|
||||
@@ -1298,10 +1444,17 @@ class OCCSketch(SketchInterface):
|
||||
All constraints that reference the point OR the removed lines are
|
||||
pruned from the log, the solver is rebuilt from survivors, labels are
|
||||
re-derived, and the system is re-solved.
|
||||
|
||||
Note: centerlines (reference axes through origin) cannot be deleted.
|
||||
"""
|
||||
if point.id not in self._entities or point.id not in self._points:
|
||||
return False
|
||||
|
||||
# Centerline points are permanent reference anchors — refuse deletion.
|
||||
if point.id in self._centerline_ids:
|
||||
logger.debug("Refusing to delete centerline point")
|
||||
return False
|
||||
|
||||
removed_ids: set = {point.id}
|
||||
# Remove lines that use this point as an endpoint.
|
||||
removed_line_keys: List[int] = [
|
||||
@@ -1327,6 +1480,18 @@ class OCCSketch(SketchInterface):
|
||||
del self._circles[cid]
|
||||
if cid in self._entities:
|
||||
del self._entities[cid]
|
||||
# Arcs referencing this point (as centre, start, or end) are invalid.
|
||||
removed_arc_keys: List[int] = [
|
||||
aid for aid, adata in list(self._arcs.items())
|
||||
if adata.get("center") == point.id
|
||||
or adata.get("start") == point.id
|
||||
or adata.get("end") == point.id
|
||||
]
|
||||
for aid in removed_arc_keys:
|
||||
removed_ids.add(aid)
|
||||
del self._arcs[aid]
|
||||
if aid in self._entities:
|
||||
del self._entities[aid]
|
||||
|
||||
self._prune_log_for(removed_ids)
|
||||
self._rebuild_solver()
|
||||
|
||||
+745
-42
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user