- added renderer
- Added undo
This commit is contained in:
Generated
+47
-39
@@ -43,45 +43,45 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"Python.2dtest.executor": "Run",
|
"Python.2dtest.executor": "Run",
|
||||||
"Python.3d_windows.executor": "Run",
|
"Python.3d_windows.executor": "Run",
|
||||||
"Python.Unnamed.executor": "Run",
|
"Python.Unnamed.executor": "Run",
|
||||||
"Python.base.executor": "Run",
|
"Python.base.executor": "Run",
|
||||||
"Python.data_model.executor": "Run",
|
"Python.data_model.executor": "Run",
|
||||||
"Python.draw_widget2d.executor": "Run",
|
"Python.draw_widget2d.executor": "Run",
|
||||||
"Python.draw_widget_solve.executor": "Run",
|
"Python.draw_widget_solve.executor": "Run",
|
||||||
"Python.fluency.executor": "Run",
|
"Python.fluency.executor": "Run",
|
||||||
"Python.fluencyb.executor": "Run",
|
"Python.fluencyb.executor": "Run",
|
||||||
"Python.gl_widget.executor": "Run",
|
"Python.gl_widget.executor": "Run",
|
||||||
"Python.gui_ui.executor": "Run",
|
"Python.gui_ui.executor": "Run",
|
||||||
"Python.kernel.executor": "Run",
|
"Python.kernel.executor": "Run",
|
||||||
"Python.main.executor": "Run",
|
"Python.main.executor": "Run",
|
||||||
"Python.meshtest.executor": "Run",
|
"Python.meshtest.executor": "Run",
|
||||||
"Python.occ_renderer.executor": "Run",
|
"Python.occ_renderer.executor": "Run",
|
||||||
"Python.occ_to_mesh.executor": "Run",
|
"Python.occ_to_mesh.executor": "Run",
|
||||||
"Python.render_backend.executor": "Run",
|
"Python.render_backend.executor": "Run",
|
||||||
"Python.side_fluency.executor": "Run",
|
"Python.side_fluency.executor": "Run",
|
||||||
"Python.simple_mesh.executor": "Run",
|
"Python.simple_mesh.executor": "Run",
|
||||||
"Python.sketch.executor": "Run",
|
"Python.sketch.executor": "Run",
|
||||||
"Python.vtk_widget.executor": "Run",
|
"Python.vtk_widget.executor": "Run",
|
||||||
"Python.vulkan.executor": "Run",
|
"Python.vulkan.executor": "Run",
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
"codeWithMe.voiceChat.enabledByDefault": "false",
|
||||||
"git-widget-placeholder": "feature/occ-migration",
|
"git-widget-placeholder": "feature/occ-migration",
|
||||||
"last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/src/fluency/rendering",
|
"last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/src/fluency/rendering",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="QodanaReportsService">
|
<component name="QodanaReportsService">
|
||||||
<option name="descriptions">
|
<option name="descriptions">
|
||||||
<ReportDescription localRun="true" path="/private/var/folders/kg/zm48w_r96yb68mlbzvb9gtq40000gn/T/qodana_output/qodana.sarif.json" reportGuid="5f5b823c-c594-48c5-ae1f-062e30303918" reportId="fluency/qodana/2024-02-04" />
|
<ReportDescription localRun="true" path="/private/var/folders/kg/zm48w_r96yb68mlbzvb9gtq40000gn/T/qodana_output/qodana.sarif.json" reportGuid="5f5b823c-c594-48c5-ae1f-062e30303918" reportId="fluency/qodana/2024-02-04" />
|
||||||
@@ -411,7 +411,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1783887704766</updated>
|
<updated>1783887704766</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="38" />
|
<task id="LOCAL-00038" summary="- added renderer - Added undo">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1783891561116</created>
|
||||||
|
<option name="number" value="00038" />
|
||||||
|
<option name="presentableId" value="LOCAL-00038" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1783891561116</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="39" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ def occ_shape_to_ply(
|
|||||||
all_faces: List[List[int]] = []
|
all_faces: List[List[int]] = []
|
||||||
vertex_offset = 0
|
vertex_offset = 0
|
||||||
|
|
||||||
|
from OCP.TopAbs import TopAbs_FORWARD
|
||||||
|
|
||||||
explorer = TopExp_Explorer(shape, TopAbs_FACE)
|
explorer = TopExp_Explorer(shape, TopAbs_FACE)
|
||||||
while explorer.More():
|
while explorer.More():
|
||||||
face = TopoDS.Face_s(explorer.Current())
|
face = TopoDS.Face_s(explorer.Current())
|
||||||
@@ -56,6 +58,10 @@ def occ_shape_to_ply(
|
|||||||
# Transform
|
# Transform
|
||||||
trsf = location.Transformation()
|
trsf = location.Transformation()
|
||||||
|
|
||||||
|
# Check face orientation: FORWARD means the surface normal points
|
||||||
|
# outward from the solid; REVERSED means it points inward.
|
||||||
|
is_forward = (face.Orientation() == TopAbs_FORWARD)
|
||||||
|
|
||||||
# Extract vertices (apply location transform to positions)
|
# Extract vertices (apply location transform to positions)
|
||||||
nb_nodes = triangulation.NbNodes()
|
nb_nodes = triangulation.NbNodes()
|
||||||
for i in range(1, nb_nodes + 1):
|
for i in range(1, nb_nodes + 1):
|
||||||
@@ -64,15 +70,25 @@ def occ_shape_to_ply(
|
|||||||
all_vertices.append([pnt.X(), pnt.Y(), pnt.Z()])
|
all_vertices.append([pnt.X(), pnt.Y(), pnt.Z()])
|
||||||
|
|
||||||
# Extract triangles
|
# Extract triangles
|
||||||
|
# For REVERSED faces, swap winding order (n1, n3, n2) so that
|
||||||
|
# the computed normal points outward consistently.
|
||||||
nb_triangles = triangulation.NbTriangles()
|
nb_triangles = triangulation.NbTriangles()
|
||||||
for i in range(1, nb_triangles + 1):
|
for i in range(1, nb_triangles + 1):
|
||||||
tri = triangulation.Triangle(i)
|
tri = triangulation.Triangle(i)
|
||||||
n1, n2, n3 = tri.Get()
|
n1, n2, n3 = tri.Get()
|
||||||
all_faces.append([
|
if is_forward:
|
||||||
n1 - 1 + vertex_offset,
|
all_faces.append([
|
||||||
n2 - 1 + vertex_offset,
|
n1 - 1 + vertex_offset,
|
||||||
n3 - 1 + vertex_offset,
|
n2 - 1 + vertex_offset,
|
||||||
])
|
n3 - 1 + vertex_offset,
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
# Swap winding for REVERSED faces
|
||||||
|
all_faces.append([
|
||||||
|
n1 - 1 + vertex_offset,
|
||||||
|
n3 - 1 + vertex_offset,
|
||||||
|
n2 - 1 + vertex_offset,
|
||||||
|
])
|
||||||
|
|
||||||
vertex_offset += nb_nodes
|
vertex_offset += nb_nodes
|
||||||
explorer.Next()
|
explorer.Next()
|
||||||
@@ -87,10 +103,8 @@ def occ_shape_to_ply(
|
|||||||
f"Tessellation: {len(vertices)} vertices, {len(faces)} triangles"
|
f"Tessellation: {len(vertices)} vertices, {len(faces)} triangles"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute outward-facing vertex normals AND correct face winding.
|
# Compute smooth vertex normals from face normals.
|
||||||
# OCC triangulation produces inconsistent winding across faces (especially
|
# Winding is already corrected during tessellation using OCC face orientation.
|
||||||
# after location transforms). Mitsuba uses face winding for front/back
|
|
||||||
# determination, so both normals AND winding must be consistent.
|
|
||||||
normals, corrected_faces = _compute_outward_normals(vertices, faces, shape)
|
normals, corrected_faces = _compute_outward_normals(vertices, faces, shape)
|
||||||
|
|
||||||
# Write PLY with corrected faces and normals
|
# Write PLY with corrected faces and normals
|
||||||
@@ -110,72 +124,28 @@ def _compute_outward_normals(
|
|||||||
) -> Tuple[np.ndarray, np.ndarray]:
|
) -> Tuple[np.ndarray, np.ndarray]:
|
||||||
"""Compute outward-facing vertex normals and correct face winding.
|
"""Compute outward-facing vertex normals and correct face winding.
|
||||||
|
|
||||||
1. Compute per-face normals from cross product of triangle edges.
|
The winding is already corrected during tessellation using OCC's face
|
||||||
2. Determine correct orientation by checking face normals against the
|
orientation (TopAbs_FORWARD/REVERSED). This function computes smooth
|
||||||
shape centroid (outward = away from center).
|
vertex normals by averaging face normals at shared vertices.
|
||||||
3. Flip triangles with inward normals before accumulating to vertices.
|
|
||||||
4. Average and normalize per-vertex normals.
|
|
||||||
|
|
||||||
Returns (normals, corrected_faces) so the PLY writer can use consistent
|
Returns (normals, corrected_faces) for PLY export.
|
||||||
winding order — critical for Mitsuba which uses winding for front/back
|
|
||||||
face determination.
|
|
||||||
"""
|
"""
|
||||||
n_verts = len(vertices)
|
n_verts = len(vertices)
|
||||||
v_normals = np.zeros((n_verts, 3), dtype=np.float64)
|
v_normals = np.zeros((n_verts, 3), dtype=np.float64)
|
||||||
|
|
||||||
# Compute shape centroid for outward direction reference.
|
|
||||||
# Use OCC bounding box if available, otherwise fall back to vertex bounds.
|
|
||||||
if shape is not None:
|
|
||||||
from OCP.Bnd import Bnd_Box
|
|
||||||
from OCP.BRepBndLib import BRepBndLib
|
|
||||||
|
|
||||||
bbox = Bnd_Box()
|
|
||||||
BRepBndLib.Add_s(shape, bbox)
|
|
||||||
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
||||||
else:
|
|
||||||
vmin = vertices.min(axis=0).astype(np.float64)
|
|
||||||
vmax = vertices.max(axis=0).astype(np.float64)
|
|
||||||
xmin, ymin, zmin = vmin
|
|
||||||
xmax, ymax, zmax = vmax
|
|
||||||
|
|
||||||
centroid = np.array(
|
|
||||||
[(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2],
|
|
||||||
dtype=np.float64,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure faces is 2D (numpy creates (3,) for single-face meshes)
|
# Ensure faces is 2D (numpy creates (3,) for single-face meshes)
|
||||||
if faces.ndim == 1:
|
if faces.ndim == 1:
|
||||||
faces = faces.reshape(1, -1)
|
faces = faces.reshape(1, -1)
|
||||||
|
|
||||||
# Compute face normals from triangle geometry
|
# Winding is already correct from tessellation (face orientation check).
|
||||||
|
# Just compute face normals and accumulate to vertices.
|
||||||
v0 = vertices[faces[:, 0]]
|
v0 = vertices[faces[:, 0]]
|
||||||
v1 = vertices[faces[:, 1]]
|
v1 = vertices[faces[:, 1]]
|
||||||
v2 = vertices[faces[:, 2]]
|
v2 = vertices[faces[:, 2]]
|
||||||
|
|
||||||
edge1 = v1 - v0
|
edge1 = v1 - v0
|
||||||
edge2 = v2 - v0
|
edge2 = v2 - v0
|
||||||
face_normals = np.cross(edge1, edge2)
|
fn = np.cross(edge1, edge2)
|
||||||
|
|
||||||
# Triangle centroids to test direction from shape center
|
|
||||||
tri_centers = (v0 + v1 + v2) / 3.0
|
|
||||||
to_tri = tri_centers - centroid
|
|
||||||
|
|
||||||
# Dot product: positive means normal points away from centroid (outward)
|
|
||||||
dots = np.sum(face_normals * to_tri, axis=1)
|
|
||||||
|
|
||||||
# Faces with negative dot have inward normals — swap columns 1 and 2
|
|
||||||
flip_mask = dots < 0
|
|
||||||
corrected_faces = faces.copy()
|
|
||||||
col1 = corrected_faces[:, 1]
|
|
||||||
col2 = corrected_faces[:, 2]
|
|
||||||
corrected_faces[flip_mask, 1] = col2[flip_mask]
|
|
||||||
corrected_faces[flip_mask, 2] = col1[flip_mask]
|
|
||||||
|
|
||||||
# Recompute face normals after correction
|
|
||||||
v0c = vertices[corrected_faces[:, 0]]
|
|
||||||
v1c = vertices[corrected_faces[:, 1]]
|
|
||||||
v2c = vertices[corrected_faces[:, 2]]
|
|
||||||
fn = np.cross(v1c - v0c, v2c - v0c)
|
|
||||||
|
|
||||||
# Normalize face normals
|
# Normalize face normals
|
||||||
lengths = np.linalg.norm(fn, axis=1, keepdims=True)
|
lengths = np.linalg.norm(fn, axis=1, keepdims=True)
|
||||||
@@ -183,8 +153,8 @@ def _compute_outward_normals(
|
|||||||
fn /= lengths
|
fn /= lengths
|
||||||
|
|
||||||
# Accumulate to vertices
|
# Accumulate to vertices
|
||||||
for i in range(len(corrected_faces)):
|
for i in range(len(faces)):
|
||||||
idx = corrected_faces[i]
|
idx = faces[i]
|
||||||
v_normals[idx[0]] += fn[i]
|
v_normals[idx[0]] += fn[i]
|
||||||
v_normals[idx[1]] += fn[i]
|
v_normals[idx[1]] += fn[i]
|
||||||
v_normals[idx[2]] += fn[i]
|
v_normals[idx[2]] += fn[i]
|
||||||
@@ -194,7 +164,7 @@ def _compute_outward_normals(
|
|||||||
v_lengths[v_lengths < 1e-10] = 1.0
|
v_lengths[v_lengths < 1e-10] = 1.0
|
||||||
v_normals /= v_lengths
|
v_normals /= v_lengths
|
||||||
|
|
||||||
return v_normals.astype(np.float32), corrected_faces.astype(np.uint32)
|
return v_normals.astype(np.float32), faces.astype(np.uint32)
|
||||||
|
|
||||||
|
|
||||||
def occ_shape_to_stl(
|
def occ_shape_to_stl(
|
||||||
|
|||||||
Reference in New Issue
Block a user