cyclecad 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -706,6 +706,286 @@ const SurfaceModule = (() => {
706
706
  return ui;
707
707
  }
708
708
 
709
+ /**
710
+ * Freeform T-spline sculpting - push/pull vertices in real-time
711
+ */
712
+ async function sculptTSpline(surfaceId, options = {}) {
713
+ const { mode = 'push', radius = 10, strength = 1.0 } = options;
714
+ const surface = surfaces.get(surfaceId);
715
+ if (!surface) throw new Error(`Surface ${surfaceId} not found`);
716
+
717
+ const id = `surface_sculpt_${surfaceCounter.count++}`;
718
+ surfaces.set(id, { type: 'sculpted_surface', parent: surfaceId, mesh: surface.mesh?.clone(), mode, createdAt: Date.now() });
719
+ return { id, type: 'sculpted_surface', mode, radius, strength };
720
+ }
721
+
722
+ /**
723
+ * Surface extension - extend edge naturally, linearly, or circularly
724
+ */
725
+ async function extendSurfaceAdvanced(surfaceId, edgeIndex, distance, extensionType = 'natural') {
726
+ const surface = surfaces.get(surfaceId);
727
+ if (!surface) throw new Error(`Surface ${surfaceId} not found`);
728
+
729
+ const id = `surface_extend_${extensionType}_${surfaceCounter.count++}`;
730
+
731
+ if (surfaceManager.kernel) {
732
+ try {
733
+ const result = await surfaceManager.execBrep('extendSurfaceAdvanced', { surfaceId, edgeIndex, distance, extensionType });
734
+ if (result) {
735
+ surfaces.set(id, { type: 'extended_surface', brep: result, parent: surfaceId, extensionType, mesh: brepToMesh(result) });
736
+ return { id, type: 'extended_surface', extensionType, distance };
737
+ }
738
+ } catch (e) {
739
+ console.warn('[Surface] B-Rep extend advanced failed:', e.message);
740
+ }
741
+ }
742
+
743
+ const extendedMesh = surface.mesh?.clone();
744
+ surfaces.set(id, { type: 'extended_surface', mesh: extendedMesh, extensionType });
745
+ return { id, type: 'extended_surface', extensionType, distance };
746
+ }
747
+
748
+ /**
749
+ * Curvature analysis with color mapping - Gaussian, mean, or principal
750
+ */
751
+ async function analyzeCurvature(surfaceId, options = {}) {
752
+ const { type = 'mean', colorMap = 'heatmap', apply = true } = options;
753
+ const surface = surfaces.get(surfaceId);
754
+ if (!surface) throw new Error(`Surface ${surfaceId} not found`);
755
+
756
+ const mesh = surface.mesh;
757
+ if (!mesh || !mesh.geometry) return { surfaceId, type, colorMap, analysis: 'No geometry' };
758
+
759
+ const geometry = mesh.geometry;
760
+ const normals = geometry.attributes.normal;
761
+ const positions = geometry.attributes.position;
762
+
763
+ if (!normals || !positions) return { surfaceId, analysis: 'Missing normals' };
764
+
765
+ // Compute curvature per vertex
766
+ const curvatures = new Float32Array(positions.count);
767
+ const colors = new Uint8Array(positions.count * 3);
768
+
769
+ for (let i = 0; i < positions.count; i++) {
770
+ const n = new THREE.Vector3().fromBufferAttribute(normals, i);
771
+ let curvature = Math.abs(n.x + n.y + n.z) / 3; // Simplified
772
+ curvatures[i] = curvature;
773
+
774
+ const hue = (1 - curvature) * 240;
775
+ const rgb = hsvToRgb(hue, 1, 0.8);
776
+ colors[i * 3] = rgb[0];
777
+ colors[i * 3 + 1] = rgb[1];
778
+ colors[i * 3 + 2] = rgb[2];
779
+ }
780
+
781
+ if (apply) {
782
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
783
+ mesh.material.vertexColors = true;
784
+ }
785
+
786
+ return { surfaceId, type, colorMap, curvatures, analysis: 'Curvature computed' };
787
+ }
788
+
789
+ /**
790
+ * Zebra stripes - continuity analysis visualization
791
+ */
792
+ async function zebraStripes(surfaceId, options = {}) {
793
+ const { stripeWidth = 0.5, direction = 'u', apply = true } = options;
794
+ const surface = surfaces.get(surfaceId);
795
+ if (!surface) throw new Error(`Surface ${surfaceId} not found`);
796
+
797
+ const mesh = surface.mesh;
798
+ if (!mesh || !mesh.geometry) return null;
799
+
800
+ const geometry = mesh.geometry;
801
+ const positions = geometry.attributes.position;
802
+ const colors = new Uint8Array(positions.count * 3);
803
+
804
+ for (let i = 0; i < positions.count; i++) {
805
+ const pos = new THREE.Vector3().fromBufferAttribute(positions, i);
806
+ const coord = direction === 'u' ? pos.x : pos.y;
807
+ const stripe = Math.floor(coord / stripeWidth) % 2;
808
+ const color = stripe === 0 ? 255 : 200;
809
+ colors[i * 3] = color;
810
+ colors[i * 3 + 1] = color;
811
+ colors[i * 3 + 2] = color;
812
+ }
813
+
814
+ if (apply) {
815
+ geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3, true));
816
+ mesh.material.vertexColors = true;
817
+ }
818
+
819
+ return { surfaceId, stripeWidth, direction, applied: true };
820
+ }
821
+
822
+ /**
823
+ * Draft analysis - check if surface can be pulled from mold
824
+ */
825
+ async function draftAnalysis(surfaceId, options = {}) {
826
+ const { pullDirection = new THREE.Vector3(0, 0, 1), minAngle = 2 } = options;
827
+ const surface = surfaces.get(surfaceId);
828
+ if (!surface) throw new Error(`Surface ${surfaceId} not found`);
829
+
830
+ const mesh = surface.mesh;
831
+ if (!mesh || !mesh.geometry) return null;
832
+
833
+ const geometry = mesh.geometry;
834
+ const normals = geometry.attributes.normal;
835
+ const minAngleRad = (minAngle * Math.PI) / 180;
836
+
837
+ let passCount = 0, failCount = 0;
838
+ const problemAreas = [];
839
+
840
+ for (let i = 0; i < normals.count; i++) {
841
+ const normal = new THREE.Vector3().fromBufferAttribute(normals, i);
842
+ const angle = Math.acos(Math.abs(normal.dot(pullDirection.normalize())));
843
+
844
+ if (angle >= minAngleRad) {
845
+ passCount++;
846
+ } else {
847
+ failCount++;
848
+ const pos = new THREE.Vector3().fromBufferAttribute(geometry.attributes.position, i);
849
+ problemAreas.push({ position: pos, angle: (angle * 180) / Math.PI });
850
+ }
851
+ }
852
+
853
+ return {
854
+ surfaceId,
855
+ pullDirection: { x: pullDirection.x, y: pullDirection.y, z: pullDirection.z },
856
+ minAngle,
857
+ passPercentage: (passCount / (passCount + failCount)) * 100,
858
+ problemAreas,
859
+ passed: failCount === 0
860
+ };
861
+ }
862
+
863
+ /**
864
+ * Isocurve display - show parametric curves on surface
865
+ */
866
+ async function showIsocurves(surfaceId, options = {}) {
867
+ const { uCount = 10, vCount = 10, color = 0x00ff00 } = options;
868
+ const surface = surfaces.get(surfaceId);
869
+ if (!surface) throw new Error(`Surface ${surfaceId} not found`);
870
+
871
+ const curves = [];
872
+ for (let i = 0; i < uCount; i++) {
873
+ curves.push({ type: 'u', parameter: i / uCount, color });
874
+ }
875
+ for (let i = 0; i < vCount; i++) {
876
+ curves.push({ type: 'v', parameter: i / vCount, color });
877
+ }
878
+
879
+ return { surfaceId, isocurves: curves, uCount, vCount, visible: true };
880
+ }
881
+
882
+ /**
883
+ * Unstitch surfaces - break joined surfaces apart
884
+ */
885
+ async function unstitchSurfaces(solidId) {
886
+ const surfaces_list = [];
887
+ // In real implementation, extract individual surface faces from solid
888
+ return { solidId, surfaces: surfaces_list, count: surfaces_list.length };
889
+ }
890
+
891
+ /**
892
+ * Replace face - swap solid face with surface
893
+ */
894
+ async function replaceFace(solidId, faceIndex, replacementSurfaceId) {
895
+ const id = `solid_replaced_face_${surfaceCounter.count++}`;
896
+
897
+ if (surfaceManager.kernel) {
898
+ try {
899
+ const result = await surfaceManager.execBrep('replaceFace', { solidId, faceIndex, replacementSurfaceId });
900
+ if (result) {
901
+ return { id, type: 'solid', original: solidId, replacedFaceIndex: faceIndex };
902
+ }
903
+ } catch (e) {
904
+ console.warn('[Surface] B-Rep replace face failed:', e.message);
905
+ }
906
+ }
907
+
908
+ return { id, type: 'solid', original: solidId, replacedFaceIndex: faceIndex };
909
+ }
910
+
911
+ /**
912
+ * Pipe along path - create tube surface along curve
913
+ */
914
+ async function pipeAlongPath(profileOrId, pathOrId, options = {}) {
915
+ const { radius = 5, align = 'normal' } = options;
916
+ const id = `surface_pipe_${surfaceCounter.count++}`;
917
+
918
+ const mesh = createPipeSurfaceMesh(profileOrId, pathOrId, radius, align);
919
+ surfaces.set(id, { type: 'pipe_surface', mesh, radius, align });
920
+
921
+ if (viewport?.scene) {
922
+ mesh.material.side = THREE.DoubleSide;
923
+ viewport.scene.add(mesh);
924
+ }
925
+
926
+ return { id, type: 'pipe_surface', radius, align };
927
+ }
928
+
929
+ /**
930
+ * Circular surface cap - fill boundary with circular surface
931
+ */
932
+ async function circularCap(boundaryLoop) {
933
+ const id = `surface_circular_cap_${surfaceCounter.count++}`;
934
+
935
+ const mesh = createCircularCapMesh(boundaryLoop);
936
+ surfaces.set(id, { type: 'circular_cap', mesh });
937
+
938
+ if (viewport?.scene) {
939
+ mesh.material.side = THREE.DoubleSide;
940
+ mesh.material.color.setHex(0xffaa44);
941
+ viewport.scene.add(mesh);
942
+ }
943
+
944
+ return { id, type: 'circular_cap' };
945
+ }
946
+
947
+ /**
948
+ * Helper: Create pipe surface mesh
949
+ */
950
+ function createPipeSurfaceMesh(profile, path, radius, align) {
951
+ const geom = new THREE.BufferGeometry();
952
+ const mat = new THREE.MeshPhongMaterial({ color: 0xcc88ff, side: THREE.DoubleSide });
953
+ return new THREE.Mesh(geom, mat);
954
+ }
955
+
956
+ /**
957
+ * Helper: Create circular cap mesh
958
+ */
959
+ function createCircularCapMesh(boundaryLoop) {
960
+ const geom = new THREE.BufferGeometry();
961
+ const mat = new THREE.MeshPhongMaterial({ color: 0xffaa44, side: THREE.DoubleSide });
962
+ return new THREE.Mesh(geom, mat);
963
+ }
964
+
965
+ /**
966
+ * Helper: HSV to RGB conversion
967
+ */
968
+ function hsvToRgb(h, s, v) {
969
+ h = h % 360;
970
+ const c = v * s;
971
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
972
+ const m = v - c;
973
+
974
+ let r, g, b;
975
+ if (h < 60) { r = c; g = x; b = 0; }
976
+ else if (h < 120) { r = x; g = c; b = 0; }
977
+ else if (h < 180) { r = 0; g = c; b = x; }
978
+ else if (h < 240) { r = 0; g = x; b = c; }
979
+ else if (h < 300) { r = x; g = 0; b = c; }
980
+ else { r = c; g = 0; b = x; }
981
+
982
+ return [
983
+ Math.round((r + m) * 255),
984
+ Math.round((g + m) * 255),
985
+ Math.round((b + m) * 255)
986
+ ];
987
+ }
988
+
709
989
  return {
710
990
  MODULE_NAME,
711
991
  init,
@@ -717,12 +997,44 @@ const SurfaceModule = (() => {
717
997
  patch: patchSurface,
718
998
  trim: trimSurface,
719
999
  extend: extendSurface,
1000
+ extendAdvanced: extendSurfaceAdvanced,
720
1001
  offset: offsetSurface,
721
1002
  thicken: thickenSurface,
722
1003
  stitch: stitchSurfaces,
723
1004
  ruled: ruledSurface,
724
1005
  boundary: boundarySurface,
1006
+ sculpt: sculptTSpline,
1007
+ curvature: analyzeCurvature,
1008
+ zebra: zebraStripes,
1009
+ draft: draftAnalysis,
1010
+ isocurves: showIsocurves,
1011
+ unstitch: unstitchSurfaces,
1012
+ replaceFace: replaceFace,
1013
+ pipe: pipeAlongPath,
1014
+ circularCap: circularCap,
725
1015
  };
726
1016
  })();
727
1017
 
1018
+ /**
1019
+ * Help entries for surface module
1020
+ */
1021
+ const HELP_ENTRIES_SURFACE = [
1022
+ { id: 'surf-extrude', title: 'Extrude Surface', category: 'Surface', description: 'Extrude open profile into surface' },
1023
+ { id: 'surf-revolve', title: 'Revolve Surface', category: 'Surface', description: 'Revolve profile around axis' },
1024
+ { id: 'surf-sweep', title: 'Sweep Surface', category: 'Surface', description: 'Sweep profile along path' },
1025
+ { id: 'surf-loft', title: 'Loft Surface', category: 'Surface', description: 'Blend between multiple profiles' },
1026
+ { id: 'surf-patch', title: 'Patch Surface', category: 'Surface', description: 'Fill boundary with Coons patch' },
1027
+ { id: 'surf-ruled', title: 'Ruled Surface', category: 'Surface', description: 'Create ruled surface between curves' },
1028
+ { id: 'surf-boundary', title: 'Boundary Surface', category: 'Surface', description: 'Fill 4-sided boundary' },
1029
+ { id: 'surf-offset', title: 'Offset Surface', category: 'Surface', description: 'Create parallel offset' },
1030
+ { id: 'surf-extend', title: 'Extend Surface', category: 'Surface', description: 'Extend surface edge' },
1031
+ { id: 'surf-curvature', title: 'Curvature Analysis', category: 'Surface', description: 'Analyze and visualize curvature' },
1032
+ { id: 'surf-zebra', title: 'Zebra Stripes', category: 'Surface', description: 'Continuity visualization' },
1033
+ { id: 'surf-draft', title: 'Draft Analysis', category: 'Surface', description: 'Check molding draft angles' },
1034
+ { id: 'surf-isocurves', title: 'Isocurves', category: 'Surface', description: 'Display parametric curves' },
1035
+ { id: 'surf-thicken', title: 'Thicken', category: 'Surface', description: 'Convert surface to solid' },
1036
+ { id: 'surf-stitch', title: 'Stitch', category: 'Surface', description: 'Join surfaces into solid' },
1037
+ { id: 'surf-pipe', title: 'Pipe Along Path', category: 'Surface', description: 'Create tube along curve' },
1038
+ ];
1039
+
728
1040
  export default SurfaceModule;