diff --git a/__init__.py b/__init__.py index c8db776724..9b9a704ac1 100755 --- a/__init__.py +++ b/__init__.py @@ -43,7 +43,7 @@ bl_info = { "name": "Sverchok", "author": "sverchok-b3d@ya.ru various authors see https://github.com/nortikin/sverchok/graphs/contributors", - "version": (1, 1, 0), + "version": (1, 2, 0), "blender": (2, 93, 0), "location": "Node Editor", "category": "Node", @@ -53,7 +53,7 @@ "tracker_url": "http://www.blenderartists.org/forum/showthread.php?272679" } -VERSION = 'v1.1.0' # looks like the only way to have custom format for the version +VERSION = 'v1.2.0-alpha' # looks like the only way to have custom format for the version import sys import importlib @@ -93,10 +93,8 @@ def register(): sv_registration_utils.register_all(imported_modules + node_list) sverchok.core.init_bookkeeping(__name__) - menu.register() if reload_event: data_structure.RELOAD_EVENT = True - menu.reload_menu() def unregister(): diff --git a/core/__init__.py b/core/__init__.py index 83103c6be8..d40560200b 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -5,7 +5,7 @@ reload_event = False root_modules = [ - "menu", "node_tree", "data_structure", "core", + "node_tree", "data_structure", "core", "utils", "ui", "nodes", "old_nodes" ] @@ -22,10 +22,10 @@ def sv_register_modules(modules): for m in modules: - if m.__name__ != "sverchok.menu": - if hasattr(m, "register"): - # print("Registering module: {}".format(m.__name__)) - m.register() + if hasattr(m, "register"): + # print("Registering module: {}".format(m.__name__)) + m.register() + def sv_unregister_modules(modules): clear_all_socket_cache() diff --git a/dependencies.py b/dependencies.py index f610e16f4b..ade91cc5b9 100644 --- a/dependencies.py +++ b/dependencies.py @@ -119,8 +119,6 @@ def draw_message(box, package, dependencies=None): scipy_d.message = "SciPy is available" scipy_d.module = scipy except ImportError: - scipy_d.message = "sv: SciPy package is not available. Voronoi nodes and RBF-based nodes will not be available." - info(scipy_d.message) scipy = None geomdl_d = sv_dependencies["geomdl"] = SvDependency("geomdl", "https://github.com/orbingol/NURBS-Python/tree/master/geomdl") @@ -130,8 +128,6 @@ def draw_message(box, package, dependencies=None): geomdl_d.message = "geomdl package is available" geomdl_d.module = geomdl except ImportError: - geomdl_d.message = "sv: geomdl package is not available, some NURBS related nodes will not be available" - info(geomdl_d.message) geomdl = None skimage_d = sv_dependencies["skimage"] = SvDependency("scikit-image", "https://scikit-image.org/") @@ -141,8 +137,6 @@ def draw_message(box, package, dependencies=None): skimage_d.message = "SciKit-Image package is available" skimage_d.module = skimage except ImportError: - skimage_d.message = "sv: SciKit-Image package is not available; SciKit-based implementation of Marching Cubes and Marching Squares will not be available" - info(skimage_d.message) skimage = None mcubes_d = sv_dependencies["mcubes"] = SvDependency("mcubes", "https://github.com/pmneila/PyMCubes") @@ -151,8 +145,6 @@ def draw_message(box, package, dependencies=None): mcubes_d.message = "PyMCubes package is available" mcubes_d.module = mcubes except ImportError: - mcubes_d.message = "sv: PyMCubes package is not available. PyMCubes-based implementation of Marching Cubes will not be available" - info(mcubes_d.message) mcubes = None circlify_d = sv_dependencies["circlify"] = SvDependency("circlify", "https://github.com/elmotec/circlify") @@ -162,8 +154,6 @@ def draw_message(box, package, dependencies=None): circlify_d.message = "Circlify package is available" circlify_d.module = circlify except ImportError: - circlify_d.message = "sv: Circlify package is not available. Circlify node will not be available" - info(circlify_d.message) circlify = None freecad_d = sv_dependencies["freecad"] = SvDependency("FreeCAD", "https://www.freecadweb.org/") @@ -172,8 +162,6 @@ def draw_message(box, package, dependencies=None): freecad_d.message = "FreeCAD package is available" freecad_d.module = FreeCAD except ImportError: - freecad_d.message = "sv: FreeCAD package is not available, Solids nodes will not be available" - info(freecad_d.message) FreeCAD = None cython_d = sv_dependencies["cython"] = SvDependency("Cython", "https://cython.org/") @@ -183,8 +171,6 @@ def draw_message(box, package, dependencies=None): cython_d.message = "Cython package is available" cython_d.module = Cython except ImportError: - cython_d.message = "sv: Cython package is not available, Enhanched KDTree search will not be available" - info(cython_d.message) Cython = None numba_d = sv_dependencies["numba"] = SvDependency("Numba", "https://numba.pydata.org/") @@ -194,8 +180,6 @@ def draw_message(box, package, dependencies=None): numba_d.message = "Numba package is available" numba_d.module = numba except ImportError: - numba_d.message = "sv: Numba package is not available, njit compiled functions will not be available" - info(numba_d.message) numba = None pyOpenSubdiv_d = sv_dependencies["pyOpenSubdiv"] = SvDependency("pyOpenSubdiv","https://github.com/GeneralPancakeMSTR/pyOpenSubdivision") @@ -205,8 +189,6 @@ def draw_message(box, package, dependencies=None): pyOpenSubdiv_d.message = "pyOpenSubdiv package is available" pyOpenSubdiv_d.module = pyOpenSubdiv except ImportError: - pyOpenSubdiv_d.message = "sv: pyOpenSubdiv package is not available, the Catmull-Clark Subdivision node will not be available" - info(pyOpenSubdiv_d.message) pyOpenSubdiv = None good_names = [d.package for d in sv_dependencies.values() if d.module is not None and d.package is not None] diff --git a/index.md b/index.md deleted file mode 100644 index ad7bb810ad..0000000000 --- a/index.md +++ /dev/null @@ -1,792 +0,0 @@ -> ### This file is parsed by menu.py -> -> The following rules apply to editing this file: -> -> - do not use tabs, anywhere -> - indent the Node's line using 4 spaces -> - if you aren't sure, follow the existing convention -> -> Failing to follow these points will break the node category parser. - -## Generator - SvLineNodeMK4 - SvSegmentGenerator - SvPlaneNodeMk3 - SvNGonNode - SvBoxNodeMk2 - SvCircleNode - SvCylinderNodeMK2 - SphereNode - SvIcosphereNode - SvTorusNodeMK2 - SvSuzanneNode - SvCricketNode - --- - BasicSplineNode - SvQuadraticSplineNode - svBasicArcNode - RandomVectorNodeMK3 - ImageNode - -## Generators Extended - SvBoxRoundedNode - SvBricksNode - SvPolygonGridNode - HilbertNode - Hilbert3dNode - HilbertImageNode - SvImageComponentsNode - SvWFCTextureNode - SvTorusKnotNodeMK2 - SvRingNodeMK2 - SvEllipseNodeMK3 - SvSuperEllipsoidNode - SvRegularSolid - SvConicSectionNode - SvTriangleNode - SvPentagonTilerNode - SvSpiralNodeMK2 - -## Curves @ Primitives - SvExLineCurveNode - SvCircleCurveMk2Node - SvEllipseCurveNode - SvRoundedRectangleNode - SvArc3ptCurveNode - SvArcSedCurveNode - SvExCatenaryCurveNode - SvFreeCadHelixNode - --- - SvExPolylineNode - SvExFilletPolylineNode - SvKinkyCurveNode - SvBiArcNode - SvPolyArcNode - -## Curves @ NURBS - SvExNurbsCurveNode - SvApproxNurbsCurveMk2Node - SvExInterpolateNurbsCurveNode - SvDeconstructCurveNode - SvNurbsCurveNodesNode - --- - SvNurbsCurveMovePointNode - --- - SvCurveInsertKnotNode - SvCurveRemoveKnotNode - SvRefineNurbsCurveNode - SvCurveRemoveExcessiveKnotsNode - --- - SvCurveElevateDegreeNode - SvCurveReduceDegreeNode - --- - SvAdaptivePlotNurbsCurveNode - -## Curves @ Bezier - SvBezierSplineNode - SvExBezierCurveFitNode - -## Curves - @ Primitives - SvExCurveFormulaNode - SvExCubicSplineNode - SvTangentsCurveNode - SvExRbfCurveNode - SvExCirclifyNode - @ Bezier - @ NURBS - --- - SvExMarchingSquaresNode - SvExMSquaresOnSurfaceNode - --- - SvExApplyFieldToCurveNode - SvExCastCurveNode - SvProjectCurveSurfaceNode - SvOffsetCurveMk2Node - SvCurveOffsetOnSurfaceNode - SvExIsoUvCurveNode - SvExCurveOnSurfaceNode - --- - SvExCurveLerpCurveNode - SvSortCurvesNode - SvExConcatCurvesNode - SvExBlendCurvesMk2Node - SvExFlipCurveNode - SvReparametrizeCurveNode - SvExSurfaceBoundaryNode - --- - SvIntersectNurbsCurvesNode - SvExNearestPointOnCurveNode - SvExOrthoProjectCurveNode - SvExCurveEndpointsNode - SvExCurveSegmentNode - SvExCurveRangeNode - SvExtendCurveNode - SvSplitCurveNode - SvExCurveLengthNode - SvExCurveFrameNode - SvCurveFrameOnSurfNode - SvExCurveCurvatureNode - SvExCurveTorsionNode - SvExCurveExtremesNode - SvExCurveZeroTwistFrameNode - SvExSlerpCurveFrameNode - SvExCurveLengthParameterNode - SvLengthRebuildCurveNode - SvExCrossCurvePlaneNode - SvExCrossCurveSurfaceNode - --- - SvAdaptivePlotCurveNode - SvExEvalCurveNode - -## Surfaces @ NURBS - SvExNurbsSurfaceNode - SvExApproxNurbsSurfaceNode - SvExInterpolateNurbsSurfaceNode - SvNurbsLoftNode - SvNurbsSweepNode - SvNurbsBirailNode - SvGordonSurfaceNode - SvDeconstructSurfaceNode - --- - SvExQuadsToNurbsNode - --- - SvSurfaceInsertKnotNode - SvSurfaceRemoveKnotNode - SvSurfaceRemoveExcessiveKnotsNode - --- - SvSurfaceElevateDegreeNode - SvSurfaceReduceDegreeNode - -## Surfaces - SvExPlaneSurfaceNode - SvExSphereNode - SvExSurfaceFormulaNode - SvInterpolatingSurfaceNode - SvExMinimalSurfaceNode - SvExMinSurfaceFromCurveNode - @ NURBS - --- - SvExRevolutionSurfaceNode - SvExTaperSweepSurfaceNode - SvBendCurveSurfaceNode - SvExExtrudeCurveVectorNode - SvExExtrudeCurveCurveSurfaceNode - SvExExtrudeCurvePointNode - SvPipeSurfaceNode - SvExCurveLerpNode - SvExSurfaceLerpNode - SvCoonsPatchNode - SvBlendSurfaceNode - SvExApplyFieldToSurfaceNode - --- - SvExSurfaceDomainNode - SvExSurfaceSubdomainNode - SvFlipSurfaceNode - SvSwapSurfaceNode - SvReparametrizeSurfaceNode - SvSurfaceNormalsNode - SvSurfaceGaussCurvatureNode - SvSurfaceCurvaturesNode - SvExSurfaceExtremesNode - SvExNearestPointOnSurfaceNode - SvExOrthoProjectSurfaceNode - SvExRaycastSurfaceNode - --- - SvExImplSurfaceRaycastNode - SvExMarchingCubesNode - --- - SvExTessellateTrimSurfaceNode - SvAdaptiveTessellateNode - SvExEvalSurfaceNode - -## Fields - SvCoordScalarFieldNode - SvExScalarFieldFormulaNode - SvExVectorFieldFormulaNode - SvExComposeVectorFieldNode - SvExDecomposeVectorFieldNode - SvExScalarFieldPointNode - SvAttractorFieldNodeMk2 - SvRotationFieldNode - SvExImageFieldNode - SvMeshSurfaceFieldNode - SvExMeshNormalFieldNode - SvExVoronoiFieldNode - SvExMinimalScalarFieldNode - SvExMinimalVectorFieldNode - SvExNoiseVectorFieldNode - --- - SvExScalarFieldMathNode - SvExVectorFieldMathNode - SvScalarFieldCurveMapNode - SvExFieldDiffOpsNode - SvScalarFieldCurvatureNode - SvExMergeScalarFieldsNode - --- - SvExBendAlongCurveFieldNode - SvExBendAlongSurfaceFieldNode - --- - SvExScalarFieldEvaluateNode - SvExVectorFieldEvaluateNode - SvExVectorFieldApplyNode - --- - SvExVectorFieldGraphNode - SvExVectorFieldLinesNode - SvExScalarFieldGraphNode - -## Solids @ Make Face - SvSolidPolygonFaceNode - SvSolidWireFaceNode - SvProjectTrimFaceNode - -## Solids @ Analyze - SvSolidValidateNode - SvRefineSolidNode - SvIsSolidClosedNode - SvSolidCenterOfMassNode - SvSolidFaceAreaNode - SvSolidAreaNode - SvSolidVolumeNode - SvSolidBoundBoxNode - -## Solids - SvBoxSolidNode - SvCylinderSolidNode - SvConeSolidNode - SvSphereSolidNode - SvToursSolidNode - @ Make Face - SvSolidFaceExtrudeNode - SvSolidFaceSolidifyNode - SvSolidFaceRevolveNode - SvSweepSolidFaceNode - SvRuledSolidNode - SvSolidFromFacesNode - --- - SvTransformSolidNode - SvChamferSolidNode - SvFilletSolidNode - SvSolidBooleanNode - SvSolidGeneralFuseNode - SvMirrorSolidNode - SvOffsetSolidNode - SvSplitSolidNode - SvHollowSolidNode - --- - SvIsInsideSolidNode - SvSolidDistanceNode - SvSliceSolidNode - SvMeshToSolidNode - SvSolidToMeshNodeMk2 - SvSolidVerticesNode - SvSolidEdgesNode - SvSolidFacesNode - SvSelectSolidNode - SvCompoundSolidNode - @ Analyze - SvSolidViewerNode - -## Analyzers - SvBBoxNodeMk3 - SvComponentAnalyzerNode - SvDiameterNode - SvVolumeNodeMK2 - SvAreaNode - DistancePPNode - SvDistancePointLineNode - SvDistancePointPlaneNode - SvDistancetLineLineNode - SvPathLengthMk2Node - SvOrigins - SvGetNormalsNodeMk2 - SvIntersectLineSphereNode - SvIntersectCircleCircleNode - SvIntersectPlanePlaneNode - SvKDTreeNodeMK2 - SvKDTreeEdgesNodeMK3 - SvKDTreePathNode - SvNearestPointOnMeshNode - SvBvhOverlapNodeNew - SvMeshFilterNode - SvEdgeAnglesNode - SvPointInside - SvProportionalEditNode - SvWavePainterNode - SvRaycasterLiteNode - SvOBJInsolationNode - SvDeformationNode - SvLinkedVertsNode - SvProjectPointToLine - --- - SvLinearApproxNode - SvCircleApproxNode - SvSphereApproxNode - SvInscribedCircleNode - SvSteinerEllipseNode - --- - SvMeshSelectNodeMk2 - SvSelectSimilarNode - SvChessSelection - -## Spatial - SvHomogenousVectorField - SvRandomPointsOnMesh - SvPopulateSurfaceMk2Node - SvPopulateSolidMk2Node - SvFieldRandomProbeMk3Node - --- - DelaunayTriangulation2DNode - SvDelaunay2DCdt - SvDelaunay3dMk2Node - --- - Voronoi2DNode - SvExVoronoi3DNode - SvExVoronoiSphereNode - SvVoronoiOnSurfaceNode - SvVoronoiOnMeshNode - SvVoronoiOnSolidNode - --- - SvLloyd2dNode - SvLloyd3dNode - SvLloydOnSphereNode - SvLloydOnMeshNode - SvLloydSolidNode - SvLloydSolidFaceNode - --- - SvConvexHullNodeMK2 - SvConcaveHullNode - -## Transforms - SvMoveNodeMk3 - SvRotationNodeMk3 - SvScaleNodeMk3 - SvSymmetrizeNode - SvMirrorNodeMk2 - MatrixApplyNode - SvBarycentricTransformNode - SvAlignMeshByMesh - --- - SvTransformSelectNode - SvTransformMesh - SvSimpleDeformNode - SvBendAlongPathNode - SvBendAlongSurfaceNode - SvDisplaceNodeMk2 - SvNoiseDisplaceNode - SvRandomizeVerticesNode - SvCastNode - SvFormulaDeformMK2Node - -## Modifier Change - SvDeleteLooseNode - SvMergeByDistanceNode - SvMeshCleanNode - SvSeparateMeshNode - SvSeparatePartsToIndexes - SvEdgenetToPathsNode - SvLimitedDissolve - SvPlanarFacesNode - SvSplitFacesNode - SvMeshBeautify - SvTriangulateNode - SvMakeMonotone - --- - SvSplitMeshElements - PolygonBoomNode - SvEdgeBoomNode - SvDissolveMeshElements - SvPols2EdgsNodeMk2 - SvMeshJoinNodeMk2 - --- - SvFillsHoleNode - SvRecalcNormalsNode - SvFlipNormalsNode - --- - SvExtrudeEdgesNodeMk2 - SvExtrudeSeparateNode - SvExtrudeRegionNode - SvPokeFacesNode - SvVertMaskNode - SvSplitEdgesMk3Node - SvRigidOrigamiNode - --- - SvFollowActiveQuads - SvFlatGeometryNode - -## Modifier Make - LineConnectNodeMK2 - --- - SvOpenSubdivisionNode - SvSubdivideNodeMK2 - SvSubdivideToQuadsNode - SvOffsetLineNode - SvContourNode - --- - SvDualMeshNode - SvDiamondMeshNode - SvClipVertsNode - --- - SvBevelCurveNode - SvAdaptiveEdgeNode - SvAdaptivePolygonsNodeMk3 - SvDuplicateAlongEdgeNode - SvFractalCurveNode - SvFrameworkNode - SvSolidifyNodeMk2 - SvWireframeNode - SvPipeNode - SvMatrixTubeNode - -## List Masks - MaskListNode - SvMaskJoinNodeMK2 - SvMaskConvertNode - SvMaskToIndexNode - SvIndexToMaskNode - SvCalcMaskNode - -## List Mutators - SvListModifierNode - SvUniqueItemsNode - SvFixEmptyObjectsNode - SvDatetimeStrings - SvVDAttrsNodeMk2 - SvPolygonSortNode - SvFindClosestValue - SvMultiCacheNode - -## List Main - ListJoinNode - SvConstantListNode - ZipNode - ListLevelsNode - ListLengthNode - ListSumNodeMK2 - ListMatchNode - ListFuncNode - SvListDecomposeNode - SvListStatisticsNode - SvIndexListNode - -## List Struct - ShiftNodeMK2 - ListRepeaterNode - ListSliceNode - SvListSplitNode - ListFLNode - SvListItemNode - SvListItemInsertNode - ListReverseNode - ListShuffleNode - SvListSortNode - ListFlipNode - SvListLevelsNodeMK2 - -## Dictionary - SvDictionaryIn - SvDictionaryOut - -## CAD - SvBevelNode - SvIntersectEdgesNodeMK3 - SvOffsetNode - SvInsetSpecialMk2 - SvInsetFaces - SvLatheNode - SvSmoothNode - SvRelaxMeshNode - SvSmoothLines - --- - CrossSectionNode - SvBisectNode - SvCutObjBySurfaceNode - SvEdgesToFaces2D - SvMergeMesh2D - SvMergeMesh2DLite - SvCropMesh2D - SvWafelNode - -## Number - SvNumberNode - SvScalarMathNodeMK4 - SvGenNumberRange - SvListInputNode - SvRndNumGen - RandomNode - Float2IntNode - --- - SvMapRangeNode - SvEasingNode - SvCurveMapperNode - SvMixNumbersNode - SvMixInputsNode - --- - SvGenFibonacci - SvGenExponential - SvOscillatorNode - SvSmoothNumbersNode - -## Vector - GenVectorsNode - VectorsOutNode - SvAxisInputNodeMK2 - SvVectorMathNodeMK3 - VertsDelDoublesNode - SvVectorRewire - --- - SvVertSortNode - SvQuadGridSortVertsNode - VectorDropNode - VectorPolarInNode - VectorPolarOutNode - SvAttractorNode - --- - SvVectorLerp - SvInterpolationStripesNode - SvInterpolationNodeMK3 - SvInterpolationNodeMK2 - --- - SvNoiseNodeMK3 - SvTurbulenceNode - SvLacunarityNode - SvVectorFractal - -## Matrix - SvMatrixInNodeMK4 - SvMatrixOutNodeMK2 - SvMatrixApplyJoinNode - SvIterateNode - MatrixDeformNode - SvMatrixValueIn - SvMatrixEulerNode - MatrixShearNode - SvMatrixNormalNode - SvMatrixTrackToNode - SvMatrixMathNode - MatrixInterpolationNode - -## Quaternion - SvQuaternionInNodeMK2 - SvQuaternionOutNodeMK2 - SvQuaternionMathNode - SvRotationDifference - -## Color - SvColorInputNode - SvColorsInNodeMK1 - SvColorsOutNodeMK1 - SvColorMixNode - SvFormulaColorNode - SvColorRampNode - --- - SvTextureEvaluateNodeMk2 - -## Logic - SvLogicNodeMK2 - SvSwitchNodeMK2 - SvInputSwitchNodeMOD - SvNeuroElman1LNode - SvCustomSwitcher - SvRangeSwitchNode - --- - SvLoopInNode - SvLoopOutNode - --- - SvEvolverNode - SvGenesHolderNode - -## Viz - Sv3DviewPropsNode - --- - SvViewerDrawMk4 - SvMatrixViewer28 - SvIDXViewer28 - SvViewer2D - SvCurveViewerDrawNode - SvSurfaceViewerDrawNode - --- - SvMeshViewer - SvCurveViewerNodeV28 - SvPolylineViewerNode - SvTypeViewerNodeV28 - SvSkinViewerNodeV28 - SvMetaballOutNode - SvBezierCurveOutNode - SvNurbsCurveOutNode - SvNurbsSurfaceOutNode - --- - SvInstancerNodeMK3 - SvDupliInstancesMK5 - SvDupliInstancesLite - --- - SvLightViewerNode - --- - SvGreasePencilStrokes - SvEmptyOutNode - --- - SvTextureViewerNode - SvTextureViewerNodeLite - SvWaveformViewer - SvConsoleNode - -## Text - ViewerNodeTextMK3 - SvDataShapeNode - SvStethoscopeNodeMK2 - SvDebugPrintNode - --- - SvTextInNodeMK2 - SvTextOutNodeMK2 - --- - NoteNode - SvGTextNode - --- - SvStringsToolsNode - SvSimpleTextNode - -## BPY Data - SvObjRemoteNodeMK2 - SvNodeRemoteNodeMK2 - SvGetAssetPropertiesMK2 - SvSetDataObjectNodeMK2 - SvSortObjsNode - SvFilterObjsNode - SvSetMeshAttributeNode - SvNamedMeshAttributeNode - SvPointOnMeshNodeMK2 - SvOBJRayCastNodeMK2 - SvSCNRayCastNodeMK2 - SvSetLoopNormalsNode - SvSetCollection - SvCopyModifiersNode - -## Scene - SvGetObjectsData - SvObjInLite - SvCurveInputNode - SvFCurveInNodeMK1 - SvCollectionPicker - SvBezierInNode - SvExNurbsInNode - --- - SvSelectionGrabberLite - SvObjEdit - --- - SvFrameInfoNodeMK2 - SvTimerNode - -## Objects - SvVertexGroupNodeMK2 - SvVertexColorNodeMK3 - SvAssignMaterialListNode - SvMaterialIndexNode - SvSetCustomUVMap - -## Exchange - SvExNurbsToJsonNode - SvExJsonToNurbsNode - SvImportSolidNode - SvExportSolidNode - SvReceiveFromSorcarNode - SvExportGcodeNode - SvReadFCStdNode - SvReadFCStdModNode - SvWriteFCStdNode - SvReadFCStdSketchNode - SvFCStdSpreadsheetNode - SvApproxSubdtoNurbsNode - -## Script - SvFormulaNodeMk5 - SvFormulaInterpolateNode - SvExecNodeMod - SvProfileNodeMK3 - SvMeshEvalNode - SvGenerativeArtNode - SvTopologySimple - --- - SvScriptNodeLite - -## Network - UdpClientNode - SvFilePathNode - -## Layout - WifiInNode - WifiOutNode - NodeReroute - ConverterNode - -## Pulga Physics - SvPulgaPhysicsSolverNode - SvPulgaVectorForceNode - SvPulgaSpringsForceNode - SvPulgaDragForceNode - SvPulgaPinForceNode - SvPulgaTimedForceNode - SvPulgaCollisionForceNode - SvPulgaAttractionForceNode - SvPulgaAlignForceNode - SvPulgaFitForceNode - SvPulgaObstacleForceNode - SvPulgaRandomForceNode - SvPulgaBoundingBoxForceNode - SvPulgaInflateForceNode - SvPulgaAttractorsForceNodeMk2 - SvPulgaAngleForceNode - SvPulgaVortexForceNode - SvPulgaPhysicsNode - -## SVG - SvSvgDocumentNode - SvSvgCircleNode - SvSvgPathNodeMk2 - SvSvgMeshNode - SvSvgTextNode - SvSvgDimensionNode - SvSvgGroupNode - SvSvgFillStrokeNodeMk2 - SvSvgPatternNode - -## Beta Nodes - SvFormulaShapeNode - SvHeavyTriangulateNode - SvMeshUVColorNode - SvUVPointonMeshNode - SvSampleUVColorNode - SvSubdivideLiteNode - SvExtrudeSeparateLiteNode - SvUnsubdivideNode - SvLimitedDissolveMK2 - SvArmaturePropsNode - SvLatticePropsNode - --- - SvSculptMaskNode - SvSelectMeshVerts - SvSetCustomMeshNormals - --- - SvCombinatoricsNode - -## Alpha Nodes - SvBManalyzinNode - SvBMObjinputNode - SvBMoutputNode - SvBMtoElementNode - SvBMOpsNodeMK2 - --- - SvCSGBooleanNodeMK2 - SvNumpyArrayNode - SvSNFunctorB - SvParticlesMK2Node - SvJoinTrianglesNode - SvListSliceLiteNode - SvCacheNode - SvUVtextureNode - SvSeparateMeshNodeMK2 - SvMultiExtrudeAlt - SvPlanarEdgenetToPolygons - SvSweepModulator - --- - SvGetPropNodeMK2 - SvSetPropNodeMK2 diff --git a/index.yaml b/index.yaml new file mode 100644 index 0000000000..5af1c60ea6 --- /dev/null +++ b/index.yaml @@ -0,0 +1,859 @@ +# This file is parsed by sverchok.ui.nodeview_space_menu +# It's quite limited representation of yaml syntax, stick to existing lexical +# constructions + +# Shift+A / Add(Node) Menu + +- Search: # Label of an operator + - icon_name: OUTLINER_DATA_FONT # icon name to show + - operator: node.sv_extra_search # name of on an operator to call + # custom properties for operators are not supported currently + +- --- # Separator + +- Generator: # Name of a node category + - icon_name: OBJECT_DATAMODE # icon name to show + - extra_menu: MeshPartialMenu # to make the category available in another menu (1,2,3,4,5) + - SvLineNodeMK4 # bl_idname of a node + - SvSegmentGenerator + - SvPlaneNodeMk3 + - SvNGonNode + - SvBoxNodeMk2 + - SvCircleNode + - SvCylinderNodeMK2 + - SphereNode + - SvIcosphereNode + - SvTorusNodeMK2 + - SvSuzanneNode + - SvCricketNode + - --- + - BasicSplineNode + - SvQuadraticSplineNode + - svBasicArcNode + - RandomVectorNodeMK3 + - ImageNode + + # this is a nested category + - Generators Extended: + - icon_name: PLUGIN + - SvBoxRoundedNode + - SvBricksNode + - SvPolygonGridNode + - HilbertNode + - Hilbert3dNode + - HilbertImageNode + - SvImageComponentsNode + - SvWFCTextureNode + - SvTorusKnotNodeMK2 + - SvRingNodeMK2 + - SvEllipseNodeMK3 + - SvSuperEllipsoidNode + - SvRegularSolid + - SvConicSectionNode + - SvTriangleNode + - SvPentagonTilerNode + - SvSpiralNodeMK2 + + +- Curves: + - icon_name: OUTLINER_OB_CURVE + - extra_menu: AdvancedObjectsPartialMenu + - Curve Primitives: + - SvExLineCurveNode + - SvCircleCurveMk2Node + - SvEllipseCurveNode + - SvRoundedRectangleNode + - SvArc3ptCurveNode + - SvArcSedCurveNode + - SvExCatenaryCurveNode + - SvFreeCadHelixNode + - --- + - SvExPolylineNode + - SvExFilletPolylineNode + - SvKinkyCurveNode + - SvBiArcNode + - SvPolyArcNode + - SvExCurveFormulaNode + - SvExCubicSplineNode + - SvTangentsCurveNode + - SvExRbfCurveNode + - SvExCirclifyNode + - Curve Bezier: + - SvBezierSplineNode + - SvExBezierCurveFitNode + - Curve NURBS: + - SvExNurbsCurveNode + - SvApproxNurbsCurveMk2Node + - SvExInterpolateNurbsCurveNode + - SvDeconstructCurveNode + - SvNurbsCurveNodesNode + - --- + - SvNurbsCurveMovePointNode + - --- + - SvCurveInsertKnotNode + - SvCurveRemoveKnotNode + - SvRefineNurbsCurveNode + - SvCurveRemoveExcessiveKnotsNode + - --- + - SvCurveElevateDegreeNode + - SvCurveReduceDegreeNode + - --- + - SvAdaptivePlotNurbsCurveNode + - --- + - SvExMarchingSquaresNode + - SvExMSquaresOnSurfaceNode + - --- + - SvExApplyFieldToCurveNode + - SvExCastCurveNode + - SvProjectCurveSurfaceNode + - SvOffsetCurveMk2Node + - SvCurveOffsetOnSurfaceNode + - SvExIsoUvCurveNode + - SvExCurveOnSurfaceNode + - --- + - SvExCurveLerpCurveNode + - SvSortCurvesNode + - SvExConcatCurvesNode + - SvExBlendCurvesMk2Node + - SvExFlipCurveNode + - SvReparametrizeCurveNode + - SvExSurfaceBoundaryNode + - --- + - SvIntersectNurbsCurvesNode + - SvExNearestPointOnCurveNode + - SvExOrthoProjectCurveNode + - SvExCurveEndpointsNode + - SvExCurveSegmentNode + - SvExCurveRangeNode + - SvExtendCurveNode + - SvSplitCurveNode + - SvExCurveLengthNode + - SvExCurveFrameNode + - SvCurveFrameOnSurfNode + - SvExCurveCurvatureNode + - SvExCurveTorsionNode + - SvExCurveExtremesNode + - SvExCurveZeroTwistFrameNode + - SvExSlerpCurveFrameNode + - SvExCurveLengthParameterNode + - SvLengthRebuildCurveNode + - SvExCrossCurvePlaneNode + - SvExCrossCurveSurfaceNode + - --- + - SvAdaptivePlotCurveNode + - SvExEvalCurveNode + +- Surfaces: + - icon_name: SURFACE_DATA + - extra_menu: AdvancedObjectsPartialMenu + - SvExPlaneSurfaceNode + - SvExSphereNode + - SvExSurfaceFormulaNode + - SvInterpolatingSurfaceNode + - SvExMinimalSurfaceNode + - SvExMinSurfaceFromCurveNode + - Surface NURBS: + - SvExNurbsSurfaceNode + - SvExApproxNurbsSurfaceNode + - SvExInterpolateNurbsSurfaceNode + - SvNurbsLoftNode + - SvNurbsSweepNode + - SvNurbsBirailNode + - SvGordonSurfaceNode + - SvDeconstructSurfaceNode + - --- + - SvExQuadsToNurbsNode + - --- + - SvSurfaceInsertKnotNode + - SvSurfaceRemoveKnotNode + - SvSurfaceRemoveExcessiveKnotsNode + - --- + - SvSurfaceElevateDegreeNode + - SvSurfaceReduceDegreeNode + - --- + - SvExRevolutionSurfaceNode + - SvExTaperSweepSurfaceNode + - SvBendCurveSurfaceNode + - SvExExtrudeCurveVectorNode + - SvExExtrudeCurveCurveSurfaceNode + - SvExExtrudeCurvePointNode + - SvPipeSurfaceNode + - SvExCurveLerpNode + - SvExSurfaceLerpNode + - SvCoonsPatchNode + - SvBlendSurfaceNode + - SvExApplyFieldToSurfaceNode + - --- + - SvExSurfaceDomainNode + - SvExSurfaceSubdomainNode + - SvFlipSurfaceNode + - SvSwapSurfaceNode + - SvReparametrizeSurfaceNode + - SvSurfaceNormalsNode + - SvSurfaceGaussCurvatureNode + - SvSurfaceCurvaturesNode + - SvExSurfaceExtremesNode + - SvExNearestPointOnSurfaceNode + - SvExOrthoProjectSurfaceNode + - SvExRaycastSurfaceNode + - --- + - SvExImplSurfaceRaycastNode + - SvExMarchingCubesNode + - --- + - SvExTessellateTrimSurfaceNode + - SvAdaptiveTessellateNode + - SvExEvalSurfaceNode + +- Fields: + - icon_name: OUTLINER_OB_FORCE_FIELD + - extra_menu: AdvancedObjectsPartialMenu + - SvCoordScalarFieldNode + - SvExScalarFieldFormulaNode + - SvExVectorFieldFormulaNode + - SvExComposeVectorFieldNode + - SvExDecomposeVectorFieldNode + - SvExScalarFieldPointNode + - SvAttractorFieldNodeMk2 + - SvRotationFieldNode + - SvExImageFieldNode + - SvMeshSurfaceFieldNode + - SvExMeshNormalFieldNode + - SvExVoronoiFieldNode + - SvExMinimalScalarFieldNode + - SvExMinimalVectorFieldNode + - SvExNoiseVectorFieldNode + - --- + - SvExScalarFieldMathNode + - SvExVectorFieldMathNode + - SvScalarFieldCurveMapNode + - SvExFieldDiffOpsNode + - SvScalarFieldCurvatureNode + - SvExMergeScalarFieldsNode + - --- + - SvExBendAlongCurveFieldNode + - SvExBendAlongSurfaceFieldNode + - --- + - SvExScalarFieldEvaluateNode + - SvExVectorFieldEvaluateNode + - SvExVectorFieldApplyNode + - --- + - SvExVectorFieldGraphNode + - SvExVectorFieldLinesNode + - SvExScalarFieldGraphNode + +- Solids: + - icon_name: MESH_CUBE + - extra_menu: AdvancedObjectsPartialMenu + - SvBoxSolidNode + - SvCylinderSolidNode + - SvConeSolidNode + - SvSphereSolidNode + - SvToursSolidNode + - Solid Make Face: + - SvSolidPolygonFaceNode + - SvSolidWireFaceNode + - SvProjectTrimFaceNode + - SvSolidFaceExtrudeNode + - SvSolidFaceSolidifyNode + - SvSolidFaceRevolveNode + - SvSweepSolidFaceNode + - SvRuledSolidNode + - SvSolidFromFacesNode + - --- + - SvTransformSolidNode + - SvChamferSolidNode + - SvFilletSolidNode + - SvSolidBooleanNode + - SvSolidGeneralFuseNode + - SvMirrorSolidNode + - SvOffsetSolidNode + - SvSplitSolidNode + - SvHollowSolidNode + - --- + - SvIsInsideSolidNode + - SvSolidDistanceNode + - SvSliceSolidNode + - SvMeshToSolidNode + - SvSolidToMeshNodeMk2 + - SvSolidVerticesNode + - SvSolidEdgesNode + - SvSolidFacesNode + - SvSelectSolidNode + - SvCompoundSolidNode + - Solid Analyze: + - SvSolidValidateNode + - SvRefineSolidNode + - SvIsSolidClosedNode + - SvSolidCenterOfMassNode + - SvSolidFaceAreaNode + - SvSolidAreaNode + - SvSolidVolumeNode + - SvSolidBoundBoxNode + - SvSolidViewerNode + +- Spatial: + - icon_name: POINTCLOUD_DATA + - extra_menu: AdvancedObjectsPartialMenu + - SvHomogenousVectorField + - SvRandomPointsOnMesh + - SvPopulateSurfaceMk2Node + - SvPopulateSolidMk2Node + - SvFieldRandomProbeMk3Node + - --- + - DelaunayTriangulation2DNode + - SvDelaunay2DCdt + - SvDelaunay3dMk2Node + - --- + - Voronoi2DNode + - SvExVoronoi3DNode + - SvExVoronoiSphereNode + - SvVoronoiOnSurfaceNode + - SvVoronoiOnMeshNode + - SvVoronoiOnSolidNode + - --- + - SvLloyd2dNode + - SvLloyd3dNode + - SvLloydOnSphereNode + - SvLloydOnMeshNode + - SvLloydSolidNode + - SvLloydSolidFaceNode + - --- + - SvConvexHullNodeMK2 + - SvConcaveHullNode + +- Transforms: + - icon_name: ORIENTATION_LOCAL + - extra_menu: MeshPartialMenu + - SvMoveNodeMk3 + - SvRotationNodeMk3 + - SvScaleNodeMk3 + - SvSymmetrizeNode + - SvMirrorNodeMk2 + - MatrixApplyNode + - SvBarycentricTransformNode + - SvAlignMeshByMesh + - --- + - SvTransformSelectNode + - SvTransformMesh + - SvSimpleDeformNode + - SvBendAlongPathNode + - SvBendAlongSurfaceNode + - SvDisplaceNodeMk2 + - SvNoiseDisplaceNode + - SvRandomizeVerticesNode + - SvCastNode + - SvFormulaDeformMK2Node + +- Analyzers: + - icon_name: VIEWZOOM + - extra_menu: MeshPartialMenu + - SvBBoxNodeMk3 + - SvComponentAnalyzerNode + - SvDiameterNode + - SvVolumeNodeMK2 + - SvAreaNode + - DistancePPNode + - SvDistancePointLineNode + - SvDistancePointPlaneNode + - SvDistancetLineLineNode + - SvPathLengthMk2Node + - SvOrigins + - SvGetNormalsNodeMk2 + - SvIntersectLineSphereNode + - SvIntersectCircleCircleNode + - SvIntersectPlanePlaneNode + - SvKDTreeNodeMK2 + - SvKDTreeEdgesNodeMK3 + - SvKDTreePathNode + - SvNearestPointOnMeshNode + - SvBvhOverlapNodeNew + - SvMeshFilterNode + - SvEdgeAnglesNode + - SvPointInside + - SvProportionalEditNode + - SvWavePainterNode + - SvRaycasterLiteNode + - SvOBJInsolationNode + - SvDeformationNode + - SvLinkedVertsNode + - SvProjectPointToLine + - --- + - SvLinearApproxNode + - SvCircleApproxNode + - SvSphereApproxNode + - SvInscribedCircleNode + - SvSteinerEllipseNode + - --- + - SvMeshSelectNodeMk2 + - SvSelectSimilarNode + - SvChessSelection + +- Modifiers: + - icon_name: MODIFIER + - extra_menu: MeshPartialMenu + - Modifier Change: + - SvDeleteLooseNode + - SvMergeByDistanceNode + - SvMeshCleanNode + - SvSeparateMeshNode + - SvSeparatePartsToIndexes + - SvEdgenetToPathsNode + - SvLimitedDissolve + - SvPlanarFacesNode + - SvSplitFacesNode + - SvMeshBeautify + - SvTriangulateNode + - SvMakeMonotone + - --- + - SvSplitMeshElements + - PolygonBoomNode + - SvEdgeBoomNode + - SvDissolveMeshElements + - SvPols2EdgsNodeMk2 + - SvMeshJoinNodeMk2 + - --- + - SvFillsHoleNode + - SvRecalcNormalsNode + - SvFlipNormalsNode + - --- + - SvExtrudeEdgesNodeMk2 + - SvExtrudeSeparateNode + - SvExtrudeRegionNode + - SvPokeFacesNode + - SvVertMaskNode + - SvSplitEdgesMk3Node + - SvRigidOrigamiNode + - --- + - SvFollowActiveQuads + - SvFlatGeometryNode + + - Modifier Make: + - LineConnectNodeMK2 + - --- + - SvOpenSubdivisionNode + - SvSubdivideNodeMK2 + - SvSubdivideToQuadsNode + - SvOffsetLineNode + - SvContourNode + - --- + - SvDualMeshNode + - SvDiamondMeshNode + - SvClipVertsNode + - --- + - SvBevelCurveNode + - SvAdaptiveEdgeNode + - SvAdaptivePolygonsNodeMk3 + - SvDuplicateAlongEdgeNode + - SvFractalCurveNode + - SvFrameworkNode + - SvSolidifyNodeMk2 + - SvWireframeNode + - SvPipeNode + - SvMatrixTubeNode + +- CAD: + - icon_name: TOOL_SETTINGS + - extra_menu: MeshPartialMenu + - SvBevelNode + - SvIntersectEdgesNodeMK3 + - SvOffsetNode + - SvInsetSpecialMk2 + - SvInsetFaces + - SvLatheNode + - SvSmoothNode + - SvRelaxMeshNode + - SvSmoothLines + - --- + - CrossSectionNode + - SvBisectNode + - SvCutObjBySurfaceNode + - SvEdgesToFaces2D + - SvMergeMesh2D + - SvMergeMesh2DLite + - SvCropMesh2D + - SvWafelNode + +- --- + +- Number: + - icon_name: SV_NUMBER + - extra_menu: BasicDataPartialMenu + - SvNumberNode + - SvScalarMathNodeMK4 + - SvGenNumberRange + - SvListInputNode + - SvRndNumGen + - RandomNode + - Float2IntNode + - --- + - SvMapRangeNode + - SvEasingNode + - SvCurveMapperNode + - SvMixNumbersNode + - SvMixInputsNode + - --- + - SvGenFibonacci + - SvGenExponential + - SvOscillatorNode + - SvSmoothNumbersNode + + +- Vector: + - icon_name: SV_VECTOR + - extra_menu: BasicDataPartialMenu + - GenVectorsNode + - VectorsOutNode + - SvAxisInputNodeMK2 + - SvVectorMathNodeMK3 + - VertsDelDoublesNode + - SvVectorRewire + - --- + - SvVertSortNode + - SvQuadGridSortVertsNode + - VectorDropNode + - VectorPolarInNode + - VectorPolarOutNode + - SvAttractorNode + - --- + - SvVectorLerp + - SvInterpolationStripesNode + - SvInterpolationNodeMK3 + - SvInterpolationNodeMK2 + - --- + - SvNoiseNodeMK3 + - SvTurbulenceNode + - SvLacunarityNode + - SvVectorFractal + +- Matrix: + - icon_name: EMPTY_AXIS + - extra_menu: BasicDataPartialMenu + - SvMatrixInNodeMK4 + - SvMatrixOutNodeMK2 + - SvMatrixApplyJoinNode + - SvIterateNode + - MatrixDeformNode + - SvMatrixValueIn + - SvMatrixEulerNode + - MatrixShearNode + - SvMatrixNormalNode + - SvMatrixTrackToNode + - SvMatrixMathNode + - MatrixInterpolationNode + +- Quaternion: + - icon_name: SV_QUATERNION + - extra_menu: BasicDataPartialMenu + - SvQuaternionInNodeMK2 + - SvQuaternionOutNodeMK2 + - SvQuaternionMathNode + - SvRotationDifference + +- Color: + - icon_name: COLOR + - extra_menu: BasicDataPartialMenu + - SvColorInputNode + - SvColorsInNodeMK1 + - SvColorsOutNodeMK1 + - SvColorMixNode + - SvFormulaColorNode + - SvColorRampNode + - --- + - SvTextureEvaluateNodeMk2 + +- Logic: + - icon_name: SV_LOGIC + - extra_menu: BasicDataPartialMenu + - SvLogicNodeMK2 + - SvSwitchNodeMK2 + - SvInputSwitchNodeMOD + - SvNeuroElman1LNode + - SvCustomSwitcher + - SvRangeSwitchNode + - --- + - SvLoopInNode + - SvLoopOutNode + - --- + - SvEvolverNode + - SvGenesHolderNode + +- List: + - icon_name: NLA + - extra_menu: BasicDataPartialMenu + - List Main: + - ListJoinNode + - SvConstantListNode + - ZipNode + - ListLevelsNode + - ListLengthNode + - ListSumNodeMK2 + - ListMatchNode + - ListFuncNode + - SvListDecomposeNode + - SvListStatisticsNode + - SvIndexListNode + - List Struct: + - ShiftNodeMK2 + - ListRepeaterNode + - ListSliceNode + - SvListSplitNode + - ListFLNode + - SvListItemNode + - SvListItemInsertNode + - ListReverseNode + - ListShuffleNode + - SvListSortNode + - ListFlipNode + - SvListLevelsNodeMK2 + - MaskListNode + - SvMaskJoinNodeMK2 + - SvMaskConvertNode + - SvMaskToIndexNode + - SvIndexToMaskNode + - SvCalcMaskNode + - --- + - SvListModifierNode + - SvUniqueItemsNode + - SvFixEmptyObjectsNode + - SvDatetimeStrings + - SvVDAttrsNodeMk2 + - SvPolygonSortNode + - SvFindClosestValue + - SvMultiCacheNode + +- Dictionary: + - icon_name: OUTLINER_OB_FONT + - extra_menu: BasicDataPartialMenu + - SvDictionaryIn + - SvDictionaryOut + +- --- + +- Viz: + - icon_name: RESTRICT_VIEW_OFF + - extra_menu: ConnectionPartialMenu + - Sv3DviewPropsNode + - --- + - SvViewerDrawMk4 + - SvMatrixViewer28 + - SvIDXViewer28 + - SvViewer2D + - SvCurveViewerDrawNode + - SvSurfaceViewerDrawNode + - --- + - SvMeshViewer + - SvCurveViewerNodeV28 + - SvPolylineViewerNode + - SvTypeViewerNodeV28 + - SvSkinViewerNodeV28 + - SvMetaballOutNode + - SvBezierCurveOutNode + - SvNurbsCurveOutNode + - SvNurbsSurfaceOutNode + - --- + - SvInstancerNodeMK3 + - SvDupliInstancesMK5 + - SvDupliInstancesLite + - --- + - SvLightViewerNode + - --- + - SvGreasePencilStrokes + - SvEmptyOutNode + - --- + - SvTextureViewerNode + - SvTextureViewerNodeLite + - SvWaveformViewer + - SvConsoleNode + +- Text: + - icon_name: TEXT + - extra_menu: ConnectionPartialMenu + - ViewerNodeTextMK3 + - SvDataShapeNode + - SvStethoscopeNodeMK2 + - SvDebugPrintNode + - --- + - SvTextInNodeMK2 + - SvTextOutNodeMK2 + - --- + - NoteNode + - SvGTextNode + - --- + - SvStringsToolsNode + - SvSimpleTextNode + +- Scene: + - icon_name: SCENE_DATA + - extra_menu: ConnectionPartialMenu + - SvGetObjectsData + - SvObjInLite + - SvCurveInputNode + - SvFCurveInNodeMK1 + - SvCollectionPicker + - SvBezierInNode + - SvExNurbsInNode + - --- + - SvSelectionGrabberLite + - SvObjEdit + - --- + - SvFrameInfoNodeMK2 + - SvTimerNode + +- Exchange: + - icon_name: ARROW_LEFTRIGHT + - extra_menu: ConnectionPartialMenu + - SvExNurbsToJsonNode + - SvExJsonToNurbsNode + - SvImportSolidNode + - SvExportSolidNode + - SvReceiveFromSorcarNode + - SvExportGcodeNode + - SvReadFCStdNode + - SvReadFCStdModNode + - SvWriteFCStdNode + - SvReadFCStdSketchNode + - SvFCStdSpreadsheetNode + - SvApproxSubdtoNurbsNode + +- Layout: + - icon_name: NODETREE + - extra_menu: UiToolsPartialMenu + - WifiInNode + - WifiOutNode + - NodeReroute + - ConverterNode + +- BPY Data: + - icon_name: BLENDER + - extra_menu: ConnectionPartialMenu + - SvObjRemoteNodeMK2 + - SvNodeRemoteNodeMK2 + - SvGetAssetPropertiesMK2 + - SvSetDataObjectNodeMK2 + - SvSortObjsNode + - SvFilterObjsNode + - SvSetMeshAttributeNode + - SvNamedMeshAttributeNode + - SvPointOnMeshNodeMK2 + - SvOBJRayCastNodeMK2 + - SvSCNRayCastNodeMK2 + - SvSetLoopNormalsNode + - SvSetCollection + - SvCopyModifiersNode + - SvVertexGroupNodeMK2 + - SvVertexColorNodeMK3 + - SvAssignMaterialListNode + - SvMaterialIndexNode + - SvSetCustomUVMap + +- --- + +- Script: + - icon_name: WORDWRAP_ON + - extra_menu: AdvancedObjectsPartialMenu + - SvFormulaNodeMk5 + - SvFormulaInterpolateNode + - SvExecNodeMod + - SvProfileNodeMK3 + - SvMeshEvalNode + - SvGenerativeArtNode + - SvTopologySimple + - --- + - SvScriptNodeLite + +- Network: + - icon_name: SYSTEM + - extra_menu: ConnectionPartialMenu + - UdpClientNode + - SvFilePathNode + +- Pulga Physics: + - icon_name: MOD_PHYSICS + - extra_menu: AdvancedObjectsPartialMenu + - SvPulgaPhysicsSolverNode + - SvPulgaVectorForceNode + - SvPulgaSpringsForceNode + - SvPulgaDragForceNode + - SvPulgaPinForceNode + - SvPulgaTimedForceNode + - SvPulgaCollisionForceNode + - SvPulgaAttractionForceNode + - SvPulgaAlignForceNode + - SvPulgaFitForceNode + - SvPulgaObstacleForceNode + - SvPulgaRandomForceNode + - SvPulgaBoundingBoxForceNode + - SvPulgaInflateForceNode + - SvPulgaAttractorsForceNodeMk2 + - SvPulgaAngleForceNode + - SvPulgaVortexForceNode + - SvPulgaPhysicsNode + +- SVG: + - icon_name: SV_SVG + - extra_menu: ConnectionPartialMenu + - SvSvgDocumentNode + - SvSvgCircleNode + - SvSvgPathNodeMk2 + - SvSvgMeshNode + - SvSvgTextNode + - SvSvgDimensionNode + - SvSvgGroupNode + - SvSvgFillStrokeNodeMk2 + - SvSvgPatternNode + +- Beta Nodes: + - icon_name: SV_BETA + - extra_menu: AdvancedObjectsPartialMenu + - SvFormulaShapeNode + - SvHeavyTriangulateNode + - SvMeshUVColorNode + - SvUVPointonMeshNode + - SvSampleUVColorNode + - SvSubdivideLiteNode + - SvExtrudeSeparateLiteNode + - SvUnsubdivideNode + - SvLimitedDissolveMK2 + - SvArmaturePropsNode + - SvLatticePropsNode + - --- + - SvSculptMaskNode + - SvSelectMeshVerts + - SvSetCustomMeshNormals + - --- + - SvCombinatoricsNode + +- Alpha Nodes: + - icon_name: SV_ALPHA + - extra_menu: AdvancedObjectsPartialMenu + - SvBManalyzinNode + - SvBMObjinputNode + - SvBMoutputNode + - SvBMtoElementNode + - SvBMOpsNodeMK2 + - --- + - SvCSGBooleanNodeMK2 + - SvNumpyArrayNode + - SvSNFunctorB + - SvParticlesMK2Node + - SvJoinTrianglesNode + - SvListSliceLiteNode + - SvCacheNode + - SvUVtextureNode + - SvSeparateMeshNodeMK2 + - SvMultiExtrudeAlt + - SvPlanarEdgenetToPolygons + - SvSweepModulator + - --- + - SvGetPropNodeMK2 + - SvSetPropNodeMK2 + +- --- + +- Group: # label of custom menu to show + - custom_menu: NODE_MT_SverchokGroupMenu # bl_idname of the custom menu + - icon_name: NODETREE + +- Presets: + - custom_menu: NODEVIEW_MT_AddPresetMenu + - icon_name: SETTINGS diff --git a/menu.py b/menu.py deleted file mode 100644 index e73b5b74ec..0000000000 --- a/menu.py +++ /dev/null @@ -1,651 +0,0 @@ -# -*- coding: utf-8 -*- -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import os -from os.path import dirname -from collections import OrderedDict, defaultdict - -import bpy -from nodeitems_utils import NodeCategory, NodeItem, NodeItemCustom -import nodeitems_utils -import bl_operators - -import sverchok -from sverchok.utils import get_node_class_reference -from sverchok.utils.logging import getLogger -from sverchok.utils.sv_help import build_help_remap -from sverchok.ui.sv_icons import node_icon, icon -from sverchok.utils.context_managers import sv_preferences -from sverchok.utils.extra_categories import get_extra_categories -from sverchok.ui.presets import apply_default_preset -from sverchok.utils.sv_json_import import JSONImporter - -temp_details = { - 'not_enabled_nodes': {}, - 'node_count': 0 -} - - -class SverchNodeCategory(NodeCategory): - @classmethod - def poll(cls, context): - return context.space_data.tree_type == 'SverchCustomTreeType' - - def __repr__(self): - return f"" - -def make_node_cats(): - ''' - this loads the index.md file and converts it to an OrderedDict of node categories. - - ''' - - index_path = os.path.join(dirname(__file__), 'index.md') - - node_cats = OrderedDict() - with open(index_path) as md: - category = None - temp_list = [] - for line in md: - if not line.strip(): - continue - if line.strip().startswith('>'): - continue - elif line.startswith('##'): - if category: - node_cats[category] = temp_list - category = line[2:].strip() - temp_list = [] - - elif line.strip() == '---': - temp_list.append(['separator']) - else: - bl_idname = line.strip() - temp_list.append([bl_idname]) - - # final append - node_cats[category] = temp_list - - return node_cats - -def is_submenu_name(name): - return '@' in name - -def is_submenu_call(name): - return name.startswith('@') - -def get_submenu_call_name(name): - return name[1:].strip() - -def compose_submenu_name(category, name): - return category + ' @ ' + get_submenu_call_name(name) - -def include_submenus(node_cats): - result = defaultdict(list) - for category in node_cats: - if is_submenu_name(category): - continue - for item in node_cats[category]: - name = item[0] - if is_submenu_call(name): - submenu_name = compose_submenu_name(category, name) - result[category].append(['separator']) - result[category].extend(node_cats[submenu_name]) - result[category].append(['separator']) - else: - result[category].append(item) - return result - -def juggle_and_join(node_cats): - ''' - this step post processes the extended categorization used - by ctrl+space dynamic menu, and attempts to merge previously - joined categories. Why? Because the default menu gets very - long if there are too many categories. - - The only real alternative to this approach is to write a - replacement for nodeitems_utils which respects categories - and submenus. - - ''' - node_cats = node_cats.copy() - - # join beta and alpha node cats - alpha = node_cats.pop('Alpha Nodes') - node_cats['Beta Nodes'].extend(alpha) - - # put masks into list main - for ltype in ["List Masks", "List Mutators"]: - node_refs = node_cats.pop(ltype) - node_cats["List Main"].extend(node_refs) - - objects_cat = node_cats.pop('Objects') - node_cats['BPY Data'].extend(objects_cat) - - # add extended gens to Gens menu - gen_ext = node_cats.pop("Generators Extended") - node_cats["Generator"].extend(gen_ext) - - return node_cats - -class SvResetNodeSearchOperator(bpy.types.Operator): - """ - Reset node search string and return to selection of node by category - """ - bl_idname = "node.sv_reset_node_search" - bl_label = "Reset search" - bl_options = {'REGISTER', 'INTERNAL'} - - def execute(self, context): - context.scene.sv_node_search = "" - return {'FINISHED'} - -# We are creating and registering node adding operators dynamically. -# So, we have to remember them in order to unregister them when needed. -node_add_operators = {} - -node_panels = [] - -class SverchNodeItem(object): - """ - A local replacement of nodeitems_utils.NodeItem. - This calls our custom operator (see make_add_operator) instead of - standard node.add_node. Having this replacement item class allows us to: - * Have icons in the T panel - * Have arbitrary tooltips in the T panel. - """ - def __init__(self, nodetype, label=None, settings=None, poll=None): - self.nodetype = nodetype - self._label = label - if settings is None: - self.settings = {} - else: - self.settings = settings - self.poll = poll - - self.make_add_operator() - - @staticmethod - def new(name): - if name == 'separator': - return SverchSeparator() - else: - return SverchNodeItem(name) - - @property - def label(self): - if self._label: - return self._label - else: - return self.get_node_class().bl_rna.name - - def get_node_class(self): - return get_node_class_reference(self.nodetype) - - def get_node_strings(self): - node_class = self.get_node_class() - if hasattr(node_class, 'docstring'): - shorthand = node_class.docstring.get_shorthand() - else: - shorthand = "" - - if hasattr(node_class, 'docstring'): - tooltip = node_class.docstring.get_tooltip() - else: - tooltip = "" - - if hasattr(node_class, "bl_label"): - label = node_class.bl_label - else: - label = "" - - return label, shorthand, tooltip - - def search_match(self, needle): - needle = needle.upper() - label, shorthand, tooltip = self.get_node_strings() - #logger.info("shorthand: %s, tooltip: %s, label: %s, needle: %s", shorthand, tooltip, label, needle) - return (needle in label.upper()) or (needle in shorthand.upper()) or (needle in tooltip.upper()) - - def get_idname(self): - return get_node_idname_for_operator(self.nodetype) - - def make_add_operator(self): - """ - Create operator class which adds specific type of node. - Tooltip (docstring) for that operator is copied from - node class docstring. - """ - - global node_add_operators - - class SverchNodeAddOperator(bl_operators.node.NodeAddOperator, bpy.types.Operator): - """Wrapper for node.add_node operator to add specific node""" - - bl_idname = "node.sv_add_" + self.get_idname() - bl_label = "Add {} node".format(self.label) - bl_options = {'REGISTER', 'UNDO'} - - def execute(operator, context): - # please not be confused: "operator" here references to - # SverchNodeAddOperator instance, and "self" references to - # SverchNodeItem instance. - operator.use_transform = True - operator.type = self.nodetype - node = operator.create_node(context) - apply_default_preset(node) - return {'FINISHED'} - - node_class = self.get_node_class() - SverchNodeAddOperator.__name__ = node_class.__name__ - - if hasattr(node_class, "docstring"): - SverchNodeAddOperator.__doc__ = node_class.docstring.get_tooltip() - else: - SverchNodeAddOperator.__doc__ = node_class.__doc__ - - node_add_operators[self.get_idname()] = SverchNodeAddOperator - bpy.utils.register_class(SverchNodeAddOperator) - - def get_icon(self): - rna = self.get_node_class().bl_rna - if hasattr(rna, "bl_icon"): - return rna.bl_icon - else: - return "RNA" - - @staticmethod - def draw(self, layout, context): - add = draw_add_node_operator(layout, self.nodetype, label=self._label) - if add is None: - return - - for setting in self.settings.items(): - ops = add.settings.add() - ops.name = setting[0] - ops.value = setting[1] - -class SverchSeparator(object): - nodetype = 'separator' - @staticmethod - def draw(self, layout, context): - layout.separator() - - @classmethod - def poll(cls, context): - return context.space_data.tree_type == 'SverchCustomTreeType' - - def search_match(self, needle): - return True - -class SvOperatorLayout(object): - """ - Abstract layout class for operator buttons. - Wraps standard Blender's UILayout. - """ - def __init__(self, parent): - self.parent = parent - self._prev_is_separator = False - - def label(self, text): - self.parent.label(text=text) - - def separator(self): - if not self._prev_is_separator: - self.parent.separator() - self._prev_is_separator = True - - @staticmethod - def get(icons_only, parent, columns): - if icons_only: - return SvIconsLayout(parent, columns) - else: - return SvNamedButtonsLayout(parent) - -class SvIconsLayout(SvOperatorLayout): - """ - Layout class that shows operator buttons - with icons only (or with text if the operator does not have icon), - in the specified number of columns. - """ - def __init__(self, parent, columns=4): - super().__init__(parent) - self.columns = columns - self._column = 0 - self._flow = None - - def separator(self): - if not self._prev_is_separator: - self._flow = None - self._column = 0 - self.parent.separator() - self._prev_is_separator = True - - def operator(self, operator_name, **params): - self._prev_is_separator = False - if 'icon_value' in params or 'icon' in params: - if self._flow is None: - #self._flow = self.parent.column_flow(columns=self.columns, align=True) - self._flow = self.parent.grid_flow(row_major=True, align=True, columns=self.columns) - self._flow.scale_x = self._flow.scale_y = 1.5 - params['text'] = "" - op = self._flow.operator(operator_name, **params) - return op - else: - self._flow = None - self._column = 0 - return self.parent.operator(operator_name, **params) - -class SvNamedButtonsLayout(SvOperatorLayout): - """ - Layout class that shows operator buttons in a standard way - (icon and text). - """ - def operator(self, operator_name, **params): - self._prev_is_separator = False - return self.parent.operator(operator_name, **params) - -def get_node_idname_for_operator(nodetype): - """Select valid bl_idname for node to create node adding operator bl_idname.""" - rna = get_node_class_reference(nodetype) - if not rna: - raise Exception("Can't find registered node {}".format(nodetype)) - if hasattr(rna, 'bl_idname'): - return rna.bl_idname.lower() - elif nodetype == "NodeReroute": - return "node_reroute" - else: - return rna.name.lower() - -def draw_add_node_operator(layout, nodetype, label=None, icon_name=None, params=None): - """ - Draw node adding operator button. - This is to be used both in Shift-A menu and in T panel. - """ - - default_context = bpy.app.translations.contexts.default - node_class = get_node_class_reference(nodetype) - if node_class is None: - logger.info("cannot locate node class: %s", nodetype) - return - node_rna = node_class.bl_rna - - if label is None: - if hasattr(node_rna, 'bl_label'): - label = node_rna.bl_label - elif nodetype == "NodeReroute": - label = "Reroute" - else: - label = node_rna.name - - if params is None: - params = dict(text=label) - params['text_ctxt'] = default_context - if icon_name is not None: - params.update(**icon(icon_name)) - else: - params.update(**node_icon(node_rna)) - - add = layout.operator("node.sv_add_" + get_node_idname_for_operator(nodetype), **params) - - add.type = nodetype - add.use_transform = True - - return add - - -def strformated_tree(nodes): - - lookup = sverchok.utils.dummy_nodes.dummy_nodes_dict - - lstr = [] - for category, nodes_in_category in nodes.items(): - lstr.append(category + "\n") - for node_bl_idname in sorted(nodes_in_category): - item = lookup.get(node_bl_idname) - if item: - node_bl_label, dependencies_listed = item - lstr.append(f" {node_bl_label} ({dependencies_listed})\n") - - return "".join(lstr) - -def log_non_enabled_nodes(): - """ - this function will output a formatted log of the nodes that are not currently enabled - """ - msg = "The following nodes are not enabled (probably due to missing dependencies)" - logger.info(f"sv: {msg}\n{strformated_tree(temp_details['not_enabled_nodes'])}") - -def log_node_count(): - """ - this will log the node count at startup, but can also be a place to log the current node count - if we want. - """ - logger.info(f"sv: {temp_details['node_count']} nodes at startup.") - -def log_details(): - log_non_enabled_nodes() - log_node_count() - - -def make_categories(): - original_categories = make_node_cats() - - node_cats = juggle_and_join(original_categories) - node_cats = include_submenus(node_cats) - node_categories = [] - node_count = 0 - - nodes_not_enabled = defaultdict(list) - - for category, nodes in node_cats.items(): - name_big = "SVERCHOK_" + category.replace(' ', '_') - node_items = [] - for item in nodes: - nodetype = item[0] - if is_submenu_call(nodetype): - continue - rna = get_node_class_reference(nodetype) - if not rna and not nodetype == 'separator': - nodes_not_enabled[category].append(nodetype) - else: - node_item = SverchNodeItem.new(nodetype) - node_items.append(node_item) - - if node_items: - node_categories.append( - SverchNodeCategory( - name_big, - category, - items=node_items)) - node_count += len(nodes) - - # logger.info(f"The following nodes are not enabled (probably due to missing dependencies)\n{strformated_tree(nodes_not_enabled)}") - temp_details['not_enabled_nodes'] = nodes_not_enabled - - return node_categories, node_count, original_categories - -def register_node_panels(identifier, std_menu): - global node_panels - - def get_cat_list(): - extra_categories = get_extra_categories() - cat_list = std_menu[:] - cat_list.extend(extra_categories) - return cat_list - - with sv_preferences() as prefs: - if prefs.node_panels == "N": - - def draw_node_item(self, context): - layout = self.layout - col = SvOperatorLayout.get(prefs.node_panels_icons_only, layout.column(align=True), prefs.node_panels_columns) - for item in self.category.items(context): - item.draw(item, col, context) - - for category in get_cat_list(): - panel_type = type("NODE_PT_category_sv_" + category.identifier, (bpy.types.Panel,), { - "bl_space_type": "NODE_EDITOR", - "bl_region_type": "UI", - "bl_label": category.name, - "bl_category": category.name, - "category": category, - "poll": category.poll, - "draw": draw_node_item, - }) - node_panels.append(panel_type) - bpy.utils.register_class(panel_type) - - elif prefs.node_panels == "T": - - class SV_PT_NodesTPanel(bpy.types.Panel): - """Nodes panel under the T panel""" - - bl_space_type = "NODE_EDITOR" - bl_region_type = "TOOLS" - bl_label = "Sverchok Nodes" - - @classmethod - def poll(cls, context): - return context.space_data.tree_type == 'SverchCustomTreeType' - - def draw(self, context): - layout = self.layout - row = layout.row(align=True) - row.prop(context.scene, "sv_node_search", text="") - row.operator("node.sv_reset_node_search", icon="X", text="") - if not context.scene.sv_node_search: - layout.prop(context.scene, "sv_selected_category", text="") - - col = SvOperatorLayout.get(prefs.node_panels_icons_only, layout.column(align=True), prefs.node_panels_columns) - - needle = context.scene.sv_node_search - # We will search either by category selected in the popup menu, - # or by search term. - check_search = needle != "" - check_category = not check_search - category_is_first = True - - for category in get_cat_list(): - category_ok = category.identifier == context.scene.sv_selected_category - if check_category: - if not category_ok: - continue - - items_to_draw = [] - has_nodes = False - for item in category.items(context): - if check_search: - if not hasattr(item, 'search_match'): - continue - if not item.search_match(needle): - continue - if not isinstance(item, SverchSeparator): - has_nodes = True - # Do not show separators if we are searching by text - - # otherwise search results would take too much vertical space. - if not (check_search and isinstance(item, SverchSeparator)): - items_to_draw.append(item) - - # Show category only if it has some nodes to display - # according to search terms. - if has_nodes: - if check_search: - if not category_is_first: - col.separator() - col.label(category.name + ":") - for item in items_to_draw: - item.draw(item, col, context) - - category_is_first = False - - node_panels.append(SV_PT_NodesTPanel) - bpy.utils.register_class(SV_PT_NodesTPanel) - -def unregister_node_panels(): - global node_panels - for panel_type in reversed(node_panels): - bpy.utils.unregister_class(panel_type) - node_panels = [] - -def reload_menu(): - menu, node_count, original_categories = make_categories() - if hasattr(bpy.types, "SV_PT_NodesTPanel"): - unregister_node_panels() - unregister_node_add_operators() - - register_node_panels("SVERCHOK", menu) - register_node_add_operators() - - build_help_remap(original_categories) - print("Reload complete, press update") - -def register_node_add_operators(): - """Register all our custom node adding operators""" - for idname in node_add_operators: - bpy.utils.register_class(node_add_operators[idname]) - -def unregister_node_add_operators(): - """Unregister all our custom node adding operators""" - for idname in node_add_operators: - bpy.utils.unregister_class(node_add_operators[idname]) - -def get_all_categories(std_categories): - - def generate(self, context): - nonlocal std_categories - extra_categories = get_extra_categories() - n = len(std_categories) - all_categories = std_categories[:] - for category in extra_categories: - n += 1 - all_categories.append((category.identifier, category.name, category.name, n)) - return all_categories - return generate - -def register(): - global logger - logger = getLogger("menu") - menu, node_count, original_categories = make_categories() - temp_details['node_count'] = node_count - - if hasattr(bpy.types, "SV_PT_NodesTPanel"): - unregister_node_panels() - - categories = [(category.identifier, category.name, category.name, i) for i, category in enumerate(menu)] - bpy.types.Scene.sv_selected_category = bpy.props.EnumProperty( - name = "Category", - description = "Select nodes category", - items = get_all_categories(categories) - ) - bpy.types.Scene.sv_node_search = bpy.props.StringProperty( - name = "Search", - description = "Enter search term and press Enter to search; clear the field to return to selection of node category." - ) - - bpy.utils.register_class(SvResetNodeSearchOperator) - register_node_panels("SVERCHOK", menu) - - build_help_remap(original_categories) - -def unregister(): - unregister_node_panels() - unregister_node_add_operators() - bpy.utils.unregister_class(SvResetNodeSearchOperator) - del bpy.types.Scene.sv_selected_category - del bpy.types.Scene.sv_node_search diff --git a/node_tree.py b/node_tree.py index 8c868c3886..18b61851c2 100644 --- a/node_tree.py +++ b/node_tree.py @@ -599,6 +599,7 @@ class SvSomeOperationNode(SverchCustomTreeNode, bpy.types.Node): ``` """ _docstring = None # A cache for docstring property + sv_category = '' #: Add node to a category by its name to display with Shift+S @final def draw_buttons(self, context, layout): diff --git a/nodes/curve/bezier_fit.py b/nodes/curve/bezier_fit.py index 99702430c5..a79ab13975 100644 --- a/nodes/curve/bezier_fit.py +++ b/nodes/curve/bezier_fit.py @@ -16,91 +16,95 @@ else: from scipy.optimize import curve_fit - def init_guess(verts, npoints): - approx = linear_approximation(verts) - line = approx.most_similar_line() - projections = line.projection_of_points(verts) - m = projections.min(axis=0) - M = projections.max(axis=0) - return np.linspace(m, M, num=npoints) - - def goal(ts, *xs): - n3 = len(xs) - n = n3 // 3 - control_points = np.array(xs).reshape((n,3)) - curve = SvBezierCurve(control_points) - pts = curve.evaluate_array(ts) - return np.ravel(pts) - - class SvExBezierCurveFitNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Bezier Curve Fit / Approximate - Tooltip: Approximate points with Bezier curve - """ - bl_idname = 'SvExBezierCurveFitNode' - bl_label = 'Approximate Bezier Curve' - bl_icon = 'CURVE_NCURVE' - sv_icon = 'SV_APPROXIMATE_BEZIER' - - degree : IntProperty( - name = "Degree", - min = 2, - default = 3, - update = updateNode) - - metrics = [ - ('MANHATTAN', 'Manhattan', "Manhattan distance metric", 0), - ('DISTANCE', 'Euclidan', "Eudlcian distance metric", 1), - ('POINTS', 'Points', "Points based", 2), - ('CHEBYSHEV', 'Chebyshev', "Chebyshev distance", 3)] - - metric: EnumProperty(name='Metric', - description = "Knot mode", - default="DISTANCE", items=metrics, - update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - self.inputs.new('SvStringsSocket', "Degree").prop_name = 'degree' - self.outputs.new('SvCurveSocket', "Curve") - self.outputs.new('SvVerticesSocket', "ControlPoints") - - def draw_buttons_ext(self, context, layout): - layout.prop(self, 'metric') - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - degree_s = self.inputs['Degree'].sv_get() - - vertices_s = ensure_nesting_level(vertices_s, 3) - degree_s = ensure_nesting_level(degree_s, 2) - - curve_out = [] - points_out = [] - for vertices, degree in zip_long_repeat(vertices_s, degree_s): - if isinstance(degree, (tuple, list)): - degree = degree[0] - - n = len(vertices) - npoints = degree + 1 - vertices = np.array(vertices) - - #xdata = np.linspace(0, 1, num=n) - xdata = Spline.create_knots(vertices, metric=self.metric) - ydata = np.ravel(vertices) - - p0 = init_guess(vertices, npoints) - popt, pcov = curve_fit(goal, xdata, ydata, p0) - control_points = popt.reshape((npoints,3)) - curve = SvBezierCurve(control_points) - curve_out.append(curve) - points_out.append(control_points.tolist()) - - self.outputs['Curve'].sv_set(curve_out) - self.outputs['ControlPoints'].sv_set(points_out) + +def init_guess(verts, npoints): + approx = linear_approximation(verts) + line = approx.most_similar_line() + projections = line.projection_of_points(verts) + m = projections.min(axis=0) + M = projections.max(axis=0) + return np.linspace(m, M, num=npoints) + + +def goal(ts, *xs): + n3 = len(xs) + n = n3 // 3 + control_points = np.array(xs).reshape((n,3)) + curve = SvBezierCurve(control_points) + pts = curve.evaluate_array(ts) + return np.ravel(pts) + + +class SvExBezierCurveFitNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Bezier Curve Fit / Approximate + Tooltip: Approximate points with Bezier curve + """ + bl_idname = 'SvExBezierCurveFitNode' + bl_label = 'Approximate Bezier Curve' + bl_icon = 'CURVE_NCURVE' + sv_icon = 'SV_APPROXIMATE_BEZIER' + + degree : IntProperty( + name = "Degree", + min = 2, + default = 3, + update = updateNode) + + metrics = [ + ('MANHATTAN', 'Manhattan', "Manhattan distance metric", 0), + ('DISTANCE', 'Euclidan', "Eudlcian distance metric", 1), + ('POINTS', 'Points', "Points based", 2), + ('CHEBYSHEV', 'Chebyshev', "Chebyshev distance", 3)] + + metric: EnumProperty(name='Metric', + description = "Knot mode", + default="DISTANCE", items=metrics, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + self.inputs.new('SvStringsSocket', "Degree").prop_name = 'degree' + self.outputs.new('SvCurveSocket', "Curve") + self.outputs.new('SvVerticesSocket', "ControlPoints") + + def draw_buttons_ext(self, context, layout): + layout.prop(self, 'metric') + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + degree_s = self.inputs['Degree'].sv_get() + + vertices_s = ensure_nesting_level(vertices_s, 3) + degree_s = ensure_nesting_level(degree_s, 2) + + curve_out = [] + points_out = [] + for vertices, degree in zip_long_repeat(vertices_s, degree_s): + if isinstance(degree, (tuple, list)): + degree = degree[0] + + n = len(vertices) + npoints = degree + 1 + vertices = np.array(vertices) + + #xdata = np.linspace(0, 1, num=n) + xdata = Spline.create_knots(vertices, metric=self.metric) + ydata = np.ravel(vertices) + + p0 = init_guess(vertices, npoints) + popt, pcov = curve_fit(goal, xdata, ydata, p0) + control_points = popt.reshape((npoints,3)) + curve = SvBezierCurve(control_points) + curve_out.append(curve) + points_out.append(control_points.tolist()) + + self.outputs['Curve'].sv_set(curve_out) + self.outputs['ControlPoints'].sv_set(points_out) + def register(): if scipy is not None: diff --git a/nodes/curve/catenary_curve.py b/nodes/curve/catenary_curve.py index f6ee6f88cc..103197befd 100644 --- a/nodes/curve/catenary_curve.py +++ b/nodes/curve/catenary_curve.py @@ -15,68 +15,70 @@ else: from sverchok.utils.catenary import CatenarySolver - class SvExCatenaryCurveNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Catenary Curve - Tooltip: Generate Catenary Curve - """ - bl_idname = 'SvExCatenaryCurveNode' - bl_label = 'Catenary Curve' - bl_icon = 'CURVE_NCURVE' - sv_icon = 'SV_CATENARY' - - length : FloatProperty( - name = "Length", - min = 0.0, - default = 3.0, + +class SvExCatenaryCurveNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Catenary Curve + Tooltip: Generate Catenary Curve + """ + bl_idname = 'SvExCatenaryCurveNode' + bl_label = 'Catenary Curve' + bl_icon = 'CURVE_NCURVE' + sv_icon = 'SV_CATENARY' + + length : FloatProperty( + name = "Length", + min = 0.0, + default = 3.0, + update = updateNode) + + def sv_init(self, context): + p = self.inputs.new('SvVerticesSocket', "Point1") + p.use_prop = True + p.default_property = (-1.0, 0.0, 0.0) + p = self.inputs.new('SvVerticesSocket', "Point2") + p.use_prop = True + p.default_property = (1.0, 0.0, 0.0) + p = self.inputs.new('SvVerticesSocket', "Gravity") + p.use_prop = True + p.default_property = (0.0, 0.0, -1.0) + + self.inputs.new('SvStringsSocket', "Length").prop_name = 'length' + + self.outputs.new('SvCurveSocket', "Curve") + + join : BoolProperty( + name = "Join", + description = "Output single list of curves for all input points", + default = True, update = updateNode) - def sv_init(self, context): - p = self.inputs.new('SvVerticesSocket', "Point1") - p.use_prop = True - p.default_property = (-1.0, 0.0, 0.0) - p = self.inputs.new('SvVerticesSocket', "Point2") - p.use_prop = True - p.default_property = (1.0, 0.0, 0.0) - p = self.inputs.new('SvVerticesSocket', "Gravity") - p.use_prop = True - p.default_property = (0.0, 0.0, -1.0) - - self.inputs.new('SvStringsSocket', "Length").prop_name = 'length' - - self.outputs.new('SvCurveSocket', "Curve") - - join : BoolProperty( - name = "Join", - description = "Output single list of curves for all input points", - default = True, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'join', toggle=True) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - point1_s = self.inputs['Point1'].sv_get() - point2_s = self.inputs['Point2'].sv_get() - force_s = self.inputs['Gravity'].sv_get() - length_s = self.inputs['Length'].sv_get() - - curves_out = [] - for point1s, point2s, forces, lengths in zip_long_repeat(point1_s, point2_s, force_s, length_s): - new_curves = [] - for point1, point2, force, length in zip_long_repeat(point1s, point2s, forces, lengths): - solver = CatenarySolver(np.array(point1), np.array(point2), length, np.array(force)) - curve = solver.solve() - new_curves.append(curve) - if self.join: - curves_out.extend(new_curves) - else: - curves_out.append(new_curves) - - self.outputs['Curve'].sv_set(curves_out) + def draw_buttons(self, context, layout): + layout.prop(self, 'join', toggle=True) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + point1_s = self.inputs['Point1'].sv_get() + point2_s = self.inputs['Point2'].sv_get() + force_s = self.inputs['Gravity'].sv_get() + length_s = self.inputs['Length'].sv_get() + + curves_out = [] + for point1s, point2s, forces, lengths in zip_long_repeat(point1_s, point2_s, force_s, length_s): + new_curves = [] + for point1, point2, force, length in zip_long_repeat(point1s, point2s, forces, lengths): + solver = CatenarySolver(np.array(point1), np.array(point2), length, np.array(force)) + curve = solver.solve() + new_curves.append(curve) + if self.join: + curves_out.extend(new_curves) + else: + curves_out.append(new_curves) + + self.outputs['Curve'].sv_set(curves_out) + def register(): if scipy is not None: diff --git a/nodes/curve/circlify.py b/nodes/curve/circlify.py index 3d9a5bfcd1..1632cf6cb3 100644 --- a/nodes/curve/circlify.py +++ b/nodes/curve/circlify.py @@ -12,108 +12,109 @@ if circlify is None: add_dummy('SvExCirclifyNode', "Circlify", 'circlify') -else: - - class SvExCirclifyNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Circlify - Tooltip: Generate circles packed into a larger circle - """ - bl_idname = 'SvExCirclifyNode' - bl_label = 'Circlify' - bl_icon = 'MESH_CIRCLE' - sv_icon = 'SV_CIRCLIFY' - - major_radius : FloatProperty( - name = "Major Radius", - default = 1.0, - update = updateNode) - - planes = [ - ('XY', "XY", "XOY plane", 0), - ('YZ', "YZ", "YOZ plane", 1), - ('XZ', "XZ", "XOZ plane", 2) - ] - - plane : EnumProperty( - name = "Plane", - items = planes, - default = 'XY', - update = updateNode) - - show_enclosure : BoolProperty( - name = "Show enclosure", - default = True, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'plane', expand=True) - layout.prop(self, 'show_enclosure', toggle=True) - - def sv_init(self, context): - self.inputs.new('SvStringsSocket', 'Radiuses') - d = self.inputs.new('SvVerticesSocket', "Center") - d.use_prop = True - d.default_property = (0.0, 0.0, 0.0) - - self.inputs.new('SvStringsSocket', "MajorRadius").prop_name = 'major_radius' - self.outputs.new('SvCurveSocket', "Circles") - self.outputs.new('SvVerticesSocket', "Centers") - self.outputs.new('SvStringsSocket', "Radiuses") - - def to_2d(self, v): - x,y,z = v - if self.plane == 'XY': - return x,y - elif self.plane == 'YZ': - return y,z - else: - return x,z - - def circle_to_curve(self, general_center, circle): - x0 = circle.x - y0 = circle.y - if self.plane == 'XY': - center = Vector((x0, y0, general_center[2])) - matrix = Matrix.Translation(center) - elif self.plane == 'YZ': - center = Vector((general_center[0], x0, y0)) - matrix = Matrix.Rotation(pi/2, 4, 'Y') - matrix.translation = center - else: - center = Vector((x0, general_center[1], y0)) - matrix = Matrix.Rotation(pi/2, 4, 'X') - matrix.translation = center - curve = SvCircle(matrix, circle.r) - return curve - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - radiuses_s = self.inputs['Radiuses'].sv_get() - radiuses_s = ensure_nesting_level(radiuses_s, 3) - center_s = self.inputs['Center'].sv_get() - center_s = ensure_nesting_level(center_s, 3) - major_radius_s = self.inputs['MajorRadius'].sv_get() - major_radius_s = ensure_nesting_level(major_radius_s, 2) - - curves_out = [] - centers_out = [] - radius_out = [] - for radiuses, centers, major_radiuses in zip_long_repeat(radiuses_s, center_s, major_radius_s): - for radiuses, center, major_radius in zip_long_repeat(radiuses, centers, major_radiuses): - center_2d = self.to_2d(center) - enclosure = circlify.Circle(x=center_2d[0], y=center_2d[1], r=major_radius) - circles = circlify.circlify(radiuses, target_enclosure=enclosure, show_enclosure=self.show_enclosure) - curves = [self.circle_to_curve(center, circle) for circle in circles] - curves_out.extend(curves) - centers_out.append([tuple(curve.center) for curve in curves]) - radius_out.append([curve.radius for curve in curves]) - - self.outputs['Circles'].sv_set(curves_out) - self.outputs['Centers'].sv_set(centers_out) - self.outputs['Radiuses'].sv_set(radius_out) + + +class SvExCirclifyNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Circlify + Tooltip: Generate circles packed into a larger circle + """ + bl_idname = 'SvExCirclifyNode' + bl_label = 'Circlify' + bl_icon = 'MESH_CIRCLE' + sv_icon = 'SV_CIRCLIFY' + + major_radius : FloatProperty( + name = "Major Radius", + default = 1.0, + update = updateNode) + + planes = [ + ('XY', "XY", "XOY plane", 0), + ('YZ', "YZ", "YOZ plane", 1), + ('XZ', "XZ", "XOZ plane", 2) + ] + + plane : EnumProperty( + name = "Plane", + items = planes, + default = 'XY', + update = updateNode) + + show_enclosure : BoolProperty( + name = "Show enclosure", + default = True, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'plane', expand=True) + layout.prop(self, 'show_enclosure', toggle=True) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', 'Radiuses') + d = self.inputs.new('SvVerticesSocket', "Center") + d.use_prop = True + d.default_property = (0.0, 0.0, 0.0) + + self.inputs.new('SvStringsSocket', "MajorRadius").prop_name = 'major_radius' + self.outputs.new('SvCurveSocket', "Circles") + self.outputs.new('SvVerticesSocket', "Centers") + self.outputs.new('SvStringsSocket', "Radiuses") + + def to_2d(self, v): + x,y,z = v + if self.plane == 'XY': + return x,y + elif self.plane == 'YZ': + return y,z + else: + return x,z + + def circle_to_curve(self, general_center, circle): + x0 = circle.x + y0 = circle.y + if self.plane == 'XY': + center = Vector((x0, y0, general_center[2])) + matrix = Matrix.Translation(center) + elif self.plane == 'YZ': + center = Vector((general_center[0], x0, y0)) + matrix = Matrix.Rotation(pi/2, 4, 'Y') + matrix.translation = center + else: + center = Vector((x0, general_center[1], y0)) + matrix = Matrix.Rotation(pi/2, 4, 'X') + matrix.translation = center + curve = SvCircle(matrix, circle.r) + return curve + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + radiuses_s = self.inputs['Radiuses'].sv_get() + radiuses_s = ensure_nesting_level(radiuses_s, 3) + center_s = self.inputs['Center'].sv_get() + center_s = ensure_nesting_level(center_s, 3) + major_radius_s = self.inputs['MajorRadius'].sv_get() + major_radius_s = ensure_nesting_level(major_radius_s, 2) + + curves_out = [] + centers_out = [] + radius_out = [] + for radiuses, centers, major_radiuses in zip_long_repeat(radiuses_s, center_s, major_radius_s): + for radiuses, center, major_radius in zip_long_repeat(radiuses, centers, major_radiuses): + center_2d = self.to_2d(center) + enclosure = circlify.Circle(x=center_2d[0], y=center_2d[1], r=major_radius) + circles = circlify.circlify(radiuses, target_enclosure=enclosure, show_enclosure=self.show_enclosure) + curves = [self.circle_to_curve(center, circle) for circle in circles] + curves_out.extend(curves) + centers_out.append([tuple(curve.center) for curve in curves]) + radius_out.append([curve.radius for curve in curves]) + + self.outputs['Circles'].sv_set(curves_out) + self.outputs['Centers'].sv_set(centers_out) + self.outputs['Radiuses'].sv_set(radius_out) + def register(): if circlify is not None: diff --git a/nodes/curve/extremes.py b/nodes/curve/extremes.py index 240a713691..513029a098 100644 --- a/nodes/curve/extremes.py +++ b/nodes/curve/extremes.py @@ -1,14 +1,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -from mathutils import Matrix -from mathutils.kdtree import KDTree +from bpy.props import EnumProperty, IntProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve from sverchok.utils.field.scalar import SvScalarField from sverchok.utils.manifolds import curve_extremes @@ -17,98 +13,100 @@ if scipy is None: add_dummy('SvExCurveExtremesNode', "Curve Extremes", 'scipy') -else: - from scipy.optimize import minimize_scalar - - def goal(curve, point_from): - def distance(t): - dv = curve.evaluate(t) - np.array(point_from) - return np.linalg.norm(dv) - return distance - - class SvExCurveExtremesNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Curve Extremes - Tooltip: Find a point on curve which provides the maximum or minimum for specified scalar field - """ - bl_idname = 'SvExCurveExtremesNode' - bl_label = 'Curve Extremes' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_CURVE_EXTREMES' - - samples : IntProperty( - name = "Max Points", - default = 1, - min = 1, - update = updateNode) - - directions = [ - ('MIN', "Min", "Find the minimum of the field", 0), - ('MAX', "Max", "Find the maximum of the field", 1) - ] - - direction : EnumProperty( - name = "Direction", - items = directions, - default = 'MIN', - update = updateNode) - - on_fail_options = [ - ('FAIL', "Fail", "Raise an exception (node becomes red)", 0), - ('SKIP', "Skip", "Skip such interval or curve - just return an empty set of points", 1) - ] - - on_fail : EnumProperty( - name = "On fail", - items = on_fail_options, - default = 'FAIL', - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'direction', expand=True) - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - layout.prop(self, 'on_fail') - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - self.inputs.new('SvScalarFieldSocket', "Field") - self.inputs.new('SvStringsSocket', "MaxPoints").prop_name = 'samples' - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvStringsSocket', "T") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - curves_s = self.inputs['Curve'].sv_get() - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - fields_s = self.inputs['Field'].sv_get() - fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) - samples_s = self.inputs['MaxPoints'].sv_get() - samples_s = ensure_nesting_level(samples_s, 2) - - t_out = [] - point_out = [] - for curves, fields, samples_i in zip_long_repeat(curves_s, fields_s, samples_s): - new_t = [] - new_points = [] - for curve, field, samples in zip_long_repeat(curves, fields, samples_i): - ts = curve_extremes(curve, field, samples, self.direction, self.on_fail, self.get_logger()) - ps = curve.evaluate_array(np.array(ts)).tolist() - new_t.extend(ts) - new_points.extend(ps) - t_out.append(new_t) - point_out.append(new_points) - - self.outputs['Point'].sv_set(point_out) - self.outputs['T'].sv_set(t_out) + + +def goal(curve, point_from): + def distance(t): + dv = curve.evaluate(t) - np.array(point_from) + return np.linalg.norm(dv) + return distance + + +class SvExCurveExtremesNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Curve Extremes + Tooltip: Find a point on curve which provides the maximum or minimum for specified scalar field + """ + bl_idname = 'SvExCurveExtremesNode' + bl_label = 'Curve Extremes' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_CURVE_EXTREMES' + + samples : IntProperty( + name = "Max Points", + default = 1, + min = 1, + update = updateNode) + + directions = [ + ('MIN', "Min", "Find the minimum of the field", 0), + ('MAX', "Max", "Find the maximum of the field", 1) + ] + + direction : EnumProperty( + name = "Direction", + items = directions, + default = 'MIN', + update = updateNode) + + on_fail_options = [ + ('FAIL', "Fail", "Raise an exception (node becomes red)", 0), + ('SKIP', "Skip", "Skip such interval or curve - just return an empty set of points", 1) + ] + + on_fail : EnumProperty( + name = "On fail", + items = on_fail_options, + default = 'FAIL', + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'direction', expand=True) + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'on_fail') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + self.inputs.new('SvScalarFieldSocket', "Field") + self.inputs.new('SvStringsSocket', "MaxPoints").prop_name = 'samples' + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvStringsSocket', "T") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curves_s = self.inputs['Curve'].sv_get() + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + fields_s = self.inputs['Field'].sv_get() + fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) + samples_s = self.inputs['MaxPoints'].sv_get() + samples_s = ensure_nesting_level(samples_s, 2) + + t_out = [] + point_out = [] + for curves, fields, samples_i in zip_long_repeat(curves_s, fields_s, samples_s): + new_t = [] + new_points = [] + for curve, field, samples in zip_long_repeat(curves, fields, samples_i): + ts = curve_extremes(curve, field, samples, self.direction, self.on_fail, self.get_logger()) + ps = curve.evaluate_array(np.array(ts)).tolist() + new_t.extend(ts) + new_points.extend(ps) + t_out.append(new_t) + point_out.append(new_points) + + self.outputs['Point'].sv_set(point_out) + self.outputs['T'].sv_set(t_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExCurveExtremesNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExCurveExtremesNode) diff --git a/nodes/curve/interpolate_frame.py b/nodes/curve/interpolate_frame.py index bdfcfa479a..315723a77a 100644 --- a/nodes/curve/interpolate_frame.py +++ b/nodes/curve/interpolate_frame.py @@ -2,13 +2,12 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty, StringProperty +from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty from mathutils import Vector, Matrix from mathutils import kdtree from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, match_long_repeat, ensure_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve from sverchok.utils.geom import PlaneEquation from sverchok.utils.math import xyz_axes @@ -18,165 +17,171 @@ if scipy is None: add_dummy('SvExSlerpCurveFrameNode', "Interpolate Curve Frame", 'scipy') -else: - - def nearest_solution(point, solutions): - if len(solutions) == 0: - return None, None - if len(solutions) <= 1: - return solutions[0] - kdt = kdtree.KDTree(len(solutions)) - for i, solution in enumerate(solutions): - v = solution[1] - kdt.insert(v, i) - kdt.balance() - _, i, _ = kdt.find(point) - return solutions[i] - - def matrix_to_curve(curve, matrix, z_axis, init_samples=10, tolerance=1e-3, maxiter=50): - plane = PlaneEquation.from_matrix(matrix, normal_axis=z_axis) - # Or take nearest point? - solutions = intersect_curve_plane(curve, plane, - init_samples=init_samples, - tolerance=tolerance, - maxiter=maxiter) - t, point = nearest_solution(matrix.translation, solutions) - if t is None: - raise Exception(f"Can't project the matrix {matrix} to the {curve}!") - #matrix.translation = Vector(point) - return t, matrix.to_quaternion() - - def interpolate(tknots, ts, points, quats): - base_indexes = tknots.searchsorted(ts, side='left')-1 - t1s, t2s = tknots[base_indexes], tknots[base_indexes+1] - dts = (ts - t1s) / (t2s - t1s) - #dts = np.clip(dts, 0.0, 1.0) # Just in case... - matrix_out = [] - # TODO: ideally this should be vectorized with numpy; - # but that would require implementation of quaternion - # interpolation in numpy. - for dt, base_index, point in zip(dts, base_indexes, points): - q1, q2 = quats[base_index], quats[base_index+1] - # spherical linear interpolation. - # TODO: implement `squad`. - if dt < 0: - q = q1 - elif dt > 1.0: - q = q2 - else: - q = q1.slerp(q2, dt) - matrix = q.to_matrix().to_4x4() - matrix.translation = Vector(point) - matrix_out.append(matrix) - return matrix_out - - def interpolate_frames(curve, frames, z_axis, ts, init_samples=10, tolerance=1e-3, maxiter=50): - quats = [] - tknots = [] - for frame in frames: - tk, quat = matrix_to_curve(curve, frame, z_axis, init_samples, tolerance, maxiter) - quats.append(quat) - tknots.append(tk) - - quats.insert(0, quats[0]) - quats.append(quats[-1]) - - tknots.insert(0, -np.inf) - tknots.append(np.inf) - tknots = np.array(tknots) - - ts = np.array(ts) - points = curve.evaluate_array(ts) - return interpolate(tknots, ts, points, quats) - - class SvExSlerpCurveFrameNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Interpolate Curve Frame - Tooltip: Interpolate curve frames - """ - bl_idname = 'SvExSlerpCurveFrameNode' - bl_label = 'Interpolate Curve Frame' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_INTERP_FRAME' - - samples : IntProperty( - name = "Curve Resolution", - description = "A number of segments to subdivide the curve in; defines the maximum number of intersection points that is possible to find.", - default = 5, - min = 1, - update = updateNode) - - accuracy : IntProperty( - name = "Accuracy", - description = "Accuracy level - number of exact digits after decimal points.", - default = 4, - min = 1, - update = updateNode) - - t_value : FloatProperty( - name = "T", - default = 0.5, - update = updateNode) - - join : BoolProperty( - name = "Join", - description = "Output single flat list of matrices for all input curves", - default = True, - update = updateNode) - - z_axis : EnumProperty( - name = "Orientation", - description = "Which axis of the provided frames points along the curve", - items = xyz_axes, - default = 'Z', - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'z_axis', expand=True) - layout.prop(self, 'samples') - layout.prop(self, 'join', toggle=True) - - def draw_buttons_ext(self, context, layout): - layout.prop(self, 'accuracy') - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - self.inputs.new('SvMatrixSocket', "Frames") - self.inputs.new('SvStringsSocket', "T").prop_name = 't_value' - self.outputs.new('SvMatrixSocket', "Frame") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - curves_s = self.inputs['Curve'].sv_get() - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - frames_s = self.inputs['Frames'].sv_get() - # list of frames per curve - frames_s = ensure_nesting_level(frames_s, 3, data_types=(Matrix,)) - ts_s = self.inputs['T'].sv_get() - # list of T values per curve - ts_s = ensure_nesting_level(ts_s, 3) - - tolerance = 10**(-self.accuracy) - - frames_out = [] - for curves, frames_i, ts_i in zip_long_repeat(curves_s, frames_s, ts_s): - for curve, frames, ts in zip_long_repeat(curves, frames_i, ts_i): - new_frames = interpolate_frames(curve, frames, self.z_axis, ts, - init_samples = self.samples+1, - tolerance = tolerance) - if self.join: - frames_out.extend(new_frames) - else: - frames_out.append(new_frames) - - self.outputs['Frame'].sv_set(frames_out) + + +def nearest_solution(point, solutions): + if len(solutions) == 0: + return None, None + if len(solutions) <= 1: + return solutions[0] + kdt = kdtree.KDTree(len(solutions)) + for i, solution in enumerate(solutions): + v = solution[1] + kdt.insert(v, i) + kdt.balance() + _, i, _ = kdt.find(point) + return solutions[i] + + +def matrix_to_curve(curve, matrix, z_axis, init_samples=10, tolerance=1e-3, maxiter=50): + plane = PlaneEquation.from_matrix(matrix, normal_axis=z_axis) + # Or take nearest point? + solutions = intersect_curve_plane(curve, plane, + init_samples=init_samples, + tolerance=tolerance, + maxiter=maxiter) + t, point = nearest_solution(matrix.translation, solutions) + if t is None: + raise Exception(f"Can't project the matrix {matrix} to the {curve}!") + #matrix.translation = Vector(point) + return t, matrix.to_quaternion() + + +def interpolate(tknots, ts, points, quats): + base_indexes = tknots.searchsorted(ts, side='left')-1 + t1s, t2s = tknots[base_indexes], tknots[base_indexes+1] + dts = (ts - t1s) / (t2s - t1s) + #dts = np.clip(dts, 0.0, 1.0) # Just in case... + matrix_out = [] + # TODO: ideally this should be vectorized with numpy; + # but that would require implementation of quaternion + # interpolation in numpy. + for dt, base_index, point in zip(dts, base_indexes, points): + q1, q2 = quats[base_index], quats[base_index+1] + # spherical linear interpolation. + # TODO: implement `squad`. + if dt < 0: + q = q1 + elif dt > 1.0: + q = q2 + else: + q = q1.slerp(q2, dt) + matrix = q.to_matrix().to_4x4() + matrix.translation = Vector(point) + matrix_out.append(matrix) + return matrix_out + + +def interpolate_frames(curve, frames, z_axis, ts, init_samples=10, tolerance=1e-3, maxiter=50): + quats = [] + tknots = [] + for frame in frames: + tk, quat = matrix_to_curve(curve, frame, z_axis, init_samples, tolerance, maxiter) + quats.append(quat) + tknots.append(tk) + + quats.insert(0, quats[0]) + quats.append(quats[-1]) + + tknots.insert(0, -np.inf) + tknots.append(np.inf) + tknots = np.array(tknots) + + ts = np.array(ts) + points = curve.evaluate_array(ts) + return interpolate(tknots, ts, points, quats) + + +class SvExSlerpCurveFrameNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Interpolate Curve Frame + Tooltip: Interpolate curve frames + """ + bl_idname = 'SvExSlerpCurveFrameNode' + bl_label = 'Interpolate Curve Frame' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_INTERP_FRAME' + + samples : IntProperty( + name = "Curve Resolution", + description = "A number of segments to subdivide the curve in; defines the maximum number of intersection points that is possible to find.", + default = 5, + min = 1, + update = updateNode) + + accuracy : IntProperty( + name = "Accuracy", + description = "Accuracy level - number of exact digits after decimal points.", + default = 4, + min = 1, + update = updateNode) + + t_value : FloatProperty( + name = "T", + default = 0.5, + update = updateNode) + + join : BoolProperty( + name = "Join", + description = "Output single flat list of matrices for all input curves", + default = True, + update = updateNode) + + z_axis : EnumProperty( + name = "Orientation", + description = "Which axis of the provided frames points along the curve", + items = xyz_axes, + default = 'Z', + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'z_axis', expand=True) + layout.prop(self, 'samples') + layout.prop(self, 'join', toggle=True) + + def draw_buttons_ext(self, context, layout): + layout.prop(self, 'accuracy') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + self.inputs.new('SvMatrixSocket', "Frames") + self.inputs.new('SvStringsSocket', "T").prop_name = 't_value' + self.outputs.new('SvMatrixSocket', "Frame") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curves_s = self.inputs['Curve'].sv_get() + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + frames_s = self.inputs['Frames'].sv_get() + # list of frames per curve + frames_s = ensure_nesting_level(frames_s, 3, data_types=(Matrix,)) + ts_s = self.inputs['T'].sv_get() + # list of T values per curve + ts_s = ensure_nesting_level(ts_s, 3) + + tolerance = 10**(-self.accuracy) + + frames_out = [] + for curves, frames_i, ts_i in zip_long_repeat(curves_s, frames_s, ts_s): + for curve, frames, ts in zip_long_repeat(curves, frames_i, ts_i): + new_frames = interpolate_frames(curve, frames, self.z_axis, ts, + init_samples = self.samples+1, + tolerance = tolerance) + if self.join: + frames_out.extend(new_frames) + else: + frames_out.append(new_frames) + + self.outputs['Frame'].sv_set(frames_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExSlerpCurveFrameNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExSlerpCurveFrameNode) diff --git a/nodes/curve/intersect_curve_plane.py b/nodes/curve/intersect_curve_plane.py index 68b3873b34..ab50b06054 100644 --- a/nodes/curve/intersect_curve_plane.py +++ b/nodes/curve/intersect_curve_plane.py @@ -1,129 +1,127 @@ -import numpy as np - import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty, StringProperty -from mathutils import Vector, Matrix +from bpy.props import BoolProperty, IntProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, match_long_repeat, ensure_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve from sverchok.utils.curve.nurbs import SvNurbsCurve from sverchok.utils.geom import PlaneEquation -from sverchok.utils.manifolds import intersect_curve_plane, EQUATION, ORTHO, NURBS +from sverchok.utils.manifolds import intersect_curve_plane, EQUATION, NURBS from sverchok.utils.dummy_nodes import add_dummy from sverchok.dependencies import scipy if scipy is None: add_dummy('SvExCrossCurvePlaneNode', "Intersect Curve with Plane", 'scipy') -else: - - class SvExCrossCurvePlaneNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Intersect Curve with Plane - Tooltip: Intersect Curve with Plane - """ - bl_idname = 'SvExCrossCurvePlaneNode' - bl_label = 'Intersect Curve with Plane' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_CROSS_CURVE_PLANE' - - samples : IntProperty( - name = "Init Resolution", - description = "A number of segments to subdivide the curve in; defines the maximum number of intersection points that is possible to find.", - default = 10, - min = 1, - update = updateNode) - - accuracy : IntProperty( - name = "Accuracy", - description = "Accuracy level - number of exact digits after decimal points.", - default = 4, - min = 1, - update = updateNode) - - use_nurbs : BoolProperty( - name = "NURBS", - description = "Use special algorithm for NURBS curves", - default = False, - update = updateNode) - - join : BoolProperty( - name = "Join", - description = "If checked, output one list of points for all curves; otherwise, output a separate list of points for each curve", - default = True, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'join') - layout.prop(self, 'use_nurbs') - layout.prop(self, 'samples') - - def draw_buttons_ext(self, context, layout): - layout.prop(self, 'accuracy') - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - d = self.inputs.new('SvVerticesSocket', "Point") - d.use_prop = True - d.default_proerty = (0.0, 0.0, 0.0) - d = self.inputs.new('SvVerticesSocket', "Normal") - d.use_prop = True - d.default_property = (0.0, 0.0, 1.0) - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvStringsSocket', "T") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - curves_s = self.inputs['Curve'].sv_get() - point_s = self.inputs['Point'].sv_get() - normal_s = self.inputs['Normal'].sv_get() - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - - points_out = [] - t_out = [] - - tolerance = 10**(-self.accuracy) - - for curves, points, normals in zip_long_repeat(curves_s, point_s, normal_s): - new_points = [] - new_ts = [] - for curve, point, normal in zip_long_repeat(curves, points, normals): - method = EQUATION - if self.use_nurbs: - c = SvNurbsCurve.to_nurbs(curve) - if c is not None: - curve = c - method = NURBS - - plane = PlaneEquation.from_normal_and_point(normal, point) - ps = intersect_curve_plane(curve, plane, - method = method, - init_samples = self.samples, - tolerance = tolerance) - ts = [p[0] for p in ps] - points = [p[1].tolist() for p in ps] - - if self.join: - new_points.extend(points) - new_ts.extend(ts) - else: - new_points.append(points) - new_ts.append(ts) - - points_out.append(new_points) - t_out.append(new_ts) - - self.outputs['Point'].sv_set(points_out) - self.outputs['T'].sv_set(t_out) + + +class SvExCrossCurvePlaneNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Intersect Curve with Plane + Tooltip: Intersect Curve with Plane + """ + bl_idname = 'SvExCrossCurvePlaneNode' + bl_label = 'Intersect Curve with Plane' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_CROSS_CURVE_PLANE' + + samples : IntProperty( + name = "Init Resolution", + description = "A number of segments to subdivide the curve in; defines the maximum number of intersection points that is possible to find.", + default = 10, + min = 1, + update = updateNode) + + accuracy : IntProperty( + name = "Accuracy", + description = "Accuracy level - number of exact digits after decimal points.", + default = 4, + min = 1, + update = updateNode) + + use_nurbs : BoolProperty( + name = "NURBS", + description = "Use special algorithm for NURBS curves", + default = False, + update = updateNode) + + join : BoolProperty( + name = "Join", + description = "If checked, output one list of points for all curves; otherwise, output a separate list of points for each curve", + default = True, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'join') + layout.prop(self, 'use_nurbs') + layout.prop(self, 'samples') + + def draw_buttons_ext(self, context, layout): + layout.prop(self, 'accuracy') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + d = self.inputs.new('SvVerticesSocket', "Point") + d.use_prop = True + d.default_proerty = (0.0, 0.0, 0.0) + d = self.inputs.new('SvVerticesSocket', "Normal") + d.use_prop = True + d.default_property = (0.0, 0.0, 1.0) + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvStringsSocket', "T") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curves_s = self.inputs['Curve'].sv_get() + point_s = self.inputs['Point'].sv_get() + normal_s = self.inputs['Normal'].sv_get() + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + + points_out = [] + t_out = [] + + tolerance = 10**(-self.accuracy) + + for curves, points, normals in zip_long_repeat(curves_s, point_s, normal_s): + new_points = [] + new_ts = [] + for curve, point, normal in zip_long_repeat(curves, points, normals): + method = EQUATION + if self.use_nurbs: + c = SvNurbsCurve.to_nurbs(curve) + if c is not None: + curve = c + method = NURBS + + plane = PlaneEquation.from_normal_and_point(normal, point) + ps = intersect_curve_plane(curve, plane, + method = method, + init_samples = self.samples, + tolerance = tolerance) + ts = [p[0] for p in ps] + points = [p[1].tolist() for p in ps] + + if self.join: + new_points.extend(points) + new_ts.extend(ts) + else: + new_points.append(points) + new_ts.append(ts) + + points_out.append(new_points) + t_out.append(new_ts) + + self.outputs['Point'].sv_set(points_out) + self.outputs['T'].sv_set(t_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExCrossCurvePlaneNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExCrossCurvePlaneNode) diff --git a/nodes/curve/marching_squares.py b/nodes/curve/marching_squares.py index 4e894de1b5..b47e7124f8 100644 --- a/nodes/curve/marching_squares.py +++ b/nodes/curve/marching_squares.py @@ -17,185 +17,187 @@ else: from skimage import measure - class SvExMarchingSquaresNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Marching Squares - Tooltip: Marching Squares - """ - bl_idname = 'SvExMarchingSquaresNode' - bl_label = 'Marching Squares' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_EX_MSQUARES' - - iso_value : FloatProperty( - name = "Value", - default = 1.0, - update = updateNode) - - sample_size : IntProperty( - name = "Samples", - default = 50, - min = 4, - update = updateNode) - - z_value : FloatProperty( - name = "Z", - default = 0.0, - update = updateNode) - - min_x : FloatProperty( - name = "Min X", - default = -1.0, - update = updateNode) - - max_x : FloatProperty( - name = "Max X", - default = 1.0, - update = updateNode) - - min_y : FloatProperty( - name = "Min Y", - default = -1.0, - update = updateNode) - - max_y : FloatProperty( - name = "Max Y", - default = 1.0, - update = updateNode) - - def update_sockets(self, context): - self.outputs['Faces'].hide_safe = not self.make_faces - updateNode(self, context) - - make_faces : BoolProperty( - name = "Make faces", - default = False, - update = update_sockets) - - connect_bounds : BoolProperty( - name = "Connect boundary", - default = True, - update = updateNode) - - join : BoolProperty( - name = "Flat output", - description = "If checked, generate one flat list of objects for all input iso values. Otherwise, generate a separate list of objects for each input iso value.", - default = True, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvScalarFieldSocket', "Field") - self.inputs.new('SvStringsSocket', "Value").prop_name = 'iso_value' - self.inputs.new('SvStringsSocket', "Samples").prop_name = 'sample_size' - self.inputs.new('SvStringsSocket', "MinX").prop_name = 'min_x' - self.inputs.new('SvStringsSocket', "MaxX").prop_name = 'max_x' - self.inputs.new('SvStringsSocket', "MinY").prop_name = 'min_y' - self.inputs.new('SvStringsSocket', "MaxY").prop_name = 'max_y' - self.inputs.new('SvStringsSocket', "Z").prop_name = 'z_value' - self.inputs.new('SvMatrixSocket', "Matrix") - self.outputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Edges") - self.outputs.new('SvStringsSocket', "Faces") - self.update_sockets(context) - - def draw_buttons(self, context, layout): - layout.prop(self, 'join') - layout.prop(self, 'make_faces') - layout.prop(self, 'connect_bounds') - - def apply_matrix(self, matrix, xs, ys, zs): - matrix = matrix.inverted() + +class SvExMarchingSquaresNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Marching Squares + Tooltip: Marching Squares + """ + bl_idname = 'SvExMarchingSquaresNode' + bl_label = 'Marching Squares' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_EX_MSQUARES' + + iso_value : FloatProperty( + name = "Value", + default = 1.0, + update = updateNode) + + sample_size : IntProperty( + name = "Samples", + default = 50, + min = 4, + update = updateNode) + + z_value : FloatProperty( + name = "Z", + default = 0.0, + update = updateNode) + + min_x : FloatProperty( + name = "Min X", + default = -1.0, + update = updateNode) + + max_x : FloatProperty( + name = "Max X", + default = 1.0, + update = updateNode) + + min_y : FloatProperty( + name = "Min Y", + default = -1.0, + update = updateNode) + + max_y : FloatProperty( + name = "Max Y", + default = 1.0, + update = updateNode) + + def update_sockets(self, context): + self.outputs['Faces'].hide_safe = not self.make_faces + updateNode(self, context) + + make_faces : BoolProperty( + name = "Make faces", + default = False, + update = update_sockets) + + connect_bounds : BoolProperty( + name = "Connect boundary", + default = True, + update = updateNode) + + join : BoolProperty( + name = "Flat output", + description = "If checked, generate one flat list of objects for all input iso values. Otherwise, generate a separate list of objects for each input iso value.", + default = True, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvScalarFieldSocket', "Field") + self.inputs.new('SvStringsSocket', "Value").prop_name = 'iso_value' + self.inputs.new('SvStringsSocket', "Samples").prop_name = 'sample_size' + self.inputs.new('SvStringsSocket', "MinX").prop_name = 'min_x' + self.inputs.new('SvStringsSocket', "MaxX").prop_name = 'max_x' + self.inputs.new('SvStringsSocket', "MinY").prop_name = 'min_y' + self.inputs.new('SvStringsSocket', "MaxY").prop_name = 'max_y' + self.inputs.new('SvStringsSocket', "Z").prop_name = 'z_value' + self.inputs.new('SvMatrixSocket', "Matrix") + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvStringsSocket', "Faces") + self.update_sockets(context) + + def draw_buttons(self, context, layout): + layout.prop(self, 'join') + layout.prop(self, 'make_faces') + layout.prop(self, 'connect_bounds') + + def apply_matrix(self, matrix, xs, ys, zs): + matrix = matrix.inverted() + m = np.array(matrix.to_3x3()) + t = np.array(matrix.translation) + points = np.stack((xs, ys, zs)).T + points = np.apply_along_axis(lambda v: m @ v + t,1, points).T + return points[0], points[1], points[2] + + def unapply_matrix(self, matrix, verts_s): + def unapply(verts): m = np.array(matrix.to_3x3()) t = np.array(matrix.translation) - points = np.stack((xs, ys, zs)).T - points = np.apply_along_axis(lambda v: m @ v + t,1, points).T - return points[0], points[1], points[2] - - def unapply_matrix(self, matrix, verts_s): - def unapply(verts): - m = np.array(matrix.to_3x3()) - t = np.array(matrix.translation) - points = np.array(verts) - points = np.apply_along_axis(lambda v: m @ v + t,1, points) - return points.tolist() - return list(map(unapply, verts_s)) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - fields_s = self.inputs['Field'].sv_get() - min_x_s = self.inputs['MinX'].sv_get() - max_x_s = self.inputs['MaxX'].sv_get() - min_y_s = self.inputs['MinY'].sv_get() - max_y_s = self.inputs['MaxY'].sv_get() - value_s = self.inputs['Value'].sv_get() - z_value_s = self.inputs['Z'].sv_get() - samples_s = self.inputs['Samples'].sv_get() - matrix_s = self.inputs['Matrix'].sv_get(default=[Matrix()]) - - value_s = ensure_nesting_level(value_s, 2) - z_value_s = ensure_nesting_level(z_value_s, 2) - input_level = get_data_nesting_level(fields_s, data_types=(SvScalarField,)) - nested_output = input_level > 1 - fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) - matrix_s = ensure_nesting_level(matrix_s, 2, data_types=(Matrix,)) - - parameters = zip_long_repeat(fields_s, matrix_s, min_x_s, max_x_s, min_y_s, max_y_s, z_value_s, value_s, samples_s) - - verts_out = [] - edges_out = [] - faces_out = [] - for field_i, matrix_i, min_x_i, max_x_i, min_y_i, max_y_i, z_value_i, value_i, samples_i in parameters: - new_verts = [] - new_edges = [] - new_faces = [] - objects = zip_long_repeat(field_i, matrix_i, min_x_i, max_x_i, - min_y_i, max_y_i, z_value_i, value_i, samples_i) - for field, matrix, min_x, max_x, min_y, max_y, z_value, value, samples in objects: - - has_matrix = matrix != Matrix() - - x_range = np.linspace(min_x, max_x, num=samples) - y_range = np.linspace(min_y, max_y, num=samples) - z_range = np.array([z_value]) - xs, ys, zs = np.meshgrid(x_range, y_range, z_range, indexing='ij') - xs, ys, zs = xs.flatten(), ys.flatten(), zs.flatten() - if has_matrix: - xs, ys, zs = self.apply_matrix(matrix, xs, ys, zs) - field_values = field.evaluate_grid(xs, ys, zs) - field_values = field_values.reshape((samples, samples)) - - contours = measure.find_contours(field_values, level=value) - - x_size = (max_x - min_x)/samples - y_size = (max_y - min_y)/samples - - value_verts, value_edges, value_faces = make_contours(samples, samples, min_x, x_size, min_y, y_size, z_value, contours, make_faces=self.make_faces, connect_bounds = self.connect_bounds) - if has_matrix: - new_verts = self.unapply_matrix(matrix, new_verts) - - if self.join: - new_verts.extend(value_verts) - new_edges.extend(value_edges) - new_faces.extend(value_faces) - else: - new_verts.append(value_verts) - new_edges.append(value_edges) - new_faces.append(value_faces) - - if nested_output: - verts_out.append(new_verts) - edges_out.append(new_edges) - faces_out.append(new_faces) + points = np.array(verts) + points = np.apply_along_axis(lambda v: m @ v + t,1, points) + return points.tolist() + return list(map(unapply, verts_s)) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + fields_s = self.inputs['Field'].sv_get() + min_x_s = self.inputs['MinX'].sv_get() + max_x_s = self.inputs['MaxX'].sv_get() + min_y_s = self.inputs['MinY'].sv_get() + max_y_s = self.inputs['MaxY'].sv_get() + value_s = self.inputs['Value'].sv_get() + z_value_s = self.inputs['Z'].sv_get() + samples_s = self.inputs['Samples'].sv_get() + matrix_s = self.inputs['Matrix'].sv_get(default=[Matrix()]) + + value_s = ensure_nesting_level(value_s, 2) + z_value_s = ensure_nesting_level(z_value_s, 2) + input_level = get_data_nesting_level(fields_s, data_types=(SvScalarField,)) + nested_output = input_level > 1 + fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) + matrix_s = ensure_nesting_level(matrix_s, 2, data_types=(Matrix,)) + + parameters = zip_long_repeat(fields_s, matrix_s, min_x_s, max_x_s, min_y_s, max_y_s, z_value_s, value_s, samples_s) + + verts_out = [] + edges_out = [] + faces_out = [] + for field_i, matrix_i, min_x_i, max_x_i, min_y_i, max_y_i, z_value_i, value_i, samples_i in parameters: + new_verts = [] + new_edges = [] + new_faces = [] + objects = zip_long_repeat(field_i, matrix_i, min_x_i, max_x_i, + min_y_i, max_y_i, z_value_i, value_i, samples_i) + for field, matrix, min_x, max_x, min_y, max_y, z_value, value, samples in objects: + + has_matrix = matrix != Matrix() + + x_range = np.linspace(min_x, max_x, num=samples) + y_range = np.linspace(min_y, max_y, num=samples) + z_range = np.array([z_value]) + xs, ys, zs = np.meshgrid(x_range, y_range, z_range, indexing='ij') + xs, ys, zs = xs.flatten(), ys.flatten(), zs.flatten() + if has_matrix: + xs, ys, zs = self.apply_matrix(matrix, xs, ys, zs) + field_values = field.evaluate_grid(xs, ys, zs) + field_values = field_values.reshape((samples, samples)) + + contours = measure.find_contours(field_values, level=value) + + x_size = (max_x - min_x)/samples + y_size = (max_y - min_y)/samples + + value_verts, value_edges, value_faces = make_contours(samples, samples, min_x, x_size, min_y, y_size, z_value, contours, make_faces=self.make_faces, connect_bounds = self.connect_bounds) + if has_matrix: + new_verts = self.unapply_matrix(matrix, new_verts) + + if self.join: + new_verts.extend(value_verts) + new_edges.extend(value_edges) + new_faces.extend(value_faces) else: - verts_out.extend(new_verts) - edges_out.extend(new_edges) - faces_out.extend(new_faces) + new_verts.append(value_verts) + new_edges.append(value_edges) + new_faces.append(value_faces) + + if nested_output: + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + else: + verts_out.extend(new_verts) + edges_out.extend(new_edges) + faces_out.extend(new_faces) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['Faces'].sv_set(faces_out) - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Edges'].sv_set(edges_out) - self.outputs['Faces'].sv_set(faces_out) def register(): if skimage is not None: diff --git a/nodes/curve/marching_squares_on_surface.py b/nodes/curve/marching_squares_on_surface.py index 6bceb7b7b3..6de23fcdb8 100644 --- a/nodes/curve/marching_squares_on_surface.py +++ b/nodes/curve/marching_squares_on_surface.py @@ -20,134 +20,136 @@ else: from skimage import measure - class SvExMSquaresOnSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Marching Squares on Surface - Tooltip: Marching Squares on Surface - """ - bl_idname = 'SvExMSquaresOnSurfaceNode' - bl_label = 'Marching Squares on Surface' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_EX_MSQUARES' - - iso_value : FloatProperty( - name = "Value", - default = 1.0, - update = updateNode) - - samples_u : IntProperty( - name = "Samples U", - default = 50, - min = 4, - update = updateNode) - - samples_v : IntProperty( - name = "Samples V", - default = 50, - min = 4, - update = updateNode) - - connect_bounds : BoolProperty( - name = "Connect boundary", - default = True, - update = updateNode) - - join : BoolProperty( - name = "Join by Surface", - description = "Output single list of vertices / edges for all input surfaces", - default = True, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvScalarFieldSocket', "Field") - self.inputs.new('SvSurfaceSocket', "Surface") - self.inputs.new('SvStringsSocket', "Value").prop_name = 'iso_value' - self.inputs.new('SvStringsSocket', "SamplesU").prop_name = 'samples_u' - self.inputs.new('SvStringsSocket', "SamplesV").prop_name = 'samples_v' - self.outputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Edges") - self.outputs.new('SvVerticesSocket', "UVVertices") - - def draw_buttons(self, context, layout): - layout.prop(self, 'join', toggle=True) - layout.prop(self, 'connect_bounds', toggle=True) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - fields_s = self.inputs['Field'].sv_get() - surface_s = self.inputs['Surface'].sv_get() - samples_u_s = self.inputs['SamplesU'].sv_get() - samples_v_s = self.inputs['SamplesV'].sv_get() - value_s = self.inputs['Value'].sv_get() - - fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) - surface_s = ensure_nesting_level(surface_s, 2, data_types=(SvSurface,)) - samples_u_s = ensure_nesting_level(samples_u_s, 2) - samples_v_s = ensure_nesting_level(samples_v_s, 2) - value_s = ensure_nesting_level(value_s, 2) - - verts_out = [] - edges_out = [] - uv_verts_out = [] - for field_i, surface_i, samples_u_i, samples_v_i, value_i in zip_long_repeat(fields_s, surface_s, samples_u_s, samples_v_s, value_s): - for field, surface, samples_u, samples_v, value in zip_long_repeat(field_i, surface_i, samples_u_i, samples_v_i, value_i): - surface_verts = [] - surface_uv = [] - surface_edges = [] - - u_min, u_max = surface.get_u_min(), surface.get_u_max() - v_min, v_max = surface.get_v_min(), surface.get_v_max() - - u_range = np.linspace(u_min, u_max, num=samples_u) - v_range = np.linspace(v_min, v_max, num=samples_v) - - us, vs = np.meshgrid(u_range, v_range, indexing='ij') - us, vs = us.flatten(), vs.flatten() - - surface_points = surface.evaluate_array(us, vs) - xs = surface_points[:,0] - ys = surface_points[:,1] - zs = surface_points[:,2] - - field_values = field.evaluate_grid(xs, ys, zs) - field_values = field_values.reshape((samples_u, samples_v)) - - contours = measure.find_contours(field_values, level=value) - - u_size = (u_max - u_min)/samples_u - v_size = (v_max - v_min)/samples_v - - uv_contours, new_edges, _ = make_contours(samples_u, samples_v, u_min, u_size, v_min, v_size, 0, contours, make_faces=True, connect_bounds = self.connect_bounds) - - if uv_contours: - for uv_points in uv_contours: - us = np.array([p[0] for p in uv_points]) - vs = np.array([p[1] for p in uv_points]) - - new_verts = surface.evaluate_array(us, vs).tolist() - - surface_uv.append(uv_points) - surface_verts.append(new_verts) - surface_edges.extend(new_edges) - - if self.join: - surface_verts, surface_edges, _ = mesh_join(surface_verts, surface_edges, [[]]*len(surface_edges)) - surface_uv = sum(surface_uv, []) - - verts_out.append(surface_verts) - uv_verts_out.append(surface_uv) - edges_out.append(surface_edges) - else: - verts_out.append([]) - uv_verts_out.append([]) - edges_out.append([]) - - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Edges'].sv_set(edges_out) - if 'UVVertices' in self.outputs: - self.outputs['UVVertices'].sv_set(uv_verts_out) + +class SvExMSquaresOnSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Marching Squares on Surface + Tooltip: Marching Squares on Surface + """ + bl_idname = 'SvExMSquaresOnSurfaceNode' + bl_label = 'Marching Squares on Surface' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_EX_MSQUARES' + + iso_value : FloatProperty( + name = "Value", + default = 1.0, + update = updateNode) + + samples_u : IntProperty( + name = "Samples U", + default = 50, + min = 4, + update = updateNode) + + samples_v : IntProperty( + name = "Samples V", + default = 50, + min = 4, + update = updateNode) + + connect_bounds : BoolProperty( + name = "Connect boundary", + default = True, + update = updateNode) + + join : BoolProperty( + name = "Join by Surface", + description = "Output single list of vertices / edges for all input surfaces", + default = True, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvScalarFieldSocket', "Field") + self.inputs.new('SvSurfaceSocket', "Surface") + self.inputs.new('SvStringsSocket', "Value").prop_name = 'iso_value' + self.inputs.new('SvStringsSocket', "SamplesU").prop_name = 'samples_u' + self.inputs.new('SvStringsSocket', "SamplesV").prop_name = 'samples_v' + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvVerticesSocket', "UVVertices") + + def draw_buttons(self, context, layout): + layout.prop(self, 'join', toggle=True) + layout.prop(self, 'connect_bounds', toggle=True) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + fields_s = self.inputs['Field'].sv_get() + surface_s = self.inputs['Surface'].sv_get() + samples_u_s = self.inputs['SamplesU'].sv_get() + samples_v_s = self.inputs['SamplesV'].sv_get() + value_s = self.inputs['Value'].sv_get() + + fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) + surface_s = ensure_nesting_level(surface_s, 2, data_types=(SvSurface,)) + samples_u_s = ensure_nesting_level(samples_u_s, 2) + samples_v_s = ensure_nesting_level(samples_v_s, 2) + value_s = ensure_nesting_level(value_s, 2) + + verts_out = [] + edges_out = [] + uv_verts_out = [] + for field_i, surface_i, samples_u_i, samples_v_i, value_i in zip_long_repeat(fields_s, surface_s, samples_u_s, samples_v_s, value_s): + for field, surface, samples_u, samples_v, value in zip_long_repeat(field_i, surface_i, samples_u_i, samples_v_i, value_i): + surface_verts = [] + surface_uv = [] + surface_edges = [] + + u_min, u_max = surface.get_u_min(), surface.get_u_max() + v_min, v_max = surface.get_v_min(), surface.get_v_max() + + u_range = np.linspace(u_min, u_max, num=samples_u) + v_range = np.linspace(v_min, v_max, num=samples_v) + + us, vs = np.meshgrid(u_range, v_range, indexing='ij') + us, vs = us.flatten(), vs.flatten() + + surface_points = surface.evaluate_array(us, vs) + xs = surface_points[:,0] + ys = surface_points[:,1] + zs = surface_points[:,2] + + field_values = field.evaluate_grid(xs, ys, zs) + field_values = field_values.reshape((samples_u, samples_v)) + + contours = measure.find_contours(field_values, level=value) + + u_size = (u_max - u_min)/samples_u + v_size = (v_max - v_min)/samples_v + + uv_contours, new_edges, _ = make_contours(samples_u, samples_v, u_min, u_size, v_min, v_size, 0, contours, make_faces=True, connect_bounds = self.connect_bounds) + + if uv_contours: + for uv_points in uv_contours: + us = np.array([p[0] for p in uv_points]) + vs = np.array([p[1] for p in uv_points]) + + new_verts = surface.evaluate_array(us, vs).tolist() + + surface_uv.append(uv_points) + surface_verts.append(new_verts) + surface_edges.extend(new_edges) + + if self.join: + surface_verts, surface_edges, _ = mesh_join(surface_verts, surface_edges, [[]]*len(surface_edges)) + surface_uv = sum(surface_uv, []) + + verts_out.append(surface_verts) + uv_verts_out.append(surface_uv) + edges_out.append(surface_edges) + else: + verts_out.append([]) + uv_verts_out.append([]) + edges_out.append([]) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + if 'UVVertices' in self.outputs: + self.outputs['UVVertices'].sv_set(uv_verts_out) + def register(): if skimage is not None: diff --git a/nodes/curve/nearest_point.py b/nodes/curve/nearest_point.py index 68782262b9..2b1e56233d 100644 --- a/nodes/curve/nearest_point.py +++ b/nodes/curve/nearest_point.py @@ -1,14 +1,9 @@ -import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -from mathutils import Matrix -from mathutils.kdtree import KDTree +from bpy.props import EnumProperty, BoolProperty, IntProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve from sverchok.utils.manifolds import nearest_point_on_curve from sverchok.utils.dummy_nodes import add_dummy @@ -16,94 +11,96 @@ if scipy is None: add_dummy('SvExNearestPointOnCurveNode', "Nearest Point on Curve", 'scipy') -else: - - class SvExNearestPointOnCurveNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Nearest Point on Curve - Tooltip: Find the point on the curve which is the nearest to the given point - """ - bl_idname = 'SvExNearestPointOnCurveNode' - bl_label = 'Nearest Point on Curve' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_NEAREST_CURVE' - - samples : IntProperty( - name = "Init Resolution", - default = 50, - min = 3, - update = updateNode) - - precise : BoolProperty( - name = "Precise", - default = True, - update = updateNode) - - solvers = [ - ('Brent', "Brent", "Uses inverse parabolic interpolation when possible to speed up convergence of golden section method", 0), - ('Bounded', "Bounded", "Uses the Brent method to find a local minimum in the interval", 1), - ('Golden', 'Golden Section', "Uses the golden section search technique", 2) - ] - - method : EnumProperty( - name = "Method", - description = "Solver method to use; select the one which works for your case", - items = solvers, - default = 'Brent', - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'samples') - layout.prop(self, 'precise', toggle=True) - - def draw_buttons_ext(self, context, layout): - layout.prop(self, 'method') - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - p = self.inputs.new('SvVerticesSocket', "Point") - p.use_prop = True - p.default_property = (0.0, 0.0, 0.0) - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvStringsSocket', "T") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - curves_s = self.inputs['Curve'].sv_get() - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - src_point_s = self.inputs['Point'].sv_get() - src_point_s = ensure_nesting_level(src_point_s, 4) - - points_out = [] - t_out = [] - need_points = self.outputs['Point'].is_linked - for curves, src_points_i in zip_long_repeat(curves_s, src_point_s): - for curve, src_points in zip_long_repeat(curves, src_points_i): - - results = nearest_point_on_curve(src_points, curve, - samples=self.samples, precise=self.precise, - output_points = need_points, - method = self.method, - logger = self.get_logger()) - if need_points: - new_t = [r[0] for r in results] - new_points = [r[1].tolist() for r in results] - else: - new_t = results - new_points = [] - - points_out.append(new_points) - t_out.append(new_t) - - self.outputs['Point'].sv_set(points_out) - self.outputs['T'].sv_set(t_out) + + +class SvExNearestPointOnCurveNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Nearest Point on Curve + Tooltip: Find the point on the curve which is the nearest to the given point + """ + bl_idname = 'SvExNearestPointOnCurveNode' + bl_label = 'Nearest Point on Curve' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_NEAREST_CURVE' + + samples : IntProperty( + name = "Init Resolution", + default = 50, + min = 3, + update = updateNode) + + precise : BoolProperty( + name = "Precise", + default = True, + update = updateNode) + + solvers = [ + ('Brent', "Brent", "Uses inverse parabolic interpolation when possible to speed up convergence of golden section method", 0), + ('Bounded', "Bounded", "Uses the Brent method to find a local minimum in the interval", 1), + ('Golden', 'Golden Section', "Uses the golden section search technique", 2) + ] + + method : EnumProperty( + name = "Method", + description = "Solver method to use; select the one which works for your case", + items = solvers, + default = 'Brent', + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'samples') + layout.prop(self, 'precise', toggle=True) + + def draw_buttons_ext(self, context, layout): + layout.prop(self, 'method') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + p = self.inputs.new('SvVerticesSocket', "Point") + p.use_prop = True + p.default_property = (0.0, 0.0, 0.0) + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvStringsSocket', "T") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curves_s = self.inputs['Curve'].sv_get() + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + src_point_s = self.inputs['Point'].sv_get() + src_point_s = ensure_nesting_level(src_point_s, 4) + + points_out = [] + t_out = [] + need_points = self.outputs['Point'].is_linked + for curves, src_points_i in zip_long_repeat(curves_s, src_point_s): + for curve, src_points in zip_long_repeat(curves, src_points_i): + + results = nearest_point_on_curve(src_points, curve, + samples=self.samples, precise=self.precise, + output_points = need_points, + method = self.method, + logger = self.get_logger()) + if need_points: + new_t = [r[0] for r in results] + new_points = [r[1].tolist() for r in results] + else: + new_t = results + new_points = [] + + points_out.append(new_points) + t_out.append(new_t) + + self.outputs['Point'].sv_set(points_out) + self.outputs['T'].sv_set(t_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExNearestPointOnCurveNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExNearestPointOnCurveNode) diff --git a/nodes/curve/ortho_project.py b/nodes/curve/ortho_project.py index 7689c4cc47..263e6f3c2e 100644 --- a/nodes/curve/ortho_project.py +++ b/nodes/curve/ortho_project.py @@ -2,12 +2,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty +from bpy.props import BoolProperty, IntProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve from sverchok.utils.dummy_nodes import add_dummy from sverchok.dependencies import scipy @@ -15,81 +13,83 @@ if scipy is None: add_dummy('SvExOrthoProjectCurveNode', "Ortho Project on Curve", 'scipy') -else: - - class SvExOrthoProjectCurveNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Ortho Project Curve - Tooltip: Find the orthogonal projection of the point onto the curve - """ - bl_idname = 'SvExOrthoProjectCurveNode' - bl_label = 'Ortho Project on Curve' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_ORTHO_CURVE' - - samples : IntProperty( - name = "Init Resolution", - default = 5, - min = 3, - update = updateNode) - - nearest : BoolProperty( - name = "Nearest", - description = "To find the nearest orthogonal projection or all of them", - default = True, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'nearest', toggle=True) - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - layout.prop(self, 'samples') - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - p = self.inputs.new('SvVerticesSocket', "Point") - p.use_prop = True - p.default_property = (0.0, 0.0, 0.0) - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvStringsSocket', "T") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - curves_s = self.inputs['Curve'].sv_get() - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - src_point_s = self.inputs['Point'].sv_get() - src_point_s = ensure_nesting_level(src_point_s, 4) - - points_out = [] - t_out = [] - for curves, src_points_i in zip_long_repeat(curves_s, src_point_s): - for curve, src_points in zip_long_repeat(curves, src_points_i): - new_points = [] - new_t = [] - for src_point in src_points: - src_point = np.array(src_point) - result = ortho_project_curve(src_point, curve, init_samples = self.samples) - if self.nearest: - t = result.nearest_u - point = result.nearest.tolist() - new_t.append(t) - new_points.append(point) - else: - new_t.extend(result.us) - new_points.extend(result.points) - points_out.append(new_points) - t_out.append(new_t) - - self.outputs['Point'].sv_set(points_out) - self.outputs['T'].sv_set(t_out) + + +class SvExOrthoProjectCurveNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Ortho Project Curve + Tooltip: Find the orthogonal projection of the point onto the curve + """ + bl_idname = 'SvExOrthoProjectCurveNode' + bl_label = 'Ortho Project on Curve' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_ORTHO_CURVE' + + samples : IntProperty( + name = "Init Resolution", + default = 5, + min = 3, + update = updateNode) + + nearest : BoolProperty( + name = "Nearest", + description = "To find the nearest orthogonal projection or all of them", + default = True, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'nearest', toggle=True) + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'samples') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + p = self.inputs.new('SvVerticesSocket', "Point") + p.use_prop = True + p.default_property = (0.0, 0.0, 0.0) + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvStringsSocket', "T") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curves_s = self.inputs['Curve'].sv_get() + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + src_point_s = self.inputs['Point'].sv_get() + src_point_s = ensure_nesting_level(src_point_s, 4) + + points_out = [] + t_out = [] + for curves, src_points_i in zip_long_repeat(curves_s, src_point_s): + for curve, src_points in zip_long_repeat(curves, src_points_i): + new_points = [] + new_t = [] + for src_point in src_points: + src_point = np.array(src_point) + result = ortho_project_curve(src_point, curve, init_samples = self.samples) + if self.nearest: + t = result.nearest_u + point = result.nearest.tolist() + new_t.append(t) + new_points.append(point) + else: + new_t.extend(result.us) + new_points.extend(result.points) + points_out.append(new_points) + t_out.append(new_t) + + self.outputs['Point'].sv_set(points_out) + self.outputs['T'].sv_set(t_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExOrthoProjectCurveNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExOrthoProjectCurveNode) diff --git a/nodes/curve/rbf_curve.py b/nodes/curve/rbf_curve.py index d4c7ea5313..627230d447 100644 --- a/nodes/curve/rbf_curve.py +++ b/nodes/curve/rbf_curve.py @@ -2,11 +2,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty +from bpy.props import FloatProperty, EnumProperty from sverchok.node_tree import SverchCustomTreeNode from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level -from sverchok.utils.logging import info, exception from sverchok.utils.curve import make_euclidean_ts from sverchok.utils.curve.rbf import SvRbfCurve from sverchok.utils.dummy_nodes import add_dummy @@ -18,74 +17,77 @@ else: from scipy.interpolate import Rbf - class SvExRbfCurveNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Minimal RBF Curve - Tooltip: Generate interpolating or approximating curve by RBF method - """ - bl_idname = 'SvExRbfCurveNode' - bl_label = 'RBF Curve' - bl_icon = 'CURVE_NCURVE' - sv_icon = 'SV_INTERP_CURVE' - - function : EnumProperty( - name = "Function", - items = rbf_functions, - default = 'multiquadric', - update = updateNode) - - smooth : FloatProperty( - name = "Smooth", - default = 0.0, - min = 0.0, - update = updateNode) - - epsilon : FloatProperty( - name = "Epsilon", - default = 1.0, - min = 0.0, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, "function") - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' - self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' - self.outputs.new('SvCurveSocket', "Curve") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - vertices_s = ensure_nesting_level(vertices_s, 3) - epsilon_s = self.inputs['Epsilon'].sv_get() - smooth_s = self.inputs['Smooth'].sv_get() - - curves_out = [] - for vertices, epsilon, smooth in zip_long_repeat(vertices_s, epsilon_s, smooth_s): - if isinstance(epsilon, (list, int)): - epsilon = epsilon[0] - if isinstance(smooth, (list, int)): - smooth = smooth[0] - - vertices = np.array(vertices) - ts = make_euclidean_ts(vertices) - rbf = Rbf(ts, vertices, - function=self.function, - smooth=smooth, - epsilon=epsilon, mode='N-D') - curve = SvRbfCurve(rbf, (0.0, 1.0)) - curves_out.append(curve) - - self.outputs['Curve'].sv_set(curves_out) + +class SvExRbfCurveNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Minimal RBF Curve + Tooltip: Generate interpolating or approximating curve by RBF method + """ + bl_idname = 'SvExRbfCurveNode' + bl_label = 'RBF Curve' + bl_icon = 'CURVE_NCURVE' + sv_icon = 'SV_INTERP_CURVE' + + function : EnumProperty( + name = "Function", + items = rbf_functions, + default = 'multiquadric', + update = updateNode) + + smooth : FloatProperty( + name = "Smooth", + default = 0.0, + min = 0.0, + update = updateNode) + + epsilon : FloatProperty( + name = "Epsilon", + default = 1.0, + min = 0.0, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "function") + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' + self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' + self.outputs.new('SvCurveSocket', "Curve") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + vertices_s = ensure_nesting_level(vertices_s, 3) + epsilon_s = self.inputs['Epsilon'].sv_get() + smooth_s = self.inputs['Smooth'].sv_get() + + curves_out = [] + for vertices, epsilon, smooth in zip_long_repeat(vertices_s, epsilon_s, smooth_s): + if isinstance(epsilon, (list, int)): + epsilon = epsilon[0] + if isinstance(smooth, (list, int)): + smooth = smooth[0] + + vertices = np.array(vertices) + ts = make_euclidean_ts(vertices) + rbf = Rbf(ts, vertices, + function=self.function, + smooth=smooth, + epsilon=epsilon, mode='N-D') + curve = SvRbfCurve(rbf, (0.0, 1.0)) + curves_out.append(curve) + + self.outputs['Curve'].sv_set(curves_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExRbfCurveNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExRbfCurveNode) diff --git a/nodes/exchange/FCStd_read.py b/nodes/exchange/FCStd_read.py index c61ee0afd6..254da395cb 100644 --- a/nodes/exchange/FCStd_read.py +++ b/nodes/exchange/FCStd_read.py @@ -1,4 +1,9 @@ +import bpy +from bpy.props import StringProperty, BoolProperty, EnumProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.utils.logging import info from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator @@ -8,208 +13,202 @@ else: F = FreeCAD - import bpy - from bpy.props import StringProperty, BoolProperty, EnumProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode - from sverchok.utils.logging import info - - class SvReadFCStdOperator(bpy.types.Operator, SvGenericNodeLocator): - - bl_idname = "node.sv_read_fcstd_operator" - bl_label = "read freecad file" - bl_options = {'INTERNAL', 'REGISTER'} - - def execute(self, context): - node = self.get_node(context) - - if not node: return {'CANCELLED'} - - if not any(socket.is_linked for socket in node.outputs): - return {'CANCELLED'} - if not node.inputs['File Path'].is_linked: - return {'CANCELLED'} - - node.read_FCStd(node) - updateNode(node,context) - - return {'FINISHED'} - - class SvReadFCStdNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Read FreeCAD file - Tooltip: import parts from a .FCStd file - """ - bl_idname = 'SvReadFCStdNode' - bl_label = 'Read FCStd' - bl_icon = 'IMPORT' - solid_catergory = "Outputs" - - read_update : BoolProperty(name="read_update", default=True) - read_body : BoolProperty(name="read_body", default=True, update = updateNode) - read_part : BoolProperty(name="read_part", default=True, update = updateNode) - - tool_parts : BoolProperty(name="tool_parts", default=False, update = updateNode) - read_features : BoolProperty(name="read_features", default=False, update = updateNode) - - inv_filter : BoolProperty(name="inv_filter", default=False, update = updateNode) - - selected_label : StringProperty( default= 'Select FC Part') - selected_part : StringProperty( default='', update = updateNode) - - def draw_buttons(self, context, layout): - - col = layout.column(align=True) - if self.inputs['File Path'].is_linked: - self.wrapper_tracked_ui_draw_op( - col, SvShowFcstdNamesOp.bl_idname, - icon= 'TRIA_DOWN', - text= self.selected_label ) - col.prop(self, 'read_update', text = 'global update') - col.prop(self, 'read_body') - col.prop(self, 'read_part') - col.prop(self, 'tool_parts') - if self.tool_parts: - col.prop(self, 'read_features') - col.prop(self, 'inv_filter') - self.wrapper_tracked_ui_draw_op(layout, SvReadFCStdOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") - - def sv_init(self, context): - - self.inputs.new('SvFilePathSocket', "File Path") - self.inputs.new('SvStringsSocket', "Part Filter") - self.outputs.new('SvSolidSocket', "Solid") + + +class SvReadFCStdOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_read_fcstd_operator" + bl_label = "read freecad file" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + + if not node: return {'CANCELLED'} + + if not any(socket.is_linked for socket in node.outputs): + return {'CANCELLED'} + if not node.inputs['File Path'].is_linked: + return {'CANCELLED'} + + node.read_FCStd(node) + updateNode(node,context) + + return {'FINISHED'} + + +class SvReadFCStdNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Read FreeCAD file + Tooltip: import parts from a .FCStd file + """ + bl_idname = 'SvReadFCStdNode' + bl_label = 'Read FCStd' + bl_icon = 'IMPORT' + sv_category = "Solid Outputs" + + read_update : BoolProperty(name="read_update", default=True) + read_body : BoolProperty(name="read_body", default=True, update = updateNode) + read_part : BoolProperty(name="read_part", default=True, update = updateNode) + + tool_parts : BoolProperty(name="tool_parts", default=False, update = updateNode) + read_features : BoolProperty(name="read_features", default=False, update = updateNode) + + inv_filter : BoolProperty(name="inv_filter", default=False, update = updateNode) + + selected_label : StringProperty( default= 'Select FC Part') + selected_part : StringProperty( default='', update = updateNode) + + def draw_buttons(self, context, layout): + + col = layout.column(align=True) + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, SvShowFcstdNamesOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_label ) + col.prop(self, 'read_update', text = 'global update') + col.prop(self, 'read_body') + col.prop(self, 'read_part') + col.prop(self, 'tool_parts') + if self.tool_parts: + col.prop(self, 'read_features') + col.prop(self, 'inv_filter') + self.wrapper_tracked_ui_draw_op(layout, SvReadFCStdOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "Part Filter") + self.outputs.new('SvSolidSocket', "Solid") + self.outputs.new('SvTextSocket', "Names") + + def ensure_name_socket_exists(self): + if not "Names" in self.outputs: self.outputs.new('SvTextSocket', "Names") - def ensure_name_socket_exists(self): - if not "Names" in self.outputs: - self.outputs.new('SvTextSocket', "Names") - - def read_FCStd(self, node): - - files = node.inputs['File Path'].sv_get()[0] - - part_filter = [] - if node.inputs['Part Filter'].is_linked: - part_filter = node.inputs['Part Filter'].sv_get()[0] - - if node.selected_part != '' and not node.selected_part in part_filter: - part_filter.append(node.selected_part) - - solids = [] - obj_mask = [] - names = [] - - if node.read_features: - obj_mask.append('PartDesign') - if node.read_part: - obj_mask.append('Part') - if node.read_body: - obj_mask.append('PartDesign::Body') - - for f in files: - S = LoadSolid(f, part_filter, obj_mask, node.tool_parts, node.inv_filter) - - for s, n in S: - solids.append(s) - names.append(list(n)) - - node.outputs['Solid'].sv_set(solids) - self.ensure_name_socket_exists() - node.outputs['Names'].sv_set(names) - - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - if not self.inputs['File Path'].is_linked: - return - - if self.read_update: - self.read_FCStd(self) - else: - return - - - class SvShowFcstdNamesOp(bpy.types.Operator, SvGenericNodeLocator): - - bl_idname = "node.sv_show_fcstd_names" - bl_label = "Show parts list" - bl_options = {'INTERNAL', 'REGISTER'} - bl_property = "option" - - def LabelReader(self,context): - labels=[('','','')] - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - fc_file_list = node.inputs['File Path'].sv_get()[0] - - obj_mask = [] - if node.read_features: - obj_mask.append('PartDesign') - if node.read_part: - obj_mask.append('Part') - if node.read_body: - obj_mask.append('PartDesign::Body') - - for f in fc_file_list: - try: - doc = F.open(f) - Fname = doc.Name or bpy.path.display_name_from_filepath(f) - - for obj in doc.Objects: - if obj.Module in obj_mask or obj.TypeId in obj_mask: - labels.append( (obj.Label, obj.Label, obj.Label) ) - - except Exception as err: - info(f'FCStd label read error: {Fname=}') - info(err) - finally: - # del doc - F.closeDocument(doc.Name) - - return labels - - - option : EnumProperty(items=LabelReader) - tree_name : StringProperty() - node_name : StringProperty() - - - def execute(self, context): - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - node.name_filter = self.option - node.selected_label = self.option - node.selected_part = self.option - bpy.context.area.tag_redraw() - return {'FINISHED'} - - def invoke(self, context, event): - context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) - wm = context.window_manager - wm.invoke_search_popup(self) - return {'FINISHED'} - + def read_FCStd(self, node): + + files = node.inputs['File Path'].sv_get()[0] + + part_filter = [] + if node.inputs['Part Filter'].is_linked: + part_filter = node.inputs['Part Filter'].sv_get()[0] + + if node.selected_part != '' and not node.selected_part in part_filter: + part_filter.append(node.selected_part) + + solids = [] + obj_mask = [] + names = [] + + if node.read_features: + obj_mask.append('PartDesign') + if node.read_part: + obj_mask.append('Part') + if node.read_body: + obj_mask.append('PartDesign::Body') + + for f in files: + S = LoadSolid(f, part_filter, obj_mask, node.tool_parts, node.inv_filter) + + for s, n in S: + solids.append(s) + names.append(list(n)) + + node.outputs['Solid'].sv_set(solids) + self.ensure_name_socket_exists() + node.outputs['Names'].sv_set(names) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + if not self.inputs['File Path'].is_linked: + return + + if self.read_update: + self.read_FCStd(self) + else: + return + + +class SvShowFcstdNamesOp(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_show_fcstd_names" + bl_label = "Show parts list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + obj_mask = [] + if node.read_features: + obj_mask.append('PartDesign') + if node.read_part: + obj_mask.append('Part') + if node.read_body: + obj_mask.append('PartDesign::Body') + + for f in fc_file_list: + try: + doc = F.open(f) + Fname = doc.Name or bpy.path.display_name_from_filepath(f) + + for obj in doc.Objects: + if obj.Module in obj_mask or obj.TypeId in obj_mask: + labels.append( (obj.Label, obj.Label, obj.Label) ) + + except Exception as err: + info(f'FCStd label read error: {Fname=}') + info(err) + finally: + # del doc + F.closeDocument(doc.Name) + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + node.name_filter = self.option + node.selected_label = self.option + node.selected_part = self.option + bpy.context.area.tag_redraw() + return {'FINISHED'} + + def invoke(self, context, event): + context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) + wm = context.window_manager + wm.invoke_search_popup(self) + return {'FINISHED'} + def LoadSolid(fc_file, part_filter, obj_mask, tool_parts, inv_filter): objs= set() outList = set() solids = set() - - try: + + try: doc = F.open(fc_file) Fname = doc.Name or bpy.path.display_name_from_filepath(fc_file) - + for obj in doc.Objects: if obj.Module in obj_mask or obj.TypeId in obj_mask: objs.add (obj) - if not tool_parts and obj.TypeId in ( 'Part::Cut','Part::Fuse','Part::MultiCommon','Part::Section','Part::FeaturePython' ): + if not tool_parts and obj.TypeId in ( 'Part::Cut','Part::Fuse','Part::MultiCommon','Part::Section','Part::FeaturePython' ): if len(obj.OutList) > 0: for out_obj in obj.OutList: outList.add (out_obj) @@ -224,8 +223,8 @@ def LoadSolid(fc_file, part_filter, obj_mask, tool_parts, inv_filter): else: if not obj.Label in part_filter: - solids.add((obj.Shape, (obj.FullName, obj.Name, obj.Label))) - + solids.add((obj.Shape, (obj.FullName, obj.Name, obj.Label))) + except: info('FCStd read error') finally: diff --git a/nodes/exchange/FCStd_read_mod.py b/nodes/exchange/FCStd_read_mod.py index 3c9a688e53..42be476fae 100644 --- a/nodes/exchange/FCStd_read_mod.py +++ b/nodes/exchange/FCStd_read_mod.py @@ -1,3 +1,9 @@ +import bpy +from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.utils.logging import info from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator @@ -10,204 +16,197 @@ # june '22 # modified version of FCStd_read includes changes by rastart, zeffii, et al # please feed changes back to the Sverchok issue tracker. - F = FreeCAD - import bpy - from mathutils import Matrix - from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode - from sverchok.utils.logging import info - from FreeCAD import Base - - class SvReadFCStdModOperator(bpy.types.Operator, SvGenericNodeLocator): - - bl_idname = "node.sv_read_fcstd_operator_mod" - bl_label = "read freecad file" - bl_options = {'INTERNAL', 'REGISTER'} - - def sv_execute(self, context, node): - - if not any(socket.is_linked for socket in node.outputs): - return {'CANCELLED'} - if not node.inputs['File Path'].is_linked: - return {'CANCELLED'} - - node.read_FCStd(node) - updateNode(node, context) - return {'FINISHED'} - - def LabelReader(operator): - tree = bpy.data.node_groups[operator.tree_name] - node = tree.nodes[operator.node_name] + +class SvReadFCStdModOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_read_fcstd_operator_mod" + bl_label = "read freecad file" + bl_options = {'INTERNAL', 'REGISTER'} + + def sv_execute(self, context, node): + + if not any(socket.is_linked for socket in node.outputs): + return {'CANCELLED'} + if not node.inputs['File Path'].is_linked: + return {'CANCELLED'} + + node.read_FCStd(node) + updateNode(node, context) + return {'FINISHED'} + + +def LabelReader(operator): + tree = bpy.data.node_groups[operator.tree_name] + node = tree.nodes[operator.node_name] + + module_filter = [] + + # \/ does not appear to be available from the items= func + # node = self.get_node(context) + # + if node.read_features: module_filter.append('PartDesign') + if node.read_part: module_filter.append('Part') + if node.read_body: module_filter.append('PartDesign::Body') + if node.merge_linked: module_filter.append('App::Link') + + labels = [('', '', '')] + + fc_file_list = node.inputs['File Path'].sv_get()[0] + for fc_file in fc_file_list: + try: + doc = F.open(fc_file) + for obj in doc.Objects: + if obj.Module in module_filter or obj.TypeId in module_filter: + labels.append( (obj.Label, obj.Label, obj.Label) ) + except: + info('FCStd label read error') + finally: + #del doc + F.closeDocument(doc.Name) + + return labels + + +class SvShowFcstdNamesModOp(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_show_fcstd_names_mod" + bl_label = "Show parts list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + option: EnumProperty(items=lambda s, c: LabelReader(s)) + + def sv_execute(self, context, node): + node.name_filter = self.option + node.selected_label = self.option + node.selected_part = self.option + bpy.context.area.tag_redraw() + return {'FINISHED'} + + def invoke(self, context, event): + context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) + wm = context.window_manager + wm.invoke_search_popup(self) + return {'FINISHED'} + + +class SvReadFCStdModNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Read FreeCAD file mod + Tooltip: import parts from a .FCStd file + """ + bl_idname = 'SvReadFCStdModNode' + bl_label = 'Read FCStd MOD' + bl_icon = 'IMPORT' + sv_category = "Solid Outputs" + + read_update : BoolProperty(name="read_update", default=True) + read_body : BoolProperty(name="read_body", default=True, update=updateNode) + read_part : BoolProperty(name="read_part", default=True, update=updateNode) + + tool_parts : BoolProperty(name="tool_parts", default=False, update=updateNode) + read_features : BoolProperty(name="read_features", default=False, update=updateNode) + inv_filter : BoolProperty(name="inv_filter", default=False, update=updateNode) + scale_factor : FloatProperty(name="unit factor", default=1, update=updateNode) + selected_label : StringProperty( default= 'Select FC Part') + selected_part : StringProperty( default='', update=updateNode) + merge_linked : BoolProperty(name="merge_linked", default=False, update=updateNode) + READ_ALL : BoolProperty(name="read all", default=False, update=updateNode) + + def draw_buttons(self, context, layout): + + col = layout.column(align=True) + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, "node.sv_show_fcstd_names_mod", + icon= 'TRIA_DOWN', + text= self.selected_label ) + + col.prop(self, 'read_update', text='global update') + col.prop(self, 'read_body') + col.prop(self, 'read_part') + col.prop(self, 'tool_parts') + if self.tool_parts: + col.prop(self, 'read_features') + col.prop(self, 'inv_filter') + col.prop(self, 'merge_linked') + col.prop(self,'READ_ALL') + col.prop(self, 'scale_factor') + self.wrapper_tracked_ui_draw_op(layout, "node.sv_read_fcstd_operator_mod", icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "Label1 Filter") + self.inputs.new('SvStringsSocket', "Label2 Filter") + self.outputs.new('SvSolidSocket', "Solid") + self.outputs.new('SvTextSocket', "Names") + + def read_FCStd(self, node): + + files = node.inputs['File Path'].sv_get()[0] + + label_1_filter = [] + label_2_filter = [] + label_1_tags = [] + label_2_tags = [] + + if (L1_Filter := node.inputs['Label1 Filter']).is_linked: + raw_filter = L1_Filter.sv_get()[0] + + if len(raw_filter) == 1 and ',' in raw_filter[0]: + raw_filter = raw_filter[0].split(',') + + for i in raw_filter: + label_1_tags.append(i) if '#' in i else label_1_filter.append(i) + + if (L2_Filter := node.inputs['Label2 Filter']).is_linked: + raw_filter = L2_Filter.sv_get()[0] + + if len(raw_filter) == 1 and ',' in raw_filter[0]: + raw_filter = raw_filter[0].split(',') + + for i in raw_filter: + label_2_tags.append(i) if '#' in i else label_2_filter.append(i) + + #ADD TO LABEL 1 FILTER THE DROPDOWN ENTRY IF SELECTED + if node.selected_part != '' and not node.selected_part in label_1_filter: + label_1_filter.append(node.selected_part) + + solids = [] + identifiers = [] module_filter = [] - # \/ does not appear to be available from the items= func - # node = self.get_node(context) - # if node.read_features: module_filter.append('PartDesign') if node.read_part: module_filter.append('Part') if node.read_body: module_filter.append('PartDesign::Body') - if node.merge_linked: module_filter.append('App::Link') - - labels = [('', '', '')] - - fc_file_list = node.inputs['File Path'].sv_get()[0] - for fc_file in fc_file_list: - try: - doc = F.open(fc_file) - for obj in doc.Objects: - if obj.Module in module_filter or obj.TypeId in module_filter: - labels.append( (obj.Label, obj.Label, obj.Label) ) - except: - info('FCStd label read error') - finally: - #del doc - F.closeDocument(doc.Name) - - return labels - - class SvShowFcstdNamesModOp(bpy.types.Operator, SvGenericNodeLocator): - - bl_idname = "node.sv_show_fcstd_names_mod" - bl_label = "Show parts list" - bl_options = {'INTERNAL', 'REGISTER'} - bl_property = "option" - - option: EnumProperty(items=lambda s, c: LabelReader(s)) - - def sv_execute(self, context, node): - node.name_filter = self.option - node.selected_label = self.option - node.selected_part = self.option - bpy.context.area.tag_redraw() - return {'FINISHED'} - - def invoke(self, context, event): - context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) - wm = context.window_manager - wm.invoke_search_popup(self) - return {'FINISHED'} - - - class SvReadFCStdModNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Read FreeCAD file mod - Tooltip: import parts from a .FCStd file - """ - bl_idname = 'SvReadFCStdModNode' - bl_label = 'Read FCStd MOD' - bl_icon = 'IMPORT' - solid_catergory = "Outputs" - - read_update : BoolProperty(name="read_update", default=True) - read_body : BoolProperty(name="read_body", default=True, update=updateNode) - read_part : BoolProperty(name="read_part", default=True, update=updateNode) - - tool_parts : BoolProperty(name="tool_parts", default=False, update=updateNode) - read_features : BoolProperty(name="read_features", default=False, update=updateNode) - inv_filter : BoolProperty(name="inv_filter", default=False, update=updateNode) - scale_factor : FloatProperty(name="unit factor", default=1, update=updateNode) - selected_label : StringProperty( default= 'Select FC Part') - selected_part : StringProperty( default='', update=updateNode) - merge_linked : BoolProperty(name="merge_linked", default=False, update=updateNode) - READ_ALL : BoolProperty(name="read all", default=False, update=updateNode) - - def draw_buttons(self, context, layout): - - col = layout.column(align=True) - if self.inputs['File Path'].is_linked: - self.wrapper_tracked_ui_draw_op( - col, "node.sv_show_fcstd_names_mod", - icon= 'TRIA_DOWN', - text= self.selected_label ) - - col.prop(self, 'read_update', text='global update') - col.prop(self, 'read_body') - col.prop(self, 'read_part') - col.prop(self, 'tool_parts') - if self.tool_parts: - col.prop(self, 'read_features') - col.prop(self, 'inv_filter') - col.prop(self, 'merge_linked') - col.prop(self,'READ_ALL') - col.prop(self, 'scale_factor') - self.wrapper_tracked_ui_draw_op(layout, "node.sv_read_fcstd_operator_mod", icon='FILE_REFRESH', text="UPDATE") - - def sv_init(self, context): - - self.inputs.new('SvFilePathSocket', "File Path") - self.inputs.new('SvStringsSocket', "Label1 Filter") - self.inputs.new('SvStringsSocket', "Label2 Filter") - self.outputs.new('SvSolidSocket', "Solid") - self.outputs.new('SvTextSocket', "Names") - - def read_FCStd(self, node): - - files = node.inputs['File Path'].sv_get()[0] - - label_1_filter = [] - label_2_filter = [] - label_1_tags = [] - label_2_tags = [] - - if (L1_Filter := node.inputs['Label1 Filter']).is_linked: - raw_filter = L1_Filter.sv_get()[0] - - if len(raw_filter) == 1 and ',' in raw_filter[0]: - raw_filter = raw_filter[0].split(',') - - for i in raw_filter: - label_1_tags.append(i) if '#' in i else label_1_filter.append(i) - - if (L2_Filter := node.inputs['Label2 Filter']).is_linked: - raw_filter = L2_Filter.sv_get()[0] - - if len(raw_filter) == 1 and ',' in raw_filter[0]: - raw_filter = raw_filter[0].split(',') - - for i in raw_filter: - label_2_tags.append(i) if '#' in i else label_2_filter.append(i) - - #ADD TO LABEL 1 FILTER THE DROPDOWN ENTRY IF SELECTED - if node.selected_part != '' and not node.selected_part in label_1_filter: - label_1_filter.append(node.selected_part) - - solids = [] - identifiers = [] - module_filter = [] - - if node.read_features: module_filter.append('PartDesign') - if node.read_part: module_filter.append('Part') - if node.read_body: module_filter.append('PartDesign::Body') - - for fname in files: - S = LoadSolid( - self.scale_factor, fname, - label_1_filter, label_1_tags, label_2_filter, label_2_tags, - module_filter, node.tool_parts, - node.inv_filter, self.READ_ALL, self.merge_linked ) - - for s, ident in S: - solids.append(s) - identifiers.append(ident) - - node.outputs['Solid'].sv_set(solids) - node.outputs['Names'].sv_set(identifiers) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - if not self.inputs['File Path'].is_linked: - return - - if self.read_update: - self.read_FCStd(self) - else: - return - + for fname in files: + S = LoadSolid( + self.scale_factor, fname, + label_1_filter, label_1_tags, label_2_filter, label_2_tags, + module_filter, node.tool_parts, + node.inv_filter, self.READ_ALL, self.merge_linked ) + + for s, ident in S: + solids.append(s) + identifiers.append(ident) + + node.outputs['Solid'].sv_set(solids) + node.outputs['Names'].sv_set(identifiers) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + if not self.inputs['File Path'].is_linked: + return + + if self.read_update: + self.read_FCStd(self) + else: + return def LoadSolid( diff --git a/nodes/exchange/FCStd_sketch.py b/nodes/exchange/FCStd_sketch.py index 649dbaa531..3e5e2fb707 100644 --- a/nodes/exchange/FCStd_sketch.py +++ b/nodes/exchange/FCStd_sketch.py @@ -1,6 +1,13 @@ +import bpy +import numpy as np +import mathutils +from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.utils.logging import info from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy -import mathutils from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator if FreeCAD is None: @@ -8,169 +15,166 @@ else: F = FreeCAD - import bpy - import numpy as np - from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode - from sverchok.utils.logging import info - - class SvReadFCStdSketchOperator(bpy.types.Operator, SvGenericNodeLocator): - - bl_idname = "node.sv_read_fcstd_sketch_operator" - bl_label = "read freecad sketch" - bl_options = {'INTERNAL', 'REGISTER'} - - def execute(self, context): - node = self.get_node(context) - - if not node: return {'CANCELLED'} - - node.read_sketch(node) - updateNode(node,context) - - return {'FINISHED'} - - class SvReadFCStdSketchNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Read FreeCAD file - Tooltip: import parts from a .FCStd file - """ - bl_idname = 'SvReadFCStdSketchNode' - bl_label = 'Read FCStd Sketches' - bl_icon = 'IMPORT' - solid_catergory = "Outputs" - - max_points : IntProperty(name="max_points", default=50, update = updateNode) - read_update : BoolProperty(name="read_update", default=True) - inv_filter : BoolProperty(name="inv_filter", default=False, update = updateNode) - selected_label : StringProperty(default='Select FC Part') - selected_part : StringProperty(default='',update = updateNode) - - read_mode : EnumProperty( - name='mode', - description='read geometry / construction', - items=[ - ('geometry', 'geometry', 'geometry'), - ('construction', 'construction', 'construction'), - ('BOTH', 'BOTH', 'BOTH')], - default='geometry', - update = updateNode ) - - def draw_buttons(self, context, layout): - - col = layout.column(align=True) - col.prop(self, 'read_mode') - - if self.inputs['File Path'].is_linked: - self.wrapper_tracked_ui_draw_op( - col, SvShowFcstdSketchNamesOp.bl_idname, - icon= 'TRIA_DOWN', - text= self.selected_label ) - - col.prop(self, 'max_points') - col.prop(self, 'read_update') - col.prop(self, 'inv_filter') - self.wrapper_tracked_ui_draw_op(layout, SvReadFCStdSketchOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") - - def sv_init(self, context): - self.inputs.new('SvFilePathSocket', "File Path") - self.inputs.new('SvStringsSocket', "Sketch Filter") - - self.outputs.new('SvVerticesSocket', "Verts") - self.outputs.new('SvStringsSocket', "Edges") - self.outputs.new('SvCurveSocket', "Curve") - - def read_sketch(self,node): - - if not any(socket.is_linked for socket in node.outputs): - return - - if not node.inputs['File Path'].is_linked: - return - - if node.read_update: - - files = node.inputs['File Path'].sv_get()[0] - sketch_filter = [] - if node.inputs['Sketch Filter'].is_linked: - sketch_filter = node.inputs['Sketch Filter'].sv_get()[0] - - if node.selected_part != '' and not node.selected_part in sketch_filter: - sketch_filter.append(node.selected_part) - - Verts = [] - Edges = [] - curves_out = [] - for f in files: - S = LoadSketch(f, sketch_filter, node.max_points, node.inv_filter, node.read_mode) - for i in S[0]: - Verts.append(i) - for i in S[1]: - Edges.append(i) - for i in S[2]: - curves_out.append(i) - node.outputs['Verts'].sv_set([Verts]) - node.outputs['Edges'].sv_set([Edges]) - node.outputs['Curve'].sv_set(curves_out) - - else: - return - def process(self): - self.read_sketch(self) +class SvReadFCStdSketchOperator(bpy.types.Operator, SvGenericNodeLocator): - class SvShowFcstdSketchNamesOp(bpy.types.Operator): - bl_idname = "node.sv_show_fcstd_sketch_names" - bl_label = "Show parts list" - bl_options = {'INTERNAL', 'REGISTER'} - bl_property = "option" + bl_idname = "node.sv_read_fcstd_sketch_operator" + bl_label = "read freecad sketch" + bl_options = {'INTERNAL', 'REGISTER'} - def LabelReader(self,context): - labels=[('','','')] + def execute(self, context): + node = self.get_node(context) - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - fc_file_list = node.inputs['File Path'].sv_get()[0] + if not node: return {'CANCELLED'} - try: + node.read_sketch(node) + updateNode(node,context) - for f in fc_file_list: - F.open(f) - Fname = bpy.path.display_name_from_filepath(f) - F.setActiveDocument(Fname) + return {'FINISHED'} - for obj in F.ActiveDocument.Objects: - if obj.Module == 'Sketcher': - labels.append( (obj.Label, obj.Label, obj.Label) ) - F.closeDocument(Fname) +class SvReadFCStdSketchNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Read FreeCAD file + Tooltip: import parts from a .FCStd file + """ + bl_idname = 'SvReadFCStdSketchNode' + bl_label = 'Read FCStd Sketches' + bl_icon = 'IMPORT' + sv_category = "Solid Outputs" - except: - info('FCStd read error') - - return labels - - option : EnumProperty(items=LabelReader) - tree_name : StringProperty() - node_name : StringProperty() - - def execute(self, context): - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - node.name_filter = self.option - node.selected_label = self.option - node.selected_part = self.option - bpy.context.area.tag_redraw() - return {'FINISHED'} - - def invoke(self, context, event): - context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) - wm = context.window_manager - wm.invoke_search_popup(self) - return {'FINISHED'} + max_points : IntProperty(name="max_points", default=50, update = updateNode) + read_update : BoolProperty(name="read_update", default=True) + inv_filter : BoolProperty(name="inv_filter", default=False, update = updateNode) + selected_label : StringProperty(default='Select FC Part') + selected_part : StringProperty(default='',update = updateNode) + + read_mode : EnumProperty( + name='mode', + description='read geometry / construction', + items=[ + ('geometry', 'geometry', 'geometry'), + ('construction', 'construction', 'construction'), + ('BOTH', 'BOTH', 'BOTH')], + default='geometry', + update = updateNode ) + + def draw_buttons(self, context, layout): + + col = layout.column(align=True) + col.prop(self, 'read_mode') + + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, SvShowFcstdSketchNamesOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_label ) + + col.prop(self, 'max_points') + col.prop(self, 'read_update') + col.prop(self, 'inv_filter') + self.wrapper_tracked_ui_draw_op(layout, SvReadFCStdSketchOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "Sketch Filter") + + self.outputs.new('SvVerticesSocket', "Verts") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvCurveSocket', "Curve") + + def read_sketch(self,node): + + if not any(socket.is_linked for socket in node.outputs): + return + + if not node.inputs['File Path'].is_linked: + return + + if node.read_update: + + files = node.inputs['File Path'].sv_get()[0] + + sketch_filter = [] + if node.inputs['Sketch Filter'].is_linked: + sketch_filter = node.inputs['Sketch Filter'].sv_get()[0] + + if node.selected_part != '' and not node.selected_part in sketch_filter: + sketch_filter.append(node.selected_part) + + Verts = [] + Edges = [] + curves_out = [] + for f in files: + S = LoadSketch(f, sketch_filter, node.max_points, node.inv_filter, node.read_mode) + for i in S[0]: + Verts.append(i) + for i in S[1]: + Edges.append(i) + for i in S[2]: + curves_out.append(i) + node.outputs['Verts'].sv_set([Verts]) + node.outputs['Edges'].sv_set([Edges]) + node.outputs['Curve'].sv_set(curves_out) + + else: + return + + def process(self): + self.read_sketch(self) + + +class SvShowFcstdSketchNamesOp(bpy.types.Operator): + bl_idname = "node.sv_show_fcstd_sketch_names" + bl_label = "Show parts list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + try: + + for f in fc_file_list: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + + if obj.Module == 'Sketcher': + labels.append( (obj.Label, obj.Label, obj.Label) ) + F.closeDocument(Fname) + + except: + info('FCStd read error') + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + node.name_filter = self.option + node.selected_label = self.option + node.selected_part = self.option + bpy.context.area.tag_redraw() + return {'FINISHED'} + + def invoke(self, context, event): + context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) + wm = context.window_manager + wm.invoke_search_popup(self) + return {'FINISHED'} def LoadSketch(fc_file, sketch_filter, max_points, inv_filter, read_mode): diff --git a/nodes/exchange/FCStd_spreadsheet.py b/nodes/exchange/FCStd_spreadsheet.py index 17e9b78677..de0e7422b7 100644 --- a/nodes/exchange/FCStd_spreadsheet.py +++ b/nodes/exchange/FCStd_spreadsheet.py @@ -1,3 +1,9 @@ +import bpy +from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.utils.logging import info from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator @@ -7,216 +13,213 @@ else: F = FreeCAD - import bpy - from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty, FloatProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode - from sverchok.utils.logging import info - class SvFCStdSpreadsheetOperator(bpy.types.Operator, SvGenericNodeLocator): - bl_idname = "node.sv_fcstd_spreadsheet_operator" - bl_label = "read/write freecad spreadsheet" - bl_options = {'INTERNAL', 'REGISTER'} +class SvFCStdSpreadsheetOperator(bpy.types.Operator, SvGenericNodeLocator): - def execute(self, context): - node = self.get_node(context) + bl_idname = "node.sv_fcstd_spreadsheet_operator" + bl_label = "read/write freecad spreadsheet" + bl_options = {'INTERNAL', 'REGISTER'} - if not node: return {'CANCELLED'} + def execute(self, context): + node = self.get_node(context) - node.edit_spreadsheet(node) - updateNode(node,context) + if not node: return {'CANCELLED'} - return {'FINISHED'} + node.edit_spreadsheet(node) + updateNode(node,context) + return {'FINISHED'} - class SvFCStdSpreadsheetNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Read FreeCAD file - Tooltip: Read/write FCStd Spreadsheets from a .FCStd file - """ - bl_idname = 'SvFCStdSpreadsheetNode' - bl_label = 'Read/write Spreadsheets' - bl_icon = 'IMPORT' - solid_catergory = "Outputs" - - auto_update : BoolProperty(name="auto_update", default=True) - write_update : BoolProperty(name="read_update", default=True) - write_parameter : BoolProperty(name="write_parameter", default=False) - - selected_label : StringProperty(default='Spreadsheet') - selected_sheet : StringProperty(default='',update = updateNode) - selected_par_label : StringProperty(default='Parameter') - selected_par : StringProperty(default='',update = updateNode) +class SvFCStdSpreadsheetNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Read FreeCAD file + Tooltip: Read/write FCStd Spreadsheets from a .FCStd file + """ + bl_idname = 'SvFCStdSpreadsheetNode' + bl_label = 'Read/write Spreadsheets' + bl_icon = 'IMPORT' + sv_category = "Solid Outputs" - cell_in : FloatProperty( name="cell_in", description='cell_in', default=0.0 ) + auto_update : BoolProperty(name="auto_update", default=True) + write_update : BoolProperty(name="read_update", default=True) + write_parameter : BoolProperty(name="write_parameter", default=False) - def draw_buttons(self, context, layout): + selected_label : StringProperty(default='Spreadsheet') + selected_sheet : StringProperty(default='',update = updateNode) - col = layout.column(align=True) - if self.inputs['File Path'].is_linked: - self.wrapper_tracked_ui_draw_op( - col, SvShowFcstdSpreadsheetsOp.bl_idname, - icon= 'TRIA_DOWN', - text= self.selected_label ) + selected_par_label : StringProperty(default='Parameter') + selected_par : StringProperty(default='',update = updateNode) + cell_in : FloatProperty( name="cell_in", description='cell_in', default=0.0 ) - if self.inputs['File Path'].is_linked: - self.wrapper_tracked_ui_draw_op( - col, SvShowFcstdParNamesOp.bl_idname, - icon= 'TRIA_DOWN', - text= self.selected_par_label ) + def draw_buttons(self, context, layout): - col.prop(self, 'auto_update') - col.prop(self, 'write_parameter') - self.wrapper_tracked_ui_draw_op(layout, SvFCStdSpreadsheetOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + col = layout.column(align=True) + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, SvShowFcstdSpreadsheetsOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_label ) - def sv_init(self, context): - self.inputs.new('SvFilePathSocket', "File Path") - self.inputs.new('SvStringsSocket', "cell_in").prop_name = 'cell_in' - self.outputs.new('SvStringsSocket', "cell_out") + if self.inputs['File Path'].is_linked: + self.wrapper_tracked_ui_draw_op( + col, SvShowFcstdParNamesOp.bl_idname, + icon= 'TRIA_DOWN', + text= self.selected_par_label ) + col.prop(self, 'auto_update') + col.prop(self, 'write_parameter') + self.wrapper_tracked_ui_draw_op(layout, SvFCStdSpreadsheetOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") - def edit_spreadsheet(self,node): + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") + self.inputs.new('SvStringsSocket', "cell_in").prop_name = 'cell_in' - if not node.inputs['File Path'].is_linked: - return + self.outputs.new('SvStringsSocket', "cell_out") - if node.selected_par != '' : + def edit_spreadsheet(self,node): - files = node.inputs['File Path'].sv_get()[0] + if not node.inputs['File Path'].is_linked: + return - cell_out=None + if node.selected_par != '' : - for f in files: - cell_out = WriteParameter( - f, - node.selected_sheet, - node.selected_par, - node.inputs['cell_in'].sv_get()[0][0], - node.write_parameter) + files = node.inputs['File Path'].sv_get()[0] + + cell_out=None + + for f in files: + cell_out = WriteParameter( + f, + node.selected_sheet, + node.selected_par, + node.inputs['cell_in'].sv_get()[0][0], + node.write_parameter) + + if cell_out != None: + + node.outputs['cell_out'].sv_set( [[cell_out]] ) + + else: + node.outputs['cell_out'].sv_set( [ ] ) + return + + def process(self): + if self.auto_update: + self.edit_spreadsheet(self) + + +class SvShowFcstdSpreadsheetsOp(bpy.types.Operator, SvGenericNodeLocator): + bl_idname = "node.sv_show_fcstd_spreadsheets" + bl_label = "Show spreadsheet list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + try: + for f in fc_file_list: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + if obj.Module == 'Spreadsheet': + labels.append( (obj.Label, obj.Label, obj.Label) ) + + except: + info('LabelReader Spreadsheet error') + finally: + F.closeDocument(Fname) + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + node.name_filter = self.option + node.selected_label = self.option + node.selected_sheet = self.option + bpy.context.area.tag_redraw() + return {'FINISHED'} + + def invoke(self, context, event): + context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) + wm = context.window_manager + wm.invoke_search_popup(self) + return {'FINISHED'} + + +class SvShowFcstdParNamesOp(bpy.types.Operator, SvGenericNodeLocator): + bl_idname = "node.sv_show_fcstd_par_names" + bl_label = "Show parameter list" + bl_options = {'INTERNAL', 'REGISTER'} + bl_property = "option" + + def LabelReader(self,context): + labels=[('','','')] + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + fc_file_list = node.inputs['File Path'].sv_get()[0] + + try: + + for f in fc_file_list: + F.open(f) + Fname = bpy.path.display_name_from_filepath(f) + F.setActiveDocument(Fname) + + for obj in F.ActiveDocument.Objects: + + if obj.Label == node.selected_sheet: + props = obj.PropertiesList + for label in props: + alias = obj.getCellFromAlias(label) + if alias: + labels.append( (label, label, label) ) + + except: + info('Label reader read cell error') + + finally: + F.closeDocument(Fname) + + return labels + + option : EnumProperty(items=LabelReader) + tree_name : StringProperty() + node_name : StringProperty() + + def execute(self, context): + + tree = bpy.data.node_groups[self.tree_name] + node = tree.nodes[self.node_name] + node.name_filter = self.option + node.selected_par_label = self.option + node.selected_par = self.option + bpy.context.area.tag_redraw() + return {'FINISHED'} + + def invoke(self, context, event): + context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) + wm = context.window_manager + wm.invoke_search_popup(self) + return {'FINISHED'} - if cell_out != None: - - node.outputs['cell_out'].sv_set( [[cell_out]] ) - - else: - node.outputs['cell_out'].sv_set( [ ] ) - return - - def process(self): - if self.auto_update: - self.edit_spreadsheet(self) - - - class SvShowFcstdSpreadsheetsOp(bpy.types.Operator, SvGenericNodeLocator): - bl_idname = "node.sv_show_fcstd_spreadsheets" - bl_label = "Show spreadsheet list" - bl_options = {'INTERNAL', 'REGISTER'} - bl_property = "option" - - def LabelReader(self,context): - labels=[('','','')] - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - fc_file_list = node.inputs['File Path'].sv_get()[0] - - try: - for f in fc_file_list: - F.open(f) - Fname = bpy.path.display_name_from_filepath(f) - F.setActiveDocument(Fname) - - for obj in F.ActiveDocument.Objects: - if obj.Module == 'Spreadsheet': - labels.append( (obj.Label, obj.Label, obj.Label) ) - - except: - info('LabelReader Spreadsheet error') - finally: - F.closeDocument(Fname) - - return labels - - option : EnumProperty(items=LabelReader) - tree_name : StringProperty() - node_name : StringProperty() - - def execute(self, context): - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - node.name_filter = self.option - node.selected_label = self.option - node.selected_sheet = self.option - bpy.context.area.tag_redraw() - return {'FINISHED'} - - def invoke(self, context, event): - context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) - wm = context.window_manager - wm.invoke_search_popup(self) - return {'FINISHED'} - - class SvShowFcstdParNamesOp(bpy.types.Operator, SvGenericNodeLocator): - bl_idname = "node.sv_show_fcstd_par_names" - bl_label = "Show parameter list" - bl_options = {'INTERNAL', 'REGISTER'} - bl_property = "option" - - def LabelReader(self,context): - labels=[('','','')] - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - fc_file_list = node.inputs['File Path'].sv_get()[0] - - try: - - for f in fc_file_list: - F.open(f) - Fname = bpy.path.display_name_from_filepath(f) - F.setActiveDocument(Fname) - - for obj in F.ActiveDocument.Objects: - - if obj.Label == node.selected_sheet: - props = obj.PropertiesList - for label in props: - alias = obj.getCellFromAlias(label) - if alias: - labels.append( (label, label, label) ) - - except: - info('Label reader read cell error') - - finally: - F.closeDocument(Fname) - - return labels - - option : EnumProperty(items=LabelReader) - tree_name : StringProperty() - node_name : StringProperty() - - def execute(self, context): - - tree = bpy.data.node_groups[self.tree_name] - node = tree.nodes[self.node_name] - node.name_filter = self.option - node.selected_par_label = self.option - node.selected_par = self.option - bpy.context.area.tag_redraw() - return {'FINISHED'} - - def invoke(self, context, event): - context.space_data.cursor_location_from_region(event.mouse_region_x, event.mouse_region_y) - wm = context.window_manager - wm.invoke_search_popup(self) - return {'FINISHED'} def WriteParameter(fc_file,spreadsheet,alias,par_write,write): diff --git a/nodes/exchange/FCStd_write.py b/nodes/exchange/FCStd_write.py index 30c309399e..04471b6d32 100644 --- a/nodes/exchange/FCStd_write.py +++ b/nodes/exchange/FCStd_write.py @@ -1,3 +1,9 @@ +import bpy +from bpy.props import StringProperty, BoolProperty,EnumProperty + +from sverchok.node_tree import SverchCustomTreeNode # OLD throttled +from sverchok.data_structure import updateNode, match_long_repeat # NEW throttle_and_update_node +from sverchok.utils.logging import info from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator @@ -7,135 +13,128 @@ else: F = FreeCAD - import bpy - from bpy.props import StringProperty, BoolProperty,EnumProperty - from sverchok.node_tree import SverchCustomTreeNode # OLD throttled - from sverchok.data_structure import updateNode, match_long_repeat # NEW throttle_and_update_node - from sverchok.utils.logging import info - class SvWriteFCStdOperator(bpy.types.Operator, SvGenericNodeLocator): +class SvWriteFCStdOperator(bpy.types.Operator, SvGenericNodeLocator): - bl_idname = "node.sv_write_fcstd_operator" - bl_label = "write freecad file" - bl_options = {'INTERNAL', 'REGISTER'} + bl_idname = "node.sv_write_fcstd_operator" + bl_label = "write freecad file" + bl_options = {'INTERNAL', 'REGISTER'} - def execute(self, context): - node = self.get_node(context) + def execute(self, context): + node = self.get_node(context) - if not node: return {'CANCELLED'} + if not node: return {'CANCELLED'} - node.write_FCStd(node) - updateNode(node,context) + node.write_FCStd(node) + updateNode(node,context) - return {'FINISHED'} + return {'FINISHED'} - class SvWriteFCStdNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: write FreeCAD file - Tooltip: write parts in a .FCStd file - """ - - bl_idname = 'SvWriteFCStdNode' - bl_label = 'Write FCStd' - bl_icon = 'IMPORT' - solid_catergory = "Inputs" - - write_update : BoolProperty( - name="write_update", - default=False) - - part_name : StringProperty( - name="part_name", - default="part_name") - - #@throttled - def changeMode(self, context): - - if self.obj_format == 'mesh': - if 'Verts' not in self.inputs: - self.inputs.remove(self.inputs['Solid']) - self.inputs.new('SvVerticesSocket', 'Verts') - self.inputs.new('SvVerticesSocket', 'Faces') - return - else: - if 'Solid' not in self.inputs: - self.inputs.remove(self.inputs['Verts']) - self.inputs.remove(self.inputs['Faces']) - self.inputs.new('SvSolidSocket', 'Solid') - return +class SvWriteFCStdNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: write FreeCAD file + Tooltip: write parts in a .FCStd file + """ - - obj_format : EnumProperty( - name='format', - description='choose format', - items={ - ('solid', 'solid', 'solid'), - ('mesh', 'mesh', 'mesh')}, - default='solid', - update=changeMode) - - def draw_buttons(self, context, layout): - - layout.label(text="write name:") - col = layout.column(align=True) - col.prop(self, 'part_name',text="") - col.prop(self, 'obj_format',text="") - col.prop(self, 'write_update') - if self.obj_format == 'mesh': - col.label(text="need triangle meshes") - self.wrapper_tracked_ui_draw_op(layout, SvWriteFCStdOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") - - - def sv_init(self, context): - self.inputs.new('SvFilePathSocket', "File Path") - - if self.obj_format == 'mesh': - self.inputs.new('SvVerticesSocket', "Verts") - self.inputs.new('SvStringsSocket', "Faces") - - else: - self.inputs.new('SvSolidSocket', 'Solid') - - def write_FCStd(self,node): + bl_idname = 'SvWriteFCStdNode' + bl_label = 'Write FCStd' + bl_icon = 'IMPORT' + sv_category = "Solid Inputs" - if not node.inputs['File Path'].is_linked: - return - - files = node.inputs['File Path'].sv_get() + write_update : BoolProperty( + name="write_update", + default=False) + + part_name : StringProperty( + name="part_name", + default="part_name") - if not len(files[0]) == 1: - print ('FCStd write node support just 1 file at once') + #@throttled + def changeMode(self, context): + + if self.obj_format == 'mesh': + if 'Verts' not in self.inputs: + self.inputs.remove(self.inputs['Solid']) + self.inputs.new('SvVerticesSocket', 'Verts') + self.inputs.new('SvVerticesSocket', 'Faces') + return + else: + if 'Solid' not in self.inputs: + self.inputs.remove(self.inputs['Verts']) + self.inputs.remove(self.inputs['Faces']) + self.inputs.new('SvSolidSocket', 'Solid') return - fc_file=files[0][0] + obj_format : EnumProperty( + name='format', + description='choose format', + items={ + ('solid', 'solid', 'solid'), + ('mesh', 'mesh', 'mesh')}, + default='solid', + update=changeMode) - if node.obj_format == 'mesh': + def draw_buttons(self, context, layout): - if any((node.inputs['Verts'].is_linked,node.inputs['Faces'].is_linked)): + layout.label(text="write name:") + col = layout.column(align=True) + col.prop(self, 'part_name',text="") + col.prop(self, 'obj_format',text="") + col.prop(self, 'write_update') + if self.obj_format == 'mesh': + col.label(text="need triangle meshes") + self.wrapper_tracked_ui_draw_op(layout, SvWriteFCStdOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") - verts_in = node.inputs['Verts'].sv_get(deepcopy=False) - pols_in = node.inputs['Faces'].sv_get(deepcopy=False) - verts, pols = match_long_repeat([verts_in, pols_in]) - fc_write_parts(fc_file, verts, pols, node.part_name, None, node.obj_format) - elif node.obj_format == 'solid': + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") - if node.inputs['Solid'].is_linked: - solid=node.inputs['Solid'].sv_get() - fc_write_parts(fc_file, None, None, node.part_name, solid, node.obj_format) + if self.obj_format == 'mesh': + self.inputs.new('SvVerticesSocket', "Verts") + self.inputs.new('SvStringsSocket', "Faces") - else: - return + else: + self.inputs.new('SvSolidSocket', 'Solid') - def process(self): + def write_FCStd(self,node): - if self.write_update: - self.write_FCStd(self) - else: - return + if not node.inputs['File Path'].is_linked: + return + + files = node.inputs['File Path'].sv_get() + + if not len(files[0]) == 1: + print ('FCStd write node support just 1 file at once') + return + + fc_file=files[0][0] + + if node.obj_format == 'mesh': + + if any((node.inputs['Verts'].is_linked,node.inputs['Faces'].is_linked)): + + verts_in = node.inputs['Verts'].sv_get(deepcopy=False) + pols_in = node.inputs['Faces'].sv_get(deepcopy=False) + verts, pols = match_long_repeat([verts_in, pols_in]) + fc_write_parts(fc_file, verts, pols, node.part_name, None, node.obj_format) + + elif node.obj_format == 'solid': + + if node.inputs['Solid'].is_linked: + solid=node.inputs['Solid'].sv_get() + fc_write_parts(fc_file, None, None, node.part_name, solid, node.obj_format) + + else: + return + + def process(self): + if self.write_update: + self.write_FCStd(self) + else: + return def fc_write_parts(fc_file, verts, faces, part_name, solid, mod): diff --git a/nodes/exchange/approx_subd_to_nurbs.py b/nodes/exchange/approx_subd_to_nurbs.py index 01b5fbd89b..2b01e8b3f3 100644 --- a/nodes/exchange/approx_subd_to_nurbs.py +++ b/nodes/exchange/approx_subd_to_nurbs.py @@ -1,92 +1,88 @@ import bpy,bmesh +from bpy.props import BoolProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy from sverchok.utils.sv_operator_mixins import SvGenericNodeLocator if FreeCAD is None: - add_dummy('SvReadFCStdNode', 'SvReadFCStdNode', 'FreeCAD') + add_dummy('SvApproxSubdtoNurbsNode', 'SvReadFCStdNode', 'FreeCAD') else: F = FreeCAD - import bpy - from bpy.props import StringProperty, BoolProperty, EnumProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode - from sverchok.utils.logging import info - class SvApproxSubdtoNurbsOperator(bpy.types.Operator, SvGenericNodeLocator): - bl_idname = "node.approx_subd_nurbs_operator" - bl_label = "Approx Subd-Nurbs" - bl_options = {'INTERNAL', 'REGISTER'} +class SvApproxSubdtoNurbsOperator(bpy.types.Operator, SvGenericNodeLocator): - def execute(self, context): - node = self.get_node(context) - - if not node: - return {'CANCELLED'} + bl_idname = "node.approx_subd_nurbs_operator" + bl_label = "Approx Subd-Nurbs" + bl_options = {'INTERNAL', 'REGISTER'} - if not any(socket.is_linked for socket in node.outputs): - return {'CANCELLED'} + def execute(self, context): + node = self.get_node(context) - try: - node.inputs['Subd Obj'].sv_get()[0] - except: - return {'CANCELLED'} + if not node: + return {'CANCELLED'} - node.Approximate(node) - updateNode(node,context) + if not any(socket.is_linked for socket in node.outputs): + return {'CANCELLED'} - return {'FINISHED'} + try: + node.inputs['Subd Obj'].sv_get()[0] + except: + return {'CANCELLED'} + node.Approximate(node) + updateNode(node,context) - class SvApproxSubdtoNurbsNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Approximate Subd to Nurbs - Tooltip: Approximate Subd to Nurbs - """ - bl_idname = 'SvApproxSubdtoNurbsNode' - bl_label = 'Approximate Subd to Nurb' - bl_icon = 'IMPORT' - solid_catergory = "Outputs" + return {'FINISHED'} - auto_update : BoolProperty(name="auto_update", default=True) +class SvApproxSubdtoNurbsNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Approximate Subd to Nurbs + Tooltip: Approximate Subd to Nurbs + """ + bl_idname = 'SvApproxSubdtoNurbsNode' + bl_label = 'Approximate Subd to Nurb' + bl_icon = 'IMPORT' + sv_category = "Solid Outputs" - - def draw_buttons(self, context, layout): + auto_update : BoolProperty(name="auto_update", default=True) - col = layout.column(align=True) - col.prop(self, 'auto_update', text = 'global update') + def draw_buttons(self, context, layout): - self.wrapper_tracked_ui_draw_op(layout, SvApproxSubdtoNurbsOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") + col = layout.column(align=True) + col.prop(self, 'auto_update', text = 'global update') - def sv_init(self, context): + self.wrapper_tracked_ui_draw_op(layout, SvApproxSubdtoNurbsOperator.bl_idname, icon='FILE_REFRESH', text="UPDATE") - self.inputs.new('SvObjectSocket', "Subd Obj") - self.outputs.new('SvSolidSocket', "Solid") + def sv_init(self, context): + self.inputs.new('SvObjectSocket', "Subd Obj") + self.outputs.new('SvSolidSocket', "Solid") - def Approximate(self,node): + def Approximate(self,node): - S = ApproxSubdToNurbs( node.inputs['Subd Obj'].sv_get()[0] ) - - node.outputs['Solid'].sv_set(S) + S = ApproxSubdToNurbs( node.inputs['Subd Obj'].sv_get()[0] ) + node.outputs['Solid'].sv_set(S) - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - try: - self.inputs['Subd Obj'].sv_get()[0] - except: - return + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + try: + self.inputs['Subd Obj'].sv_get()[0] + except: + return - if self.auto_update: - self.Approximate(self) - else: - return + if self.auto_update: + self.Approximate(self) + else: + return def ApproxSubdToNurbs(Object): diff --git a/nodes/exchange/export_rw3dm_json.py b/nodes/exchange/export_rw3dm_json.py index 25faa3280c..e0e25f95d2 100644 --- a/nodes/exchange/export_rw3dm_json.py +++ b/nodes/exchange/export_rw3dm_json.py @@ -19,118 +19,121 @@ from geomdl import _exchange from geomdl import multi - class SvExNurbsToJsonOp(bpy.types.Operator): - "NURBS to JSON" - bl_idname = "node.sv_ex_nurbs_to_json" - bl_label = "NURBS to JSON" - bl_options = {'REGISTER', 'INTERNAL'} - - nodename: StringProperty(name='nodename') - treename: StringProperty(name='treename') - - def execute(self, context): - node = bpy.data.node_groups[self.treename].nodes[self.nodename] - - def callback(data): - return json.dumps(data, indent=2) - - geometry = node.get_geometry() - if geometry: - exported_data = _exchange.export_dict_str(obj=geometry, callback=callback) - text_name = node.text_block - self.write_data(text_name, exported_data) - return {'FINISHED'} - else: - self.report({'INFO'}, "No geometry to export") - return {'CANCELLED'} - - def write_data(self, text_name, data): - texts = bpy.data.texts.items() - exists = False - for t in texts: - if bpy.data.texts[t[0]].name == text_name: - exists = True - break - - if not exists: - bpy.data.texts.new(text_name) - bpy.data.texts[text_name].clear() - bpy.data.texts[text_name].write(data) - - class SvExNurbsToJsonNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: NURBS to JSON - Tooltip: Export NURBS to JSON file - """ - bl_idname = 'SvExNurbsToJsonNode' - bl_label = 'NURBS to JSON' - bl_icon = 'CURVE_NCURVE' - - text_block : StringProperty( - name = "Text", - default = "nurbs.json", - update = updateNode) - - modes = [ - ('CURVE', "Curves", "Export set of curves", 0), - ('SURFACE', "Surfaces", "Export set of surfaces", 1) - ] - - def update_sockets(self, context): - self.inputs['Curves'].hide_safe = self.mode != 'CURVE' - self.inputs['Surfaces'].hide_safe = self.mode != 'SURFACE' - updateNode(self, context) - - mode : EnumProperty( - name = "Export", - items = modes, - default = 'CURVE', - update = update_sockets) - - def draw_buttons(self, context, layout): - layout.label(text='Export NURBS:') - layout.prop(self, 'mode', expand=True) - layout.prop_search(self, 'text_block', bpy.data, 'texts', text='', icon='TEXT') - - export = layout.operator('node.sv_ex_nurbs_to_json', text='Export!', icon='EXPORT') - export.nodename = self.name - export.treename = self.id_data.name - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curves") - self.inputs.new('SvSurfaceSocket', "Surfaces") - self.update_sockets(context) - - def process(self): - pass - - def get_geometry(self): - if self.mode == 'CURVE': - curves = self.inputs['Curves'].sv_get() - if isinstance(curves[0], (list, tuple)): - curves = sum(curves, []) - container = multi.CurveContainer() - for i, curve in enumerate(curves): - if not isinstance(curve, SvNurbsCurve): - if hasattr(curve, 'to_nurbs'): - curve = curve.to_nurbs(implementation = SvNurbsCurve.GEOMDL) - else: - raise TypeError("Provided object #%s is not a NURBS curve, but %s!" % (i, type(curve))) - container.append(SvGeomdlCurve.from_any_nurbs(curve).curve) - return container - else: # SURFACE - surfaces = self.inputs['Surfaces'].sv_get() - if isinstance(surfaces[0], (list, tuple)): - surfaces = sum(surfaces, []) - container = multi.SurfaceContainer() - for i, surface in enumerate(surfaces): - if not isinstance(surface, SvNurbsSurface): - if hasattr(surface, 'to_nurbs'): - surface = surface.to_nurbs(implementation = SvNurbsCurve.GEOMDL) - else: - raise TypeError("Provided object #%s is not a NURBS surface, but %s!" % (i, type(surface))) - container.append(SvGeomdlSurface.from_any_nurbs(surface).surface) - return container + +class SvExNurbsToJsonOp(bpy.types.Operator): + "NURBS to JSON" + bl_idname = "node.sv_ex_nurbs_to_json" + bl_label = "NURBS to JSON" + bl_options = {'REGISTER', 'INTERNAL'} + + nodename: StringProperty(name='nodename') + treename: StringProperty(name='treename') + + def execute(self, context): + node = bpy.data.node_groups[self.treename].nodes[self.nodename] + + def callback(data): + return json.dumps(data, indent=2) + + geometry = node.get_geometry() + if geometry: + exported_data = _exchange.export_dict_str(obj=geometry, callback=callback) + text_name = node.text_block + self.write_data(text_name, exported_data) + return {'FINISHED'} + else: + self.report({'INFO'}, "No geometry to export") + return {'CANCELLED'} + + def write_data(self, text_name, data): + texts = bpy.data.texts.items() + exists = False + for t in texts: + if bpy.data.texts[t[0]].name == text_name: + exists = True + break + + if not exists: + bpy.data.texts.new(text_name) + bpy.data.texts[text_name].clear() + bpy.data.texts[text_name].write(data) + + +class SvExNurbsToJsonNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: NURBS to JSON + Tooltip: Export NURBS to JSON file + """ + bl_idname = 'SvExNurbsToJsonNode' + bl_label = 'NURBS to JSON' + bl_icon = 'CURVE_NCURVE' + + text_block : StringProperty( + name = "Text", + default = "nurbs.json", + update = updateNode) + + modes = [ + ('CURVE', "Curves", "Export set of curves", 0), + ('SURFACE', "Surfaces", "Export set of surfaces", 1) + ] + + def update_sockets(self, context): + self.inputs['Curves'].hide_safe = self.mode != 'CURVE' + self.inputs['Surfaces'].hide_safe = self.mode != 'SURFACE' + updateNode(self, context) + + mode : EnumProperty( + name = "Export", + items = modes, + default = 'CURVE', + update = update_sockets) + + def draw_buttons(self, context, layout): + layout.label(text='Export NURBS:') + layout.prop(self, 'mode', expand=True) + layout.prop_search(self, 'text_block', bpy.data, 'texts', text='', icon='TEXT') + + export = layout.operator('node.sv_ex_nurbs_to_json', text='Export!', icon='EXPORT') + export.nodename = self.name + export.treename = self.id_data.name + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curves") + self.inputs.new('SvSurfaceSocket', "Surfaces") + self.update_sockets(context) + + def process(self): + pass + + def get_geometry(self): + if self.mode == 'CURVE': + curves = self.inputs['Curves'].sv_get() + if isinstance(curves[0], (list, tuple)): + curves = sum(curves, []) + container = multi.CurveContainer() + for i, curve in enumerate(curves): + if not isinstance(curve, SvNurbsCurve): + if hasattr(curve, 'to_nurbs'): + curve = curve.to_nurbs(implementation = SvNurbsCurve.GEOMDL) + else: + raise TypeError("Provided object #%s is not a NURBS curve, but %s!" % (i, type(curve))) + container.append(SvGeomdlCurve.from_any_nurbs(curve).curve) + return container + else: # SURFACE + surfaces = self.inputs['Surfaces'].sv_get() + if isinstance(surfaces[0], (list, tuple)): + surfaces = sum(surfaces, []) + container = multi.SurfaceContainer() + for i, surface in enumerate(surfaces): + if not isinstance(surface, SvNurbsSurface): + if hasattr(surface, 'to_nurbs'): + surface = surface.to_nurbs(implementation = SvNurbsCurve.GEOMDL) + else: + raise TypeError("Provided object #%s is not a NURBS surface, but %s!" % (i, type(surface))) + container.append(SvGeomdlSurface.from_any_nurbs(surface).surface) + return container + def register(): if geomdl is not None: diff --git a/nodes/exchange/import_rw3dm_json.py b/nodes/exchange/import_rw3dm_json.py index aed20302b1..ca72674e23 100644 --- a/nodes/exchange/import_rw3dm_json.py +++ b/nodes/exchange/import_rw3dm_json.py @@ -22,58 +22,60 @@ from geomdl import _exchange from geomdl import BSpline - class SvExJsonToNurbsNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: JSON to NURBS - Tooltip: Import NURBS from JSON file - """ - bl_idname = 'SvExJsonToNurbsNode' - bl_label = 'JSON to NURBS' - bl_icon = 'CURVE_NCURVE' - - text_block : StringProperty( - name = "Text", - default = "nurbs.json", - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop_search(self, 'text_block', bpy.data, 'texts', text='', icon='TEXT') - - def sv_init(self, context): - self.outputs.new('SvCurveSocket', "Curves") - self.outputs.new('SvSurfaceSocket', "Surfaces") - - def load_json(self): - def callback(data): - return json.loads(data) - - internal_file = bpy.data.texts[self.text_block] - data = internal_file.as_string() - items = _exchange.import_dict_str(file_src=data, delta=-1.0, callback=callback, tmpl=False) - return items - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - if not self.text_block: - return - - curves_out = [] - surfaces_out = [] - items = self.load_json() - for i, item in enumerate(items): - if isinstance(item, BSpline.Curve): - curve = SvGeomdlCurve(item) - curves_out.append(curve) - elif isinstance(item, BSpline.Surface): - surface = SvGeomdlSurface(item) - surfaces_out.append(surface) - else: - self.warning("JSON data item #%s contains unsupported data type: %s", i, type(item)) - - self.outputs['Curves'].sv_set(curves_out) - self.outputs['Surfaces'].sv_set(surfaces_out) + +class SvExJsonToNurbsNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: JSON to NURBS + Tooltip: Import NURBS from JSON file + """ + bl_idname = 'SvExJsonToNurbsNode' + bl_label = 'JSON to NURBS' + bl_icon = 'CURVE_NCURVE' + + text_block : StringProperty( + name = "Text", + default = "nurbs.json", + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop_search(self, 'text_block', bpy.data, 'texts', text='', icon='TEXT') + + def sv_init(self, context): + self.outputs.new('SvCurveSocket', "Curves") + self.outputs.new('SvSurfaceSocket', "Surfaces") + + def load_json(self): + def callback(data): + return json.loads(data) + + internal_file = bpy.data.texts[self.text_block] + data = internal_file.as_string() + items = _exchange.import_dict_str(file_src=data, delta=-1.0, callback=callback, tmpl=False) + return items + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + if not self.text_block: + return + + curves_out = [] + surfaces_out = [] + items = self.load_json() + for i, item in enumerate(items): + if isinstance(item, BSpline.Curve): + curve = SvGeomdlCurve(item) + curves_out.append(curve) + elif isinstance(item, BSpline.Surface): + surface = SvGeomdlSurface(item) + surfaces_out.append(surface) + else: + self.warning("JSON data item #%s contains unsupported data type: %s", i, type(item)) + + self.outputs['Curves'].sv_set(curves_out) + self.outputs['Surfaces'].sv_set(surfaces_out) + def register(): if geomdl is not None: diff --git a/nodes/field/mesh_normal_field.py b/nodes/field/mesh_normal_field.py index 9901a0b335..09b65bbc56 100644 --- a/nodes/field/mesh_normal_field.py +++ b/nodes/field/mesh_normal_field.py @@ -31,7 +31,6 @@ class SvExMeshNormalFieldNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvExMeshNormalFieldNode' bl_label = 'Mesh Nearest Normal' - bl_icon = 'OUTLINER_OB_EMPTY' interpolate : BoolProperty( name = "Interpolate", diff --git a/nodes/field/mesh_surface_field.py b/nodes/field/mesh_surface_field.py index ba0670190a..e648223bff 100644 --- a/nodes/field/mesh_surface_field.py +++ b/nodes/field/mesh_surface_field.py @@ -28,7 +28,6 @@ class SvMeshSurfaceFieldNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvMeshSurfaceFieldNode' bl_label = 'Mesh Smoothed Surface Field' - bl_icon = 'OUTLINER_OB_EMPTY' function : EnumProperty( name = "Function", diff --git a/nodes/field/minimal_sfield.py b/nodes/field/minimal_sfield.py index bafc153c72..12369adb79 100644 --- a/nodes/field/minimal_sfield.py +++ b/nodes/field/minimal_sfield.py @@ -2,13 +2,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -from mathutils import Matrix +from bpy.props import FloatProperty, EnumProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat from sverchok.utils.field.rbf import SvRbfScalarField from sverchok.utils.math import rbf_functions from sverchok.utils.dummy_nodes import add_dummy @@ -19,81 +16,83 @@ else: from scipy.interpolate import Rbf - class SvExMinimalScalarFieldNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: RBF Minimal Scalar Field - Tooltip: RBF (Minimal) Scalar Field - """ - bl_idname = 'SvExMinimalScalarFieldNode' - bl_label = 'RBF Scalar Field' - bl_icon = 'OUTLINER_OB_EMPTY' - - function : EnumProperty( - name = "Function", - items = rbf_functions, - default = 'multiquadric', - update = updateNode) - - epsilon : FloatProperty( - name = "Epsilon", - default = 1.0, - min = 0.0, - update = updateNode) - - smooth : FloatProperty( - name = "Smooth", - default = 0.0, - min = 0.0, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - self.inputs.new('SvStringsSocket', "Values") - self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' - self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' - self.outputs.new('SvScalarFieldSocket', "Field") - - def draw_buttons(self, context, layout): - layout.prop(self, "function") - - def process(self): - - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - values_s = self.inputs['Values'].sv_get() - epsilon_s = self.inputs['Epsilon'].sv_get() - smooth_s = self.inputs['Smooth'].sv_get() - - fields_out = [] - for vertices, values, epsilon, smooth in zip_long_repeat(vertices_s, values_s, epsilon_s, smooth_s): - if isinstance(epsilon, (list, int)): - epsilon = epsilon[0] - if isinstance(smooth, (list, int)): - smooth = smooth[0] - - XYZ_from = np.array(vertices) - xs_from = XYZ_from[:,0] - ys_from = XYZ_from[:,1] - zs_from = XYZ_from[:,2] - - values = np.array(values) - - rbf = Rbf(xs_from, ys_from, zs_from, values, - function = self.function, - smooth = smooth, - epsilon = epsilon, mode='1-D') - - field = SvRbfScalarField(rbf) - fields_out.append(field) - - self.outputs['Field'].sv_set(fields_out) + +class SvExMinimalScalarFieldNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: RBF Minimal Scalar Field + Tooltip: RBF (Minimal) Scalar Field + """ + bl_idname = 'SvExMinimalScalarFieldNode' + bl_label = 'RBF Scalar Field' + + function : EnumProperty( + name = "Function", + items = rbf_functions, + default = 'multiquadric', + update = updateNode) + + epsilon : FloatProperty( + name = "Epsilon", + default = 1.0, + min = 0.0, + update = updateNode) + + smooth : FloatProperty( + name = "Smooth", + default = 0.0, + min = 0.0, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + self.inputs.new('SvStringsSocket', "Values") + self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' + self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' + self.outputs.new('SvScalarFieldSocket', "Field") + + def draw_buttons(self, context, layout): + layout.prop(self, "function") + + def process(self): + + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + values_s = self.inputs['Values'].sv_get() + epsilon_s = self.inputs['Epsilon'].sv_get() + smooth_s = self.inputs['Smooth'].sv_get() + + fields_out = [] + for vertices, values, epsilon, smooth in zip_long_repeat(vertices_s, values_s, epsilon_s, smooth_s): + if isinstance(epsilon, (list, int)): + epsilon = epsilon[0] + if isinstance(smooth, (list, int)): + smooth = smooth[0] + + XYZ_from = np.array(vertices) + xs_from = XYZ_from[:,0] + ys_from = XYZ_from[:,1] + zs_from = XYZ_from[:,2] + + values = np.array(values) + + rbf = Rbf(xs_from, ys_from, zs_from, values, + function = self.function, + smooth = smooth, + epsilon = epsilon, mode='1-D') + + field = SvRbfScalarField(rbf) + fields_out.append(field) + + self.outputs['Field'].sv_set(fields_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExMinimalScalarFieldNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExMinimalScalarFieldNode) diff --git a/nodes/field/minimal_vfield.py b/nodes/field/minimal_vfield.py index 022bc71cef..9ef3a0b1da 100644 --- a/nodes/field/minimal_vfield.py +++ b/nodes/field/minimal_vfield.py @@ -19,91 +19,92 @@ else: from scipy.interpolate import Rbf - class SvExMinimalVectorFieldNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: RBF Minimal Vector Field - Tooltip: RBF Vector Field - """ - bl_idname = 'SvExMinimalVectorFieldNode' - bl_label = 'RBF Vector Field' - bl_icon = 'OUTLINER_OB_EMPTY' - - function : EnumProperty( - name = "Function", - items = rbf_functions, - default = 'multiquadric', - update = updateNode) - - epsilon : FloatProperty( - name = "Epsilon", - default = 1.0, - min = 0.0, - update = updateNode) - - smooth : FloatProperty( - name = "Smooth", - default = 0.0, - min = 0.0, - update = updateNode) - - types = [ - ('R', "Relative", "Field value in the point means the vector of force applied to this point", 0), - ('A', "Absolute", "Field value in the point means the new point where this point should be moved to", 1) - ] - - field_type : EnumProperty( - name = "Type", - description = "Field type", - items = types, - default = 'R', - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "VerticesFrom") - self.inputs.new('SvVerticesSocket', "VerticesTo") - self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' - self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' - self.outputs.new('SvVectorFieldSocket', "Field") - - def draw_buttons(self, context, layout): - layout.prop(self, "field_type", text='') - layout.prop(self, "function") - - def process(self): - - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_from_s = self.inputs['VerticesFrom'].sv_get() - vertices_to_s = self.inputs['VerticesTo'].sv_get() - epsilon_s = self.inputs['Epsilon'].sv_get() - smooth_s = self.inputs['Smooth'].sv_get() - - fields_out = [] - for vertices_from, vertices_to, epsilon, smooth in zip_long_repeat(vertices_from_s, vertices_to_s, epsilon_s, smooth_s): - if isinstance(epsilon, (list, int)): - epsilon = epsilon[0] - if isinstance(smooth, (list, int)): - smooth = smooth[0] - - XYZ_from = np.array(vertices_from) - xs_from = XYZ_from[:,0] - ys_from = XYZ_from[:,1] - zs_from = XYZ_from[:,2] - - XYZ_to = np.array(vertices_to) - if self.field_type == 'R': - XYZ_to = XYZ_from + XYZ_to - - rbf = Rbf(xs_from, ys_from, zs_from, XYZ_to, - function = self.function, - smooth = smooth, - epsilon = epsilon, mode='N-D') - - field = SvRbfVectorField(rbf, relative = self.field_type == 'R') - fields_out.append(field) - - self.outputs['Field'].sv_set(fields_out) + +class SvExMinimalVectorFieldNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: RBF Minimal Vector Field + Tooltip: RBF Vector Field + """ + bl_idname = 'SvExMinimalVectorFieldNode' + bl_label = 'RBF Vector Field' + + function : EnumProperty( + name = "Function", + items = rbf_functions, + default = 'multiquadric', + update = updateNode) + + epsilon : FloatProperty( + name = "Epsilon", + default = 1.0, + min = 0.0, + update = updateNode) + + smooth : FloatProperty( + name = "Smooth", + default = 0.0, + min = 0.0, + update = updateNode) + + types = [ + ('R', "Relative", "Field value in the point means the vector of force applied to this point", 0), + ('A', "Absolute", "Field value in the point means the new point where this point should be moved to", 1) + ] + + field_type : EnumProperty( + name = "Type", + description = "Field type", + items = types, + default = 'R', + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "VerticesFrom") + self.inputs.new('SvVerticesSocket', "VerticesTo") + self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' + self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' + self.outputs.new('SvVectorFieldSocket', "Field") + + def draw_buttons(self, context, layout): + layout.prop(self, "field_type", text='') + layout.prop(self, "function") + + def process(self): + + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_from_s = self.inputs['VerticesFrom'].sv_get() + vertices_to_s = self.inputs['VerticesTo'].sv_get() + epsilon_s = self.inputs['Epsilon'].sv_get() + smooth_s = self.inputs['Smooth'].sv_get() + + fields_out = [] + for vertices_from, vertices_to, epsilon, smooth in zip_long_repeat(vertices_from_s, vertices_to_s, epsilon_s, smooth_s): + if isinstance(epsilon, (list, int)): + epsilon = epsilon[0] + if isinstance(smooth, (list, int)): + smooth = smooth[0] + + XYZ_from = np.array(vertices_from) + xs_from = XYZ_from[:,0] + ys_from = XYZ_from[:,1] + zs_from = XYZ_from[:,2] + + XYZ_to = np.array(vertices_to) + if self.field_type == 'R': + XYZ_to = XYZ_from + XYZ_to + + rbf = Rbf(xs_from, ys_from, zs_from, XYZ_to, + function = self.function, + smooth = smooth, + epsilon = epsilon, mode='N-D') + + field = SvRbfVectorField(rbf, relative = self.field_type == 'R') + fields_out.append(field) + + self.outputs['Field'].sv_set(fields_out) + def register(): if scipy is not None: diff --git a/nodes/field/scalar_field_graph.py b/nodes/field/scalar_field_graph.py index d8a2152819..cd24b1bdd0 100644 --- a/nodes/field/scalar_field_graph.py +++ b/nodes/field/scalar_field_graph.py @@ -16,154 +16,155 @@ else: from skimage import measure - class SvExScalarFieldGraphNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Scalar Field Graph - Tooltip: Generate a graphical representation of the scalar field - """ - bl_idname = 'SvExScalarFieldGraphNode' - bl_label = 'Scalar Field Graph' - bl_icon = 'OUTLINER_OB_EMPTY' - - samples_xy : IntProperty( - name = "Samples X/Y", - default = 50, - min = 3, - update = updateNode) - - samples_z : IntProperty( - name = "Samples Z", - default = 10, - min = 2, - update = updateNode) - samples_value : IntProperty( - name = "Value samples", - default = 10, - min = 2, +class SvExScalarFieldGraphNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Scalar Field Graph + Tooltip: Generate a graphical representation of the scalar field + """ + bl_idname = 'SvExScalarFieldGraphNode' + bl_label = 'Scalar Field Graph' + + samples_xy : IntProperty( + name = "Samples X/Y", + default = 50, + min = 3, + update = updateNode) + + samples_z : IntProperty( + name = "Samples Z", + default = 10, + min = 2, + update = updateNode) + + samples_value : IntProperty( + name = "Value samples", + default = 10, + min = 2, + update = updateNode) + + join : BoolProperty( + name = "Join", + default = True, + update = updateNode) + + def update_sockets(self, context): + self.outputs['Faces'].hide_safe = not self.make_faces + updateNode(self, context) + + make_faces : BoolProperty( + name = "Make faces", + default = False, + update = update_sockets) + + connect_bounds : BoolProperty( + name = "Connect boundary", + default = False, update = updateNode) - join : BoolProperty( - name = "Join", - default = True, - update = updateNode) + def draw_buttons(self, context, layout): + layout.prop(self, 'make_faces', toggle=True) + layout.prop(self, 'connect_bounds', toggle=True) + layout.prop(self, 'join', toggle=True) + + def sv_init(self, context): + self.inputs.new('SvScalarFieldSocket', "Field") + self.inputs.new('SvVerticesSocket', "Bounds") + self.inputs.new('SvStringsSocket', "SamplesXY").prop_name = 'samples_xy' + self.inputs.new('SvStringsSocket', "SamplesZ").prop_name = 'samples_z' + self.inputs.new('SvStringsSocket', "ValueSamples").prop_name = 'samples_value' + self.outputs.new('SvVerticesSocket', 'Vertices') + self.outputs.new('SvStringsSocket', 'Edges') + self.outputs.new('SvStringsSocket', "Faces") + self.update_sockets(context) + + def get_bounds(self, vertices): + vs = np.array(vertices) + min = vs.min(axis=0) + max = vs.max(axis=0) + return min.tolist(), max.tolist() + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + field_s = self.inputs['Field'].sv_get() + bounds_s = self.inputs['Bounds'].sv_get() + samples_xy_s = self.inputs['SamplesXY'].sv_get() + samples_z_s = self.inputs['SamplesZ'].sv_get() + samples_value_s = self.inputs['ValueSamples'].sv_get() + + verts_out = [] + edges_out = [] + faces_out = [] + + inputs = zip_long_repeat(field_s, bounds_s, samples_xy_s, samples_z_s, samples_value_s) + for field, bounds, samples_xy, samples_z, samples_value in inputs: + if isinstance(samples_xy, (list, tuple)): + samples_xy = samples_xy[0] + if isinstance(samples_z, (list, tuple)): + samples_z = samples_z[0] + if isinstance(samples_value, (list, tuple)): + samples_value = samples_value[0] + + b1, b2 = self.get_bounds(bounds) + min_x, max_x = b1[0], b2[0] + min_y, max_y = b1[1], b2[1] + min_z, max_z = b1[2], b2[2] + + x_size = (max_x - min_x)/samples_xy + y_size = (max_y - min_y)/samples_xy + + x_range = np.linspace(min_x, max_x, num=samples_xy) + y_range = np.linspace(min_y, max_y, num=samples_xy) + z_range = np.linspace(min_z, max_z, num=samples_z) + xs, ys, zs = np.meshgrid(x_range, y_range, z_range, indexing='ij') + xs, ys, zs = xs.flatten(), ys.flatten(), zs.flatten() + field_values = field.evaluate_grid(xs, ys, zs) + min_field = field_values.min() + max_field = field_values.max() + field_values = field_values.reshape((samples_xy, samples_xy, samples_z)) + field_stops = np.linspace(min_field, max_field, num=samples_value) + + new_verts = [] + new_edges = [] + new_faces = [] + for i in range(samples_z): + z_value = zs[i] + z_verts = [] + z_edges = [] + z_faces = [] + for j in range(samples_value): + value = field_stops[j] + contours = measure.find_contours(field_values[:,:,i], level=value) + + value_verts, value_edges, value_faces = make_contours(samples_xy, samples_xy, + min_x, x_size, min_y, y_size, z_value, contours, + make_faces=self.make_faces, connect_bounds = self.connect_bounds) + if value_verts: + z_verts.extend(value_verts) + z_edges.extend(value_edges) + z_faces.extend(value_faces) + + new_verts.extend(z_verts) + new_edges.extend(z_edges) + new_faces.extend(z_faces) + + if self.join: + new_verts, new_edges, new_faces = mesh_join(new_verts, new_edges, new_faces) + + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + else: + verts_out.extend(new_verts) + edges_out.extend(new_edges) + faces_out.extend(new_faces) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['Faces'].sv_set(faces_out) - def update_sockets(self, context): - self.outputs['Faces'].hide_safe = not self.make_faces - updateNode(self, context) - - make_faces : BoolProperty( - name = "Make faces", - default = False, - update = update_sockets) - - connect_bounds : BoolProperty( - name = "Connect boundary", - default = False, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'make_faces', toggle=True) - layout.prop(self, 'connect_bounds', toggle=True) - layout.prop(self, 'join', toggle=True) - - def sv_init(self, context): - self.inputs.new('SvScalarFieldSocket', "Field") - self.inputs.new('SvVerticesSocket', "Bounds") - self.inputs.new('SvStringsSocket', "SamplesXY").prop_name = 'samples_xy' - self.inputs.new('SvStringsSocket', "SamplesZ").prop_name = 'samples_z' - self.inputs.new('SvStringsSocket', "ValueSamples").prop_name = 'samples_value' - self.outputs.new('SvVerticesSocket', 'Vertices') - self.outputs.new('SvStringsSocket', 'Edges') - self.outputs.new('SvStringsSocket', "Faces") - self.update_sockets(context) - - def get_bounds(self, vertices): - vs = np.array(vertices) - min = vs.min(axis=0) - max = vs.max(axis=0) - return min.tolist(), max.tolist() - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - field_s = self.inputs['Field'].sv_get() - bounds_s = self.inputs['Bounds'].sv_get() - samples_xy_s = self.inputs['SamplesXY'].sv_get() - samples_z_s = self.inputs['SamplesZ'].sv_get() - samples_value_s = self.inputs['ValueSamples'].sv_get() - - verts_out = [] - edges_out = [] - faces_out = [] - - inputs = zip_long_repeat(field_s, bounds_s, samples_xy_s, samples_z_s, samples_value_s) - for field, bounds, samples_xy, samples_z, samples_value in inputs: - if isinstance(samples_xy, (list, tuple)): - samples_xy = samples_xy[0] - if isinstance(samples_z, (list, tuple)): - samples_z = samples_z[0] - if isinstance(samples_value, (list, tuple)): - samples_value = samples_value[0] - - b1, b2 = self.get_bounds(bounds) - min_x, max_x = b1[0], b2[0] - min_y, max_y = b1[1], b2[1] - min_z, max_z = b1[2], b2[2] - - x_size = (max_x - min_x)/samples_xy - y_size = (max_y - min_y)/samples_xy - - x_range = np.linspace(min_x, max_x, num=samples_xy) - y_range = np.linspace(min_y, max_y, num=samples_xy) - z_range = np.linspace(min_z, max_z, num=samples_z) - xs, ys, zs = np.meshgrid(x_range, y_range, z_range, indexing='ij') - xs, ys, zs = xs.flatten(), ys.flatten(), zs.flatten() - field_values = field.evaluate_grid(xs, ys, zs) - min_field = field_values.min() - max_field = field_values.max() - field_values = field_values.reshape((samples_xy, samples_xy, samples_z)) - field_stops = np.linspace(min_field, max_field, num=samples_value) - - new_verts = [] - new_edges = [] - new_faces = [] - for i in range(samples_z): - z_value = zs[i] - z_verts = [] - z_edges = [] - z_faces = [] - for j in range(samples_value): - value = field_stops[j] - contours = measure.find_contours(field_values[:,:,i], level=value) - - value_verts, value_edges, value_faces = make_contours(samples_xy, samples_xy, - min_x, x_size, min_y, y_size, z_value, contours, - make_faces=self.make_faces, connect_bounds = self.connect_bounds) - if value_verts: - z_verts.extend(value_verts) - z_edges.extend(value_edges) - z_faces.extend(value_faces) - - new_verts.extend(z_verts) - new_edges.extend(z_edges) - new_faces.extend(z_faces) - - if self.join: - new_verts, new_edges, new_faces = mesh_join(new_verts, new_edges, new_faces) - - verts_out.append(new_verts) - edges_out.append(new_edges) - faces_out.append(new_faces) - else: - verts_out.extend(new_verts) - edges_out.extend(new_edges) - faces_out.extend(new_faces) - - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Edges'].sv_set(edges_out) - self.outputs['Faces'].sv_set(faces_out) def register(): if skimage is not None: diff --git a/nodes/modifier_change/delete_loose.py b/nodes/modifier_change/delete_loose.py index 73e1535478..729b770967 100644 --- a/nodes/modifier_change/delete_loose.py +++ b/nodes/modifier_change/delete_loose.py @@ -29,7 +29,6 @@ class SvDeleteLooseNode(ModifierLiteNode, SverchCustomTreeNode, bpy.types.Node): bl_idname = 'SvDeleteLooseNode' bl_label = 'Delete Loose' - bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_DELETE_LOOSE' def sv_init(self, context): diff --git a/nodes/solid/area.py b/nodes/solid/area.py index e9078fb501..022d745129 100644 --- a/nodes/solid/area.py +++ b/nodes/solid/area.py @@ -27,7 +27,7 @@ class SvSolidAreaNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid Area' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_AREA' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSolidSocket', "Solid") diff --git a/nodes/solid/bound_box.py b/nodes/solid/bound_box.py index 374a3f8109..9408daa4a3 100644 --- a/nodes/solid/bound_box.py +++ b/nodes/solid/bound_box.py @@ -28,7 +28,7 @@ class SvSolidBoundBoxNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid Bounding Box' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_BOUNDING_BOX' - solid_catergory = "Operators" + sv_category = "Solid Operators" def _get_socket(self, axis, key): return self.outputs[axis + key] diff --git a/nodes/solid/box_solid.py b/nodes/solid/box_solid.py index ce29a0e75d..f8da7ea14c 100644 --- a/nodes/solid/box_solid.py +++ b/nodes/solid/box_solid.py @@ -1,98 +1,99 @@ +import bpy +from bpy.props import FloatProperty, FloatVectorProperty, EnumProperty from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.data_structure import match_long_repeat as mlr if FreeCAD is None: add_dummy('SvBoxSolidNode', 'Box (Solid)', 'FreeCAD') else: - import bpy - from bpy.props import FloatProperty, FloatVectorProperty, EnumProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode - from sverchok.data_structure import match_long_repeat as mlr import Part from FreeCAD import Base - class SvBoxSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Box - Tooltip: Generate Solid Box - """ - bl_idname = 'SvBoxSolidNode' - bl_label = 'Box (Solid)' - bl_icon = 'META_CUBE' - solid_catergory = "Inputs" - box_length: FloatProperty( - name="Length", - default=1, - precision=4, - update=updateNode) - box_width: FloatProperty( - name="Width", - default=1, - precision=4, - update=updateNode) - box_height: FloatProperty( - name="Height", - default=1, - precision=4, - update=updateNode) - - origin: FloatVectorProperty( - name="Origin", - default=(0, 0, 0), - size=3, - update=updateNode) - direction: FloatVectorProperty( - name="Direction", - default=(0, 0, 1), - size=3, - update=updateNode) - - origin_options = [ - ('CORNER', "Corner", "`Origin` input defines the location of box's corner with smallest X, Y, Z coordinate values", 0), - ('CENTER', "Center", "`Origin` input defines the location of box's center", 1), - ('BOTTOM', "Bottom", "`Origin` input defines the location of center of box's bottom face", 2) - ] - - origin_type : EnumProperty( - name = "Origin type", - description = "Which point of the box is defined by `Origin` input", - items = origin_options, - default = 'CORNER', - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvStringsSocket', "Length").prop_name = 'box_length' - self.inputs.new('SvStringsSocket', "Width").prop_name = 'box_width' - self.inputs.new('SvStringsSocket', "Height").prop_name = 'box_height' - self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' - self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' - self.outputs.new('SvSolidSocket', "Solid") - - def draw_buttons(self, context, layout): - layout.prop(self, 'origin_type') - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - p = [s.sv_get()[0] for s in self.inputs] - - solids = [] - for l, w, h, o ,d in zip(*mlr(p)): - origin = Base.Vector(o) - if self.origin_type == 'CENTER': - dc = Base.Vector(l/2.0, w/2.0, h/2.0) - origin = origin - dc - elif self.origin_type == 'BOTTOM': - dc = Base.Vector(l/2.0, w/2.0, 0) - origin = origin - dc - box = Part.makeBox(l, w, h, origin, Base.Vector(d)) - solids.append(box) - - self.outputs['Solid'].sv_set(solids) + +class SvBoxSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Box + Tooltip: Generate Solid Box + """ + bl_idname = 'SvBoxSolidNode' + bl_label = 'Box (Solid)' + bl_icon = 'META_CUBE' + sv_category = "Solid Inputs" + box_length: FloatProperty( + name="Length", + default=1, + precision=4, + update=updateNode) + box_width: FloatProperty( + name="Width", + default=1, + precision=4, + update=updateNode) + box_height: FloatProperty( + name="Height", + default=1, + precision=4, + update=updateNode) + + origin: FloatVectorProperty( + name="Origin", + default=(0, 0, 0), + size=3, + update=updateNode) + direction: FloatVectorProperty( + name="Direction", + default=(0, 0, 1), + size=3, + update=updateNode) + + origin_options = [ + ('CORNER', "Corner", "`Origin` input defines the location of box's corner with smallest X, Y, Z coordinate values", 0), + ('CENTER', "Center", "`Origin` input defines the location of box's center", 1), + ('BOTTOM', "Bottom", "`Origin` input defines the location of center of box's bottom face", 2) + ] + + origin_type : EnumProperty( + name = "Origin type", + description = "Which point of the box is defined by `Origin` input", + items = origin_options, + default = 'CORNER', + update=updateNode) + + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', "Length").prop_name = 'box_length' + self.inputs.new('SvStringsSocket', "Width").prop_name = 'box_width' + self.inputs.new('SvStringsSocket', "Height").prop_name = 'box_height' + self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' + self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' + self.outputs.new('SvSolidSocket', "Solid") + + def draw_buttons(self, context, layout): + layout.prop(self, 'origin_type') + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + p = [s.sv_get()[0] for s in self.inputs] + + solids = [] + for l, w, h, o ,d in zip(*mlr(p)): + origin = Base.Vector(o) + if self.origin_type == 'CENTER': + dc = Base.Vector(l/2.0, w/2.0, h/2.0) + origin = origin - dc + elif self.origin_type == 'BOTTOM': + dc = Base.Vector(l/2.0, w/2.0, 0) + origin = origin - dc + box = Part.makeBox(l, w, h, origin, Base.Vector(d)) + solids.append(box) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/center_of_mass.py b/nodes/solid/center_of_mass.py index ed44cff20c..c3dbe46c29 100644 --- a/nodes/solid/center_of_mass.py +++ b/nodes/solid/center_of_mass.py @@ -25,8 +25,7 @@ class SvSolidCenterOfMassNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvSolidCenterOfMassNode' bl_label = 'Center of Mass' - bl_icon = 'OUTLINER_OB_EMPTY' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSolidSocket', "Solid") diff --git a/nodes/solid/chamfer_solid.py b/nodes/solid/chamfer_solid.py index 5d1d7208b8..a16b612cd3 100644 --- a/nodes/solid/chamfer_solid.py +++ b/nodes/solid/chamfer_solid.py @@ -1,75 +1,73 @@ +import bpy +from bpy.props import FloatProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.data_structure import match_long_repeat as mlr, fullList from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy + if FreeCAD is None: add_dummy('SvChamferSolidNode', 'Chamfer Solid', 'FreeCAD') -else: - import bpy - from sverchok.node_tree import SverchCustomTreeNode - from bpy.props import FloatProperty, StringProperty - from sverchok.data_structure import updateNode - from sverchok.data_structure import match_long_repeat as mlr, fullList - - - class SvChamferSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Straight Bevel - Tooltip: Sraight cut in solid edge - """ - bl_idname = 'SvChamferSolidNode' - bl_label = 'Chamfer Solid' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_CHAMFER_SOLID' - solid_catergory = "Operators" - - replacement_nodes = [('SvFilletSolidNode', None, None)] - - distance_a: FloatProperty( - name="Distance A", - default=0.1, - min=1e-6, - precision=4, - update=updateNode) - distance_b: FloatProperty( - name="Distance B", - default=0.1, - min=1e-6, - precision=4, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvStringsSocket', "Distance A").prop_name="distance_a" - self.inputs.new('SvStringsSocket', "Distance B").prop_name="distance_b" - self.inputs.new('SvStringsSocket', "Mask") - self.outputs.new('SvSolidSocket', "Solid") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get() - distance_a_s = self.inputs[1].sv_get()[0] - distance_b_s = self.inputs[2].sv_get()[0] - mask_s = self.inputs[3].sv_get(default=[[1]]) - solids = [] - for solid, d_a, d_b, mask in zip(*mlr([solids_in, distance_a_s, distance_b_s, mask_s])): - - selected_edges = [] - fullList(mask, len(solid.Edges)) - - for edge, m in zip(solid.Edges, mask): - if m: - selected_edges.append(edge) - solid_o = solid.makeChamfer(d_a, d_b, selected_edges) - solids.append(solid_o) - - - self.outputs['Solid'].sv_set(solids) +class SvChamferSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Straight Bevel + Tooltip: Sraight cut in solid edge + """ + bl_idname = 'SvChamferSolidNode' + bl_label = 'Chamfer Solid' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_CHAMFER_SOLID' + sv_category = "Solid Operators" + + replacement_nodes = [('SvFilletSolidNode', None, None)] + + distance_a: FloatProperty( + name="Distance A", + default=0.1, + min=1e-6, + precision=4, + update=updateNode) + distance_b: FloatProperty( + name="Distance B", + default=0.1, + min=1e-6, + precision=4, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvStringsSocket', "Distance A").prop_name="distance_a" + self.inputs.new('SvStringsSocket', "Distance B").prop_name="distance_b" + self.inputs.new('SvStringsSocket', "Mask") + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get() + distance_a_s = self.inputs[1].sv_get()[0] + distance_b_s = self.inputs[2].sv_get()[0] + mask_s = self.inputs[3].sv_get(default=[[1]]) + solids = [] + for solid, d_a, d_b, mask in zip(*mlr([solids_in, distance_a_s, distance_b_s, mask_s])): + + selected_edges = [] + fullList(mask, len(solid.Edges)) + + for edge, m in zip(solid.Edges, mask): + if m: + selected_edges.append(edge) + solid_o = solid.makeChamfer(d_a, d_b, selected_edges) + solids.append(solid_o) + + + self.outputs['Solid'].sv_set(solids) + def register(): if FreeCAD is not None: diff --git a/nodes/solid/cone_solid.py b/nodes/solid/cone_solid.py index 1f9da6aade..a7038b2429 100644 --- a/nodes/solid/cone_solid.py +++ b/nodes/solid/cone_solid.py @@ -1,88 +1,84 @@ +import bpy +from bpy.props import FloatProperty, FloatVectorProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvConeSolidNode', 'Cone (Solid)', 'FreeCAD') else: - - import bpy - from bpy.props import FloatProperty, FloatVectorProperty, StringProperty - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr import Part from FreeCAD import Base - class SvConeSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Cylinder - Tooltip: Create Solid cylinder - """ - bl_idname = 'SvConeSolidNode' - bl_label = 'Cone (Solid)' - bl_icon = 'MESH_CONE' - solid_catergory = "Inputs" - - cylinder_radius: FloatProperty( - name="Radius Bottom", - default=1, - precision=4, - update=updateNode) - cylinder_radius_top: FloatProperty( - name="Radius Top", - default=0, - precision=4, - update=updateNode) - cylinder_height: FloatProperty( - name="Height", - default=1, - precision=4, - update=updateNode) - cylinder_angle: FloatProperty( - name="Angle", - default=360, - precision=4, - update=updateNode) - - origin: FloatVectorProperty( - name="Origin", - default=(0, 0, 0), - size=3, - update=updateNode) - direction: FloatVectorProperty( - name="Origin", - default=(0, 0, 1), - size=3, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvStringsSocket', "Radius Bottom").prop_name = 'cylinder_radius' - self.inputs.new('SvStringsSocket', "Radius Top").prop_name = 'cylinder_radius_top' - self.inputs.new('SvStringsSocket', "Height").prop_name = 'cylinder_height' - self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' - self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' - self.inputs.new('SvStringsSocket', "Angle").prop_name = 'cylinder_angle' - self.outputs.new('SvSolidSocket', "Solid") - - - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - p = [s.sv_get()[0] for s in self.inputs] - - solids = [] - for rad, rad_top, height, origin, direc, angle in zip(*mlr(p)): - if rad_top == rad: - solid = Part.makeCylinder(rad, height, Base.Vector(origin), Base.Vector(direc), angle) - else: - solid = Part.makeCone(rad, rad_top, height, Base.Vector(origin), Base.Vector(direc), angle) - solids.append(solid) - self.outputs['Solid'].sv_set(solids) +class SvConeSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Cylinder + Tooltip: Create Solid cylinder + """ + bl_idname = 'SvConeSolidNode' + bl_label = 'Cone (Solid)' + bl_icon = 'MESH_CONE' + sv_category = "Solid Inputs" + + cylinder_radius: FloatProperty( + name="Radius Bottom", + default=1, + precision=4, + update=updateNode) + cylinder_radius_top: FloatProperty( + name="Radius Top", + default=0, + precision=4, + update=updateNode) + cylinder_height: FloatProperty( + name="Height", + default=1, + precision=4, + update=updateNode) + cylinder_angle: FloatProperty( + name="Angle", + default=360, + precision=4, + update=updateNode) + + origin: FloatVectorProperty( + name="Origin", + default=(0, 0, 0), + size=3, + update=updateNode) + direction: FloatVectorProperty( + name="Origin", + default=(0, 0, 1), + size=3, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', "Radius Bottom").prop_name = 'cylinder_radius' + self.inputs.new('SvStringsSocket', "Radius Top").prop_name = 'cylinder_radius_top' + self.inputs.new('SvStringsSocket', "Height").prop_name = 'cylinder_height' + self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' + self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' + self.inputs.new('SvStringsSocket', "Angle").prop_name = 'cylinder_angle' + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + p = [s.sv_get()[0] for s in self.inputs] + + solids = [] + for rad, rad_top, height, origin, direc, angle in zip(*mlr(p)): + if rad_top == rad: + solid = Part.makeCylinder(rad, height, Base.Vector(origin), Base.Vector(direc), angle) + else: + solid = Part.makeCone(rad, rad_top, height, Base.Vector(origin), Base.Vector(direc), angle) + solids.append(solid) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/cylinder_solid.py b/nodes/solid/cylinder_solid.py index 143a9bd2fb..ee55d7143d 100644 --- a/nodes/solid/cylinder_solid.py +++ b/nodes/solid/cylinder_solid.py @@ -1,79 +1,77 @@ +import bpy +from bpy.props import FloatProperty, FloatVectorProperty from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr + if FreeCAD is None: add_dummy('SvCylinderSolidNode', 'Cylinder (Solid)', 'FreeCAD') else: - import bpy - from bpy.props import FloatProperty, FloatVectorProperty - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr import Part from FreeCAD import Base - class SvCylinderSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Cylinder - Tooltip: Create Solid cylinder - """ - bl_idname = 'SvCylinderSolidNode' - bl_label = 'Cylinder (Solid)' - bl_icon = 'META_CAPSULE' - solid_catergory = "Inputs" - - cylinder_radius: FloatProperty( - name="Radius", - default=1, - precision=4, - update=updateNode) - cylinder_height: FloatProperty( - name="Height", - default=1, - precision=4, - update=updateNode) - cylinder_angle: FloatProperty( - name="Angle", - default=360, - precision=4, - update=updateNode) - - origin: FloatVectorProperty( - name="Origin", - default=(0, 0, 0), - size=3, - update=updateNode) - direction: FloatVectorProperty( - name="Direction", - default=(0, 0, 1), - size=3, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvStringsSocket', "Radius").prop_name = 'cylinder_radius' - self.inputs.new('SvStringsSocket', "Height").prop_name = 'cylinder_height' - self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' - self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' - self.inputs.new('SvStringsSocket', "Angle").prop_name = 'cylinder_angle' - self.outputs.new('SvSolidSocket', "Solid") - - - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - params = [s.sv_get()[0] for s in self.inputs] - - solids = [] - for rad, height, origin, direc, angle in zip(*mlr(params)): - cylinder = Part.makeCylinder(rad, height, Base.Vector(origin), Base.Vector(direc), angle) - solids.append(cylinder) - - self.outputs['Solid'].sv_set(solids) + +class SvCylinderSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Cylinder + Tooltip: Create Solid cylinder + """ + bl_idname = 'SvCylinderSolidNode' + bl_label = 'Cylinder (Solid)' + bl_icon = 'META_CAPSULE' + sv_category = "Solid Inputs" + + cylinder_radius: FloatProperty( + name="Radius", + default=1, + precision=4, + update=updateNode) + cylinder_height: FloatProperty( + name="Height", + default=1, + precision=4, + update=updateNode) + cylinder_angle: FloatProperty( + name="Angle", + default=360, + precision=4, + update=updateNode) + + origin: FloatVectorProperty( + name="Origin", + default=(0, 0, 0), + size=3, + update=updateNode) + direction: FloatVectorProperty( + name="Direction", + default=(0, 0, 1), + size=3, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', "Radius").prop_name = 'cylinder_radius' + self.inputs.new('SvStringsSocket', "Height").prop_name = 'cylinder_height' + self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' + self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' + self.inputs.new('SvStringsSocket', "Angle").prop_name = 'cylinder_angle' + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + params = [s.sv_get()[0] for s in self.inputs] + + solids = [] + for rad, height, origin, direc, angle in zip(*mlr(params)): + cylinder = Part.makeCylinder(rad, height, Base.Vector(origin), Base.Vector(direc), angle) + solids.append(cylinder) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/export_solid.py b/nodes/solid/export_solid.py index 7c205cce51..c1ad783e20 100644 --- a/nodes/solid/export_solid.py +++ b/nodes/solid/export_solid.py @@ -27,94 +27,95 @@ except ImportError: PartModule = Part - from sverchok.utils.solid import to_solid - - class SvExportSolidOperator(bpy.types.Operator, SvGenericNodeLocator): - - bl_idname = "node.sv_export_solid_mk2" - bl_label = "Export Solid" - bl_options = {'INTERNAL', 'REGISTER'} - - def execute(self, context): - node = self.get_node(context) - if not node: return {'CANCELLED'} - - if not node.inputs['Folder Path'].is_linked: - self.report({'WARNING'}, "Folder path is not specified") - return {'FINISHED'} - if not node.inputs['Solids'].is_linked: - self.report({'WARNING'}, "Object to be exported is not specified") - return {'FINISHED'} - - folder_path = node.inputs[0].sv_get()[0][0] - objects = node.inputs['Solids'].sv_get() - #objects = flatten_data(objects, data_types=(PartModule.Shape, SvCurve, SvSurface)) - base_name = node.base_name - if not base_name: - base_name = "sv_solid" - for i, shape in enumerate(objects): - #shape = map_recursive(to_solid, object, data_types=(PartModule.Shape, SvCurve, SvSurface)) - debug("Exporting", shape) - if isinstance(shape, (list, tuple)): - shape = flatten_data(shape, data_types=(PartModule.Shape,)) - if isinstance(shape, (list,tuple)): - debug("Make compound:", shape) - shape = PartModule.Compound(shape) - file_path = folder_path + base_name + "_" + "%05d" % i - - if node.mode == "BREP": - file_path += ".brp" - shape.exportBrep(file_path) - elif node.mode == "IGES": - file_path += ".igs" - shape.exportIges(file_path) - else: - file_path += ".stp" - shape.exportStep(file_path) - self.report({'INFO'}, f"Saved object #{i} to {file_path}") +class SvExportSolidOperator(bpy.types.Operator, SvGenericNodeLocator): + + bl_idname = "node.sv_export_solid_mk2" + bl_label = "Export Solid" + bl_options = {'INTERNAL', 'REGISTER'} + + def execute(self, context): + node = self.get_node(context) + if not node: return {'CANCELLED'} + + if not node.inputs['Folder Path'].is_linked: + self.report({'WARNING'}, "Folder path is not specified") return {'FINISHED'} + if not node.inputs['Solids'].is_linked: + self.report({'WARNING'}, "Object to be exported is not specified") + return {'FINISHED'} + + folder_path = node.inputs[0].sv_get()[0][0] + objects = node.inputs['Solids'].sv_get() + #objects = flatten_data(objects, data_types=(PartModule.Shape, SvCurve, SvSurface)) + base_name = node.base_name + if not base_name: + base_name = "sv_solid" + for i, shape in enumerate(objects): + #shape = map_recursive(to_solid, object, data_types=(PartModule.Shape, SvCurve, SvSurface)) + debug("Exporting", shape) + if isinstance(shape, (list, tuple)): + shape = flatten_data(shape, data_types=(PartModule.Shape,)) + if isinstance(shape, (list,tuple)): + debug("Make compound:", shape) + shape = PartModule.Compound(shape) + file_path = folder_path + base_name + "_" + "%05d" % i + + if node.mode == "BREP": + file_path += ".brp" + shape.exportBrep(file_path) + elif node.mode == "IGES": + file_path += ".igs" + shape.exportIges(file_path) + else: + file_path += ".stp" + shape.exportStep(file_path) + self.report({'INFO'}, f"Saved object #{i} to {file_path}") + + return {'FINISHED'} + + +class SvExportSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Export Solid + Tooltip: Export Solid to BREP, IGES or STEP + """ + bl_idname = 'SvExportSolidNode' + bl_label = 'Export Solid' + bl_icon = 'EXPORT' + sv_category = "Solid Outputs" + # sv_icon = 'SV_VORONOI' + + file_types = [ + ("BREP", "BREP", "", 0), + ("IGES", "IGES", "", 1), + ("STEP", "STEP", "", 2), + ] + + mode: EnumProperty( + name="File Type", + description="Choose file type", + items=file_types, + default="BREP", + ) + + base_name: StringProperty( + name="Base Name", + description="Name of file", + ) + + def draw_buttons(self, context, layout): + layout.prop(self, "mode") + layout.prop(self, "base_name") + self.wrapper_tracked_ui_draw_op(layout, SvExportSolidOperator.bl_idname, icon='EXPORT', text="EXPORT") + + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "Folder Path") + self.inputs.new('SvSolidSocket', "Solids") + + def process(self): + pass - class SvExportSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Export Solid - Tooltip: Export Solid to BREP, IGES or STEP - """ - bl_idname = 'SvExportSolidNode' - bl_label = 'Export Solid' - bl_icon = 'EXPORT' - solid_catergory = "Outputs" - # sv_icon = 'SV_VORONOI' - - file_types = [ - ("BREP", "BREP", "", 0), - ("IGES", "IGES", "", 1), - ("STEP", "STEP", "", 2), - ] - - mode: EnumProperty( - name="File Type", - description="Choose file type", - items=file_types, - default="BREP", - ) - - base_name: StringProperty( - name="Base Name", - description="Name of file", - ) - - def draw_buttons(self, context, layout): - layout.prop(self, "mode") - layout.prop(self, "base_name") - self.wrapper_tracked_ui_draw_op(layout, SvExportSolidOperator.bl_idname, icon='EXPORT', text="EXPORT") - - def sv_init(self, context): - self.inputs.new('SvFilePathSocket', "Folder Path") - self.inputs.new('SvSolidSocket', "Solids") - - def process(self): - pass def register(): if FreeCAD is not None: diff --git a/nodes/solid/extrude_face.py b/nodes/solid/extrude_face.py index 037ff835b8..21faacc225 100644 --- a/nodes/solid/extrude_face.py +++ b/nodes/solid/extrude_face.py @@ -33,7 +33,7 @@ class SvSolidFaceExtrudeNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Extrude Face (Solid)' bl_icon = 'EDGESEL' sv_icon = 'SV_EXTRUDE_FACE' - solid_catergory = "Operators" + sv_category = "Solid Operators" refine_solid: BoolProperty( name="Refine Solid", diff --git a/nodes/solid/face_area.py b/nodes/solid/face_area.py index 07f1bb312a..c2606eb92b 100644 --- a/nodes/solid/face_area.py +++ b/nodes/solid/face_area.py @@ -29,7 +29,7 @@ class SvSolidFaceAreaNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid Face Area' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_AREA' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSurfaceSocket', "SolidFace") diff --git a/nodes/solid/fillet_solid.py b/nodes/solid/fillet_solid.py index 89d79e4380..6d9f191d99 100644 --- a/nodes/solid/fillet_solid.py +++ b/nodes/solid/fillet_solid.py @@ -1,75 +1,70 @@ +import bpy +from bpy.props import FloatProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr, fullList from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvFilletSolidNode', 'Fillet Solid', 'FreeCAD') -else: - import bpy - from bpy.props import FloatProperty, StringProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr, fullList - import Part - from FreeCAD import Base - - class SvFilletSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Bevel Solid - Tooltip: Transform Solid with Matrix - """ - bl_idname = 'SvFilletSolidNode' - bl_label = 'Fillet Solid' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_FILLET_SOLID' - solid_catergory = "Operators" - - replacement_nodes = [('SvChamferSolidNode', None, None)] - - radius_start: FloatProperty( - name="Radius Start", - default=0.1, - precision=4, - update=updateNode) - radius_end: FloatProperty( - name="Radius End", - default=0.1, - precision=4, - update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvStringsSocket', "Radius Start").prop_name = "radius_start" - self.inputs.new('SvStringsSocket', "Radius End").prop_name = "radius_end" - self.inputs.new('SvStringsSocket', "Mask") - self.outputs.new('SvSolidSocket', "Solid") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get() - radius_start_s = self.inputs[1].sv_get()[0] - radius_end_s = self.inputs[2].sv_get()[0] - mask_s = self.inputs[3].sv_get(default=[[1]]) - solids = [] - for solid, r_s, r_e, mask in zip(*mlr([solids_in, radius_start_s, radius_end_s, mask_s])): - - selected_edges = [] - fullList(mask, len(solid.Edges)) - - for edge, m in zip(solid.Edges, mask): - if m: - selected_edges.append(edge) - if selected_edges: - solid_o = solid.makeFillet(r_s, r_e, selected_edges) - else: - solid_o = solid - solids.append(solid_o) - - - self.outputs['Solid'].sv_set(solids) +class SvFilletSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Bevel Solid + Tooltip: Transform Solid with Matrix + """ + bl_idname = 'SvFilletSolidNode' + bl_label = 'Fillet Solid' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_FILLET_SOLID' + sv_category = "Solid Operators" + + replacement_nodes = [('SvChamferSolidNode', None, None)] + + radius_start: FloatProperty( + name="Radius Start", + default=0.1, + precision=4, + update=updateNode) + radius_end: FloatProperty( + name="Radius End", + default=0.1, + precision=4, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvStringsSocket', "Radius Start").prop_name = "radius_start" + self.inputs.new('SvStringsSocket', "Radius End").prop_name = "radius_end" + self.inputs.new('SvStringsSocket', "Mask") + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get() + radius_start_s = self.inputs[1].sv_get()[0] + radius_end_s = self.inputs[2].sv_get()[0] + mask_s = self.inputs[3].sv_get(default=[[1]]) + solids = [] + for solid, r_s, r_e, mask in zip(*mlr([solids_in, radius_start_s, radius_end_s, mask_s])): + + selected_edges = [] + fullList(mask, len(solid.Edges)) + + for edge, m in zip(solid.Edges, mask): + if m: + selected_edges.append(edge) + if selected_edges: + solid_o = solid.makeFillet(r_s, r_e, selected_edges) + else: + solid_o = solid + solids.append(solid_o) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/general_fuse.py b/nodes/solid/general_fuse.py index 8a6982ec37..8e14e89621 100644 --- a/nodes/solid/general_fuse.py +++ b/nodes/solid/general_fuse.py @@ -30,7 +30,7 @@ class SvSolidGeneralFuseNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid General Fuse' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_GENERAL_FUSE' - solid_catergory = "Operators" + sv_category = "Solid Operators" def update_sockets(self, context): hide_masks = self.merge_result and self.refine_solid diff --git a/nodes/solid/hollow_solid.py b/nodes/solid/hollow_solid.py index 67161defca..e9dfae44cb 100644 --- a/nodes/solid/hollow_solid.py +++ b/nodes/solid/hollow_solid.py @@ -33,7 +33,7 @@ class SvHollowSolidNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Hollow Solid' bl_icon = 'MOD_THICKNESS' sv_icon = 'SV_HOLLOW_SOLID' - solid_catergory = "Operators" + sv_category = "Solid Operators" thickness : FloatProperty( name = "Thickness", diff --git a/nodes/solid/import_solid.py b/nodes/solid/import_solid.py index 0b450e4512..cc2074bece 100644 --- a/nodes/solid/import_solid.py +++ b/nodes/solid/import_solid.py @@ -1,43 +1,43 @@ +import bpy +from sverchok.node_tree import SverchCustomTreeNode from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvImportSolidNode', 'Import Solid', 'FreeCAD') else: - import bpy - from sverchok.node_tree import SverchCustomTreeNode import Part - class SvImportSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Import Solid - Tooltip: Import Solid from BREP file - """ - bl_idname = 'SvImportSolidNode' - bl_label = 'Import Solid' - bl_icon = 'IMPORT' - solid_catergory = "Inputs" - - def sv_init(self, context): - self.inputs.new('SvFilePathSocket', "File Path") - self.outputs.new('SvSolidSocket', "Solid") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - files = self.inputs[0].sv_get()[0] - - solids = [] - for f in files: - shape = Part.Shape() - shape.importBrep(f) - solid = Part.makeSolid(shape) - solids.append(solid) - - self.outputs['Solid'].sv_set(solids) +class SvImportSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Import Solid + Tooltip: Import Solid from BREP file + """ + bl_idname = 'SvImportSolidNode' + bl_label = 'Import Solid' + bl_icon = 'IMPORT' + sv_category = "Solid Inputs" + + def sv_init(self, context): + self.inputs.new('SvFilePathSocket', "File Path") + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + files = self.inputs[0].sv_get()[0] + + solids = [] + for f in files: + shape = Part.Shape() + shape.importBrep(f) + solid = Part.makeSolid(shape) + solids.append(solid) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/is_closed.py b/nodes/solid/is_closed.py index c0883fa2d5..69e3968e1c 100644 --- a/nodes/solid/is_closed.py +++ b/nodes/solid/is_closed.py @@ -25,8 +25,7 @@ class SvIsSolidClosedNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvIsSolidClosedNode' bl_label = 'Is Solid Closed' - bl_icon = 'OUTLINER_OB_EMPTY' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSolidSocket', "Solid") diff --git a/nodes/solid/make_compound.py b/nodes/solid/make_compound.py index b7971c8cd0..911f7bc330 100644 --- a/nodes/solid/make_compound.py +++ b/nodes/solid/make_compound.py @@ -21,58 +21,59 @@ from sverchok.utils.solid import to_solid - class SvCompoundSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Compound Solid - Tooltip: Make Compound Solid object out of Solids, Curves and Surfaces - """ - bl_idname = 'SvCompoundSolidNode' - bl_label = 'Compound Solid' - bl_icon = 'STICKY_UVS_LOC' - solid_catergory = "Outputs" - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solids") - self.inputs.new('SvCurveSocket', "Curves") - self.inputs.new('SvSurfaceSocket', "Surfaces") - self.outputs.new('SvSolidSocket', "Compound") - - def process(self): - if not self.outputs['Compound'].is_linked: - return - if not any(sock.is_linked for sock in self.inputs): - return - - solids_s = self.inputs['Solids'].sv_get(default=[[None]]) - curves_s = self.inputs['Curves'].sv_get(default=[[None]]) - surfaces_s = self.inputs['Surfaces'].sv_get(default=[[None]]) - - - if self.inputs['Solids'].is_linked: - solids_s = ensure_nesting_level(solids_s, 2, data_types=(PartModule.Shape,)) - solids_level = get_data_nesting_level(solids_s, data_types=(PartModule.Shape,)) - else: - solids_level = 2 - if self.inputs['Curves'].is_linked: - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - curves_level = get_data_nesting_level(curves_s, data_types=(SvCurve,)) - else: - curves_level = 2 - if self.inputs['Surfaces'].is_linked: - surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) - surfaces_level = get_data_nesting_level(surfaces_s, data_types=(SvSurface,)) - else: - surfaces_level = 2 - - max_level = max(solids_level, curves_level, surfaces_level) - compounds_out = [] - for solids, curves, surfaces in zip_long_repeat(solids_s, curves_s, surfaces_s): - shapes = solids + curves + surfaces - shapes = [to_solid(s) for s in shapes if s is not None] - compound = PartModule.Compound(shapes) - compounds_out.append(compound) - - self.outputs['Compound'].sv_set(compounds_out) + +class SvCompoundSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Compound Solid + Tooltip: Make Compound Solid object out of Solids, Curves and Surfaces + """ + bl_idname = 'SvCompoundSolidNode' + bl_label = 'Compound Solid' + bl_icon = 'STICKY_UVS_LOC' + sv_category = "Solid Outputs" + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solids") + self.inputs.new('SvCurveSocket', "Curves") + self.inputs.new('SvSurfaceSocket', "Surfaces") + self.outputs.new('SvSolidSocket', "Compound") + + def process(self): + if not self.outputs['Compound'].is_linked: + return + if not any(sock.is_linked for sock in self.inputs): + return + + solids_s = self.inputs['Solids'].sv_get(default=[[None]]) + curves_s = self.inputs['Curves'].sv_get(default=[[None]]) + surfaces_s = self.inputs['Surfaces'].sv_get(default=[[None]]) + + if self.inputs['Solids'].is_linked: + solids_s = ensure_nesting_level(solids_s, 2, data_types=(PartModule.Shape,)) + solids_level = get_data_nesting_level(solids_s, data_types=(PartModule.Shape,)) + else: + solids_level = 2 + if self.inputs['Curves'].is_linked: + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + curves_level = get_data_nesting_level(curves_s, data_types=(SvCurve,)) + else: + curves_level = 2 + if self.inputs['Surfaces'].is_linked: + surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) + surfaces_level = get_data_nesting_level(surfaces_s, data_types=(SvSurface,)) + else: + surfaces_level = 2 + + max_level = max(solids_level, curves_level, surfaces_level) + compounds_out = [] + for solids, curves, surfaces in zip_long_repeat(solids_s, curves_s, surfaces_s): + shapes = solids + curves + surfaces + shapes = [to_solid(s) for s in shapes if s is not None] + compound = PartModule.Compound(shapes) + compounds_out.append(compound) + + self.outputs['Compound'].sv_set(compounds_out) + def register(): if FreeCAD is not None: diff --git a/nodes/solid/mesh_to_solid.py b/nodes/solid/mesh_to_solid.py index 7ec0d5e81a..c47563fed0 100644 --- a/nodes/solid/mesh_to_solid.py +++ b/nodes/solid/mesh_to_solid.py @@ -1,118 +1,114 @@ +import bpy +from bpy.props import FloatProperty, BoolProperty +from mathutils import Vector +from mathutils.geometry import tessellate_polygon as tessellate +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvMeshToSolidNode', 'Mesh to Solid', 'FreeCAD') else: - import bpy - from bpy.props import FloatProperty, BoolProperty - - from mathutils import Vector - from mathutils.geometry import tessellate_polygon as tessellate - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr - import Part import Mesh - from FreeCAD import Base - - def svmesh_to_solid(verts, faces, precision): - """ - input: verts / faces / precision - output a Solid (FreeCad type) - - this utility function is included in the node, to keep this code in the same place - """ - - tri_faces = ensure_triangles(verts, faces, True) - faces_t = [[verts[c] for c in f] for f in tri_faces] - mesh = Mesh.Mesh(faces_t) - shape = Part.Shape() - shape.makeShapeFromMesh(mesh.Topology, precision) - shape = shape.removeSplitter() # may slow it down, or be totally necessary - return Part.makeSolid(shape) - - def ensure_triangles(coords, indices, handle_concave_quads): - """ - this fully tesselates the incoming topology into tris, - not optimized for meshes that don't contain ngons - """ - new_indices = [] - concat = new_indices.append - concat2 = new_indices.extend - for idxset in indices: - num_verts = len(idxset) - if num_verts == 3: - concat(tuple(idxset)) - elif num_verts == 4 and not handle_concave_quads: - # a b c d -> [a, b, c], [a, c, d] - concat2([(idxset[0], idxset[1], idxset[2]), (idxset[0], idxset[2], idxset[3])]) - else: - subcoords = [Vector(coords[idx]) for idx in idxset] - for pol in tessellate([subcoords]): - concat([idxset[i] for i in pol]) - return new_indices - - class SvMeshToSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Mesh to Solid - Tooltip: Generate solid from closed mesh - """ - bl_idname = 'SvMeshToSolidNode' - bl_label = 'Mesh to Solid' - bl_icon = 'MESH_CUBE' - sv_icon = 'SV_MESH_TO_SOLID' - solid_catergory = "Inputs" - - precision: FloatProperty( - name="Precision", - default=0.1, - precision=4, - update=updateNode) - refine_solid: BoolProperty( - name="Refine Solid", - description="Removes redundant edges (may slow the process)", - default=True, - update=updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, "precision") - layout.prop(self, "refine_solid") - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Verts") - self.inputs.new('SvStringsSocket', "Faces") - self.outputs.new('SvSolidSocket', "Solid") - - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - verts_s = self.inputs[0].sv_get(deepcopy=False) - faces_s = self.inputs[1].sv_get(deepcopy=False) - solids = [] - faces = [] - for verts, faces in zip(*mlr([verts_s, faces_s])): - tri_faces = ensure_triangles(verts, faces, True) - faces_t = [] - for f in tri_faces: - faces_t.append([verts[c] for c in f]) - - mesh = Mesh.Mesh(faces_t) - shape = Part.Shape() - shape.makeShapeFromMesh(mesh.Topology, self.precision) - if self.refine_solid: - shape = shape.removeSplitter() - solid = Part.makeSolid(shape) - - solids.append(solid) - - - self.outputs['Solid'].sv_set(solids) +def svmesh_to_solid(verts, faces, precision): + """ + input: verts / faces / precision + output a Solid (FreeCad type) + + this utility function is included in the node, to keep this code in the same place + """ + + tri_faces = ensure_triangles(verts, faces, True) + faces_t = [[verts[c] for c in f] for f in tri_faces] + mesh = Mesh.Mesh(faces_t) + shape = Part.Shape() + shape.makeShapeFromMesh(mesh.Topology, precision) + shape = shape.removeSplitter() # may slow it down, or be totally necessary + return Part.makeSolid(shape) + + +def ensure_triangles(coords, indices, handle_concave_quads): + """ + this fully tesselates the incoming topology into tris, + not optimized for meshes that don't contain ngons + """ + new_indices = [] + concat = new_indices.append + concat2 = new_indices.extend + for idxset in indices: + num_verts = len(idxset) + if num_verts == 3: + concat(tuple(idxset)) + elif num_verts == 4 and not handle_concave_quads: + # a b c d -> [a, b, c], [a, c, d] + concat2([(idxset[0], idxset[1], idxset[2]), (idxset[0], idxset[2], idxset[3])]) + else: + subcoords = [Vector(coords[idx]) for idx in idxset] + for pol in tessellate([subcoords]): + concat([idxset[i] for i in pol]) + return new_indices + + +class SvMeshToSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Mesh to Solid + Tooltip: Generate solid from closed mesh + """ + bl_idname = 'SvMeshToSolidNode' + bl_label = 'Mesh to Solid' + bl_icon = 'MESH_CUBE' + sv_icon = 'SV_MESH_TO_SOLID' + sv_category = "Solid Inputs" + + precision: FloatProperty( + name="Precision", + default=0.1, + precision=4, + update=updateNode) + refine_solid: BoolProperty( + name="Refine Solid", + description="Removes redundant edges (may slow the process)", + default=True, + update=updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "precision") + layout.prop(self, "refine_solid") + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Verts") + self.inputs.new('SvStringsSocket', "Faces") + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + verts_s = self.inputs[0].sv_get(deepcopy=False) + faces_s = self.inputs[1].sv_get(deepcopy=False) + solids = [] + faces = [] + for verts, faces in zip(*mlr([verts_s, faces_s])): + tri_faces = ensure_triangles(verts, faces, True) + faces_t = [] + for f in tri_faces: + faces_t.append([verts[c] for c in f]) + + mesh = Mesh.Mesh(faces_t) + shape = Part.Shape() + shape.makeShapeFromMesh(mesh.Topology, self.precision) + if self.refine_solid: + shape = shape.removeSplitter() + solid = Part.makeSolid(shape) + + solids.append(solid) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/mirror_solid.py b/nodes/solid/mirror_solid.py index 0cb72dea00..3ce111596a 100644 --- a/nodes/solid/mirror_solid.py +++ b/nodes/solid/mirror_solid.py @@ -1,56 +1,54 @@ +import bpy +from bpy.props import FloatProperty +from mathutils import Vector +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvMirrorSolidNode', 'Mirror Solid', 'FreeCAD') else: - import bpy - from bpy.props import FloatProperty, StringProperty - from mathutils import Vector - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr from FreeCAD import Base - class SvMirrorSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Mirror Solid - Tooltip: Mirror Solid with Matrix as Plane - """ - bl_idname = 'SvMirrorSolidNode' - bl_label = 'Mirror Solid' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_MIRROR_SOLID' - solid_catergory = "Operators" - - precision: FloatProperty( - name="Precision", - default=0.1, - precision=4, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvMatrixSocket', "Matrix") - self.outputs.new('SvSolidSocket', "Solid") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get() - matrixes = self.inputs[1].sv_get() - solids = [] - for solid, matrix in zip(*mlr([solids_in, matrixes])): - loc = Vector((0,0,0)) @ matrix - norm = Vector((0,0,1)) @ matrix - solid_o = solid.mirror(Base.Vector(loc[:]),Base.Vector(norm[:])) - solids.append(solid_o) - - self.outputs['Solid'].sv_set(solids) - +class SvMirrorSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Mirror Solid + Tooltip: Mirror Solid with Matrix as Plane + """ + bl_idname = 'SvMirrorSolidNode' + bl_label = 'Mirror Solid' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_MIRROR_SOLID' + sv_category = "Solid Operators" + + precision: FloatProperty( + name="Precision", + default=0.1, + precision=4, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvMatrixSocket', "Matrix") + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get() + matrixes = self.inputs[1].sv_get() + solids = [] + for solid, matrix in zip(*mlr([solids_in, matrixes])): + loc = Vector((0,0,0)) @ matrix + norm = Vector((0,0,1)) @ matrix + solid_o = solid.mirror(Base.Vector(loc[:]),Base.Vector(norm[:])) + solids.append(solid_o) + + self.outputs['Solid'].sv_set(solids) def register(): if FreeCAD is not None: diff --git a/nodes/solid/offset_solid.py b/nodes/solid/offset_solid.py index 965827b8ed..ef4d689a61 100644 --- a/nodes/solid/offset_solid.py +++ b/nodes/solid/offset_solid.py @@ -1,98 +1,92 @@ +import bpy +from bpy.props import FloatProperty, BoolProperty, EnumProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvOffsetSolidNode', 'Offset Solid', 'FreeCAD') else: - import bpy - from bpy.props import FloatProperty, BoolProperty, EnumProperty - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr - import Part - class SvOffsetSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Offset Solid - Tooltip: Generate solid by offsetting the boundaries of another along its normals. - """ - bl_idname = 'SvOffsetSolidNode' - bl_label = 'Offset Solid' - bl_icon = 'MESH_CUBE' - sv_icon = 'SV_OFFSET_SOLID' - solid_catergory = "Operators" - - offset: FloatProperty( - name="Offset", - default=0.1, - precision=4, - update=updateNode) - tolerance: FloatProperty( - name="Tolerance", - default=0.01, - precision=4, - update=updateNode) - - join_type_items = [ - ('Arcs', 'Arcs', "", 0), - ('Intersections', 'Intersections', "", 2)] - join_type: EnumProperty( - name="Join Type", - items=join_type_items, - default="Intersections", - update=updateNode) - intersection: BoolProperty( - name="Intersection", - description="Allow intersection", - default=True, - update=updateNode) - refine_solid: BoolProperty( - name="Refine Solid", - description="Removes redundant edges (may slow the process)", - default=True, - update=updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, "tolerance") - layout.prop(self, "join_type") - layout.prop(self, "intersection") - layout.prop(self, "refine_solid") - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvStringsSocket', "Offset").prop_name = 'offset' - self.outputs.new('SvSolidSocket', "Solid") - - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get(deepcopy=False) - offsets = self.inputs[1].sv_get(deepcopy=False)[0] - - solids = [] - for solid_base, offset in zip(*mlr([solids_in, offsets])): - - shape = solid_base.makeOffsetShape(offset, self.tolerance, inter=self.intersection, join=self['join_type']) - if self.refine_solid: - shape = shape.removeSplitter() - try: - valid = shape.isValid() - solid = Part.makeSolid(shape) - except Exception as e: - self.warning("Shape is not valid: %s: %s", shape, e) - continue - - solids.append(solid) - - - self.outputs['Solid'].sv_set(solids) - - +class SvOffsetSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Offset Solid + Tooltip: Generate solid by offsetting the boundaries of another along its normals. + """ + bl_idname = 'SvOffsetSolidNode' + bl_label = 'Offset Solid' + bl_icon = 'MESH_CUBE' + sv_icon = 'SV_OFFSET_SOLID' + sv_category = "Solid Operators" + + offset: FloatProperty( + name="Offset", + default=0.1, + precision=4, + update=updateNode) + tolerance: FloatProperty( + name="Tolerance", + default=0.01, + precision=4, + update=updateNode) + + join_type_items = [ + ('Arcs', 'Arcs', "", 0), + ('Intersections', 'Intersections', "", 2)] + join_type: EnumProperty( + name="Join Type", + items=join_type_items, + default="Intersections", + update=updateNode) + intersection: BoolProperty( + name="Intersection", + description="Allow intersection", + default=True, + update=updateNode) + refine_solid: BoolProperty( + name="Refine Solid", + description="Removes redundant edges (may slow the process)", + default=True, + update=updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "tolerance") + layout.prop(self, "join_type") + layout.prop(self, "intersection") + layout.prop(self, "refine_solid") + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvStringsSocket', "Offset").prop_name = 'offset' + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get(deepcopy=False) + offsets = self.inputs[1].sv_get(deepcopy=False)[0] + + solids = [] + for solid_base, offset in zip(*mlr([solids_in, offsets])): + + shape = solid_base.makeOffsetShape(offset, self.tolerance, inter=self.intersection, join=self['join_type']) + if self.refine_solid: + shape = shape.removeSplitter() + try: + valid = shape.isValid() + solid = Part.makeSolid(shape) + except Exception as e: + self.warning("Shape is not valid: %s: %s", shape, e) + continue + + solids.append(solid) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/points_inside_solid.py b/nodes/solid/points_inside_solid.py index 0701c735f3..0544312390 100644 --- a/nodes/solid/points_inside_solid.py +++ b/nodes/solid/points_inside_solid.py @@ -1,82 +1,80 @@ +import bpy +from bpy.props import FloatProperty, BoolProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.data_structure import match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvIsInsideSolidNode', 'Point Inside Solid', 'FreeCAD') else: - import bpy - from sverchok.node_tree import SverchCustomTreeNode - from bpy.props import FloatProperty, StringProperty, BoolProperty - from sverchok.data_structure import updateNode - from sverchok.data_structure import match_long_repeat as mlr from FreeCAD import Base - class SvIsInsideSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Straight Bevel - Tooltip: Sraight cut in solid edge - """ - bl_idname = 'SvIsInsideSolidNode' - bl_label = 'Points Inside Solid' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_POINTS_INSIDE_SOLID' - solid_catergory = "Operators" - - tolerance: FloatProperty( - name="Tolerance", - default=1e-5, - min=1e-6, - precision=4, - update=updateNode) - in_surface: BoolProperty( - name="Accept in surface", - description="Accept point if it is over solid surface", - default=True, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Mask") - self.outputs.new('SvVerticesSocket', "Inside Vertices") - self.outputs.new('SvVerticesSocket', "Outside Vertices") - - def draw_buttons(self, context, layout): - layout.prop(self, "tolerance") - layout.prop(self, "in_surface") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get() - points = self.inputs[1].sv_get() - - inside_mask = [] - inside_verts_out = [] - outside_verts_out = [] - for solid, points, in zip(*mlr([solids_in, points])): - verts_inside = [] - verts_outside = [] - is_inside = [] - for v in points: - v_is_inside = solid.isInside(Base.Vector(v), self.tolerance, self.in_surface) - is_inside.append(v_is_inside) - if v_is_inside: - verts_inside.append(v) - else: - verts_outside.append(v) - inside_mask.append(is_inside) - inside_verts_out.append(verts_inside) - outside_verts_out.append(verts_outside) - - - self.outputs['Mask'].sv_set(inside_mask) - self.outputs['Inside Vertices'].sv_set(inside_verts_out) - self.outputs['Outside Vertices'].sv_set(outside_verts_out) +class SvIsInsideSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Straight Bevel + Tooltip: Sraight cut in solid edge + """ + bl_idname = 'SvIsInsideSolidNode' + bl_label = 'Points Inside Solid' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_POINTS_INSIDE_SOLID' + sv_category = "Solid Operators" + + tolerance: FloatProperty( + name="Tolerance", + default=1e-5, + min=1e-6, + precision=4, + update=updateNode) + in_surface: BoolProperty( + name="Accept in surface", + description="Accept point if it is over solid surface", + default=True, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Mask") + self.outputs.new('SvVerticesSocket', "Inside Vertices") + self.outputs.new('SvVerticesSocket', "Outside Vertices") + + def draw_buttons(self, context, layout): + layout.prop(self, "tolerance") + layout.prop(self, "in_surface") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get() + points = self.inputs[1].sv_get() + + inside_mask = [] + inside_verts_out = [] + outside_verts_out = [] + for solid, points, in zip(*mlr([solids_in, points])): + verts_inside = [] + verts_outside = [] + is_inside = [] + for v in points: + v_is_inside = solid.isInside(Base.Vector(v), self.tolerance, self.in_surface) + is_inside.append(v_is_inside) + if v_is_inside: + verts_inside.append(v) + else: + verts_outside.append(v) + inside_mask.append(is_inside) + inside_verts_out.append(verts_inside) + outside_verts_out.append(verts_outside) + + self.outputs['Mask'].sv_set(inside_mask) + self.outputs['Inside Vertices'].sv_set(inside_verts_out) + self.outputs['Outside Vertices'].sv_set(outside_verts_out) def register(): diff --git a/nodes/solid/polygon_face.py b/nodes/solid/polygon_face.py index 0ab0e2736b..e6dedb8b82 100644 --- a/nodes/solid/polygon_face.py +++ b/nodes/solid/polygon_face.py @@ -32,7 +32,7 @@ class SvSolidPolygonFaceNode(SverchCustomTreeNode, bpy.types.Node): bl_label = "Polygon Face (Solid)" bl_icon = 'EDGESEL' sv_icon = 'SV_POLYGON_FACE' - solid_catergory = "Inputs" + sv_category = "Solid Inputs" def sv_init(self, context): self.inputs.new('SvVerticesSocket', "Vertices") diff --git a/nodes/solid/projection_trim_face.py b/nodes/solid/projection_trim_face.py index e8597d833a..0e54a51488 100644 --- a/nodes/solid/projection_trim_face.py +++ b/nodes/solid/projection_trim_face.py @@ -39,7 +39,7 @@ class SvProjectTrimFaceNode(SverchCustomTreeNode, bpy.types.Node): bl_label = "Face from Surface (Solid)" bl_icon = 'EDGESEL' sv_icon = 'SV_PROJECT_CUT_FACE' - solid_catergory = "Inputs" + sv_category = "Solid Inputs" def update_sockets(self, context): self.inputs['Point'].hide_safe = self.projection_type != 'PERSPECTIVE' diff --git a/nodes/solid/refine.py b/nodes/solid/refine.py index bd21c866ac..555c79f66d 100644 --- a/nodes/solid/refine.py +++ b/nodes/solid/refine.py @@ -25,8 +25,7 @@ class SvRefineSolidNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvRefineSolidNode' bl_label = 'Refine Solid' - bl_icon = 'OUTLINER_OB_EMPTY' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSolidSocket', "Solid") diff --git a/nodes/solid/revolve_face.py b/nodes/solid/revolve_face.py index d79ebea4bb..6dcf1fb477 100644 --- a/nodes/solid/revolve_face.py +++ b/nodes/solid/revolve_face.py @@ -33,7 +33,7 @@ class SvSolidFaceRevolveNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Revolve Face (Solid)' bl_icon = 'EDGESEL' sv_icon = 'SV_REVOLVE_FACE' - solid_catergory = "Operators" + sv_category = "Solid Operators" refine_solid: BoolProperty( name="Refine Solid", diff --git a/nodes/solid/ruled_solid.py b/nodes/solid/ruled_solid.py index 59f28bbd19..d731b145e8 100644 --- a/nodes/solid/ruled_solid.py +++ b/nodes/solid/ruled_solid.py @@ -62,7 +62,7 @@ class SvRuledSolidNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid from two Faces' bl_icon = 'EDGESEL' sv_icon = 'SV_RULED_SOLID' - solid_catergory = "Operators" + sv_category = "Solid Operators" flip_face1 : BoolProperty( name = "Flip Face", diff --git a/nodes/solid/slice_solid.py b/nodes/solid/slice_solid.py index 7a5cc03b9a..5193b56ddc 100644 --- a/nodes/solid/slice_solid.py +++ b/nodes/solid/slice_solid.py @@ -1,82 +1,83 @@ +import bpy +from mathutils import Vector +from bpy.props import BoolProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy +from sverchok.utils.curve.freecad import SvSolidEdgeCurve +from sverchok.utils.surface.freecad import SvSolidFaceSurface if FreeCAD is None: - add_dummy('SvTransformSolidNode', 'Transform Solid', 'FreeCAD') + add_dummy('SvSliceSolidNode', 'Transform Solid', 'FreeCAD') else: - import bpy - from mathutils import Vector - from bpy.props import BoolProperty - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr from FreeCAD import Base import Part - from sverchok.utils.curve.freecad import SvSolidEdgeCurve - from sverchok.utils.surface.freecad import SvSolidFaceSurface - - class SvSliceSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Apply Matrix to Solid - Tooltip: Transform Solid with Matrix - """ - bl_idname = 'SvSliceSolidNode' - bl_label = 'Slice Solid' - bl_icon = 'MESH_CUBE' - sv_icon = 'SV_SLICE_SOLID' - solid_catergory = "Operators" - - flat_output: BoolProperty( - name="Flat Output", - default=False, - update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvMatrixSocket', "Matrix") - - self.outputs.new('SvCurveSocket', "Edges") - self.outputs.new('SvSurfaceSocket', "Faces") - - def draw_buttons(self, context, layout): - layout.prop(self, 'flat_output') - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get(deepcopy=False) - matrixes = self.inputs[1].sv_get(deepcopy=False) - slices = [] - slices_face = [] - faces_add = slices_face.extend if self.flat_output else slices_face.append - slices_add = slices.extend if self.flat_output else slices.append - - for solid, matrix in zip(*mlr([solids_in, matrixes])): - - location = matrix.decompose()[0] - norm = (matrix @ Vector((0, 0, 1))) - location - dist = norm.dot(location) - - wires = solid.slice(Base.Vector(norm), dist) - edges_curves = [] - faces = [] - for wire in wires: - for edge in wire.Edges: - curve = SvSolidEdgeCurve(edge) - edges_curves.append(curve) - - if wires: - face = Part.Face(wires) - faces.append(SvSolidFaceSurface(face).to_nurbs()) - if faces: - faces_add(faces) - if edges_curves: - slices_add(edges_curves) - - self.outputs['Edges'].sv_set(slices) - self.outputs['Faces'].sv_set(slices_face) + + +class SvSliceSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Apply Matrix to Solid + Tooltip: Transform Solid with Matrix + """ + bl_idname = 'SvSliceSolidNode' + bl_label = 'Slice Solid' + bl_icon = 'MESH_CUBE' + sv_icon = 'SV_SLICE_SOLID' + sv_category = "Solid Operators" + + flat_output: BoolProperty( + name="Flat Output", + default=False, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvMatrixSocket', "Matrix") + + self.outputs.new('SvCurveSocket', "Edges") + self.outputs.new('SvSurfaceSocket', "Faces") + + def draw_buttons(self, context, layout): + layout.prop(self, 'flat_output') + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get(deepcopy=False) + matrixes = self.inputs[1].sv_get(deepcopy=False) + slices = [] + slices_face = [] + faces_add = slices_face.extend if self.flat_output else slices_face.append + slices_add = slices.extend if self.flat_output else slices.append + + for solid, matrix in zip(*mlr([solids_in, matrixes])): + + location = matrix.decompose()[0] + norm = (matrix @ Vector((0, 0, 1))) - location + dist = norm.dot(location) + + wires = solid.slice(Base.Vector(norm), dist) + edges_curves = [] + faces = [] + for wire in wires: + for edge in wire.Edges: + curve = SvSolidEdgeCurve(edge) + edges_curves.append(curve) + + if wires: + face = Part.Face(wires) + faces.append(SvSolidFaceSurface(face).to_nurbs()) + if faces: + faces_add(faces) + if edges_curves: + slices_add(edges_curves) + + self.outputs['Edges'].sv_set(slices) + self.outputs['Faces'].sv_set(slices_face) + def register(): if FreeCAD is not None: diff --git a/nodes/solid/solid_boolean.py b/nodes/solid/solid_boolean.py index a1c4236ec1..9ed43806c2 100644 --- a/nodes/solid/solid_boolean.py +++ b/nodes/solid/solid_boolean.py @@ -30,7 +30,7 @@ class SvSolidBooleanNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid Boolean' bl_icon = 'OUTLINER_OB_EMPTY' sv_icon = 'SV_SOLID_BOOLEAN' - solid_catergory = "Operators" + sv_category = "Solid Operators" mode_options = [ ("ITX", "Intersect", "", 0), diff --git a/nodes/solid/solid_distance.py b/nodes/solid/solid_distance.py index 909d1b26bc..9cd040e375 100644 --- a/nodes/solid/solid_distance.py +++ b/nodes/solid/solid_distance.py @@ -1,158 +1,154 @@ +import bpy +from bpy.props import EnumProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode +from sverchok.data_structure import match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvSolidDistanceNode', 'Solid Distance', 'FreeCAD') else: - import bpy - from sverchok.node_tree import SverchCustomTreeNode - from bpy.props import EnumProperty - from sverchok.data_structure import updateNode - from sverchok.data_structure import match_long_repeat as mlr import Part from FreeCAD import Base - def get_shape(mode, base_object): - if mode == 'Solid': - if isinstance(base_object, Part.Solid): - shape = base_object.OuterShell - else: - shape = base_object - elif mode == 'Face': - shape = base_object.face + +def get_shape(mode, base_object): + if mode == 'Solid': + if isinstance(base_object, Part.Solid): + shape = base_object.OuterShell else: - shape = base_object.edge - return shape - - - class SvSolidDistanceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Closest point on Solids - Tooltip: Distance between solids. Closest point on a solid surface, accepts also solid faces and solid edges - """ - bl_idname = 'SvSolidDistanceNode' - bl_label = 'Solid Distance' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_SOLID_DISTANCE' - solid_catergory = "Operators" - - modes = [ - ('Solid', 'Solid', '', 0), - ('Face', 'Face', '', 1), - ('Edge', 'Edge', '', 2), - ('Vertex', 'Vertex', '', 3), - - ] - def set_sockets(self, context): - self.update_sockets() - updateNode(self, context) - - def update_sockets(self): - self.inputs['Solid A'].hide_safe = self.mode != 'Solid' - self.inputs['Solid Face A'].hide_safe = self.mode != 'Face' - self.inputs['Solid Edge A'].hide_safe = self.mode != 'Edge' - self.inputs['Solid B'].hide_safe = self.mode_b != 'Solid' - self.inputs['Solid Face B'].hide_safe = self.mode_b != 'Face' - self.inputs['Solid Edge B'].hide_safe = self.mode_b != 'Edge' - self.inputs['Vertices'].hide_safe = self.mode_b != 'Vertex' - self.outputs['Closest Point B'].hide_safe = self.mode_b == 'Vertex' - self.outputs['Info B'].hide_safe = self.mode_b == 'Vertex' - - - mode: EnumProperty( - name="Mode", - description="Algorithm used for conversion", - items=modes[:3], default="Solid", - update=set_sockets) - - mode_b: EnumProperty( - name="Mode", - description="Algorithm used for conversion", - items=modes, default="Solid", - update=set_sockets) - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid A") - self.inputs.new('SvSurfaceSocket', "Solid Face A") - self.inputs.new('SvCurveSocket', "Solid Edge A") - self.inputs.new('SvSolidSocket', "Solid B") - self.inputs.new('SvSurfaceSocket', "Solid Face B") - self.inputs.new('SvCurveSocket', "Solid Edge B") - self.inputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Distance") - self.outputs.new('SvVerticesSocket', "Closest Point A") - self.outputs.new('SvStringsSocket', "Info A") - self.outputs.new('SvVerticesSocket', "Closest Point B") - self.outputs.new('SvStringsSocket', "Info B") - self.mode = "Solid" - self.mode_b = "Solid" - self.update_sockets() - - def draw_buttons(self, context, layout): - row = layout.row(align=True) - col = row.column(align=True) - col.label(text='From') - col.prop(self, "mode", text="") - col = row.column(align=True) - col.label(text='To') - col.prop(self, "mode_b", text="") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - - objects_a = self.inputs[self["mode"]].sv_get() - objects_b = self.inputs[self["mode_b"] + 3].sv_get() - - distances_out = [] - closest_points_out_a = [] - infos_out_a = [] - closest_points_out_b = [] - infos_out_b = [] - - for object_a, object_b, in zip(*mlr([objects_a, objects_b])): - distances = [] - closest_points_a = [] - infos_a = [] - closest_points_b = [] - infos_b = [] - shape = get_shape(self.mode, object_a) - - if self.mode_b == 'Vertex': - for v in object_b: - vertex = Part.Vertex(Base.Vector(v)) - - dist = shape.distToShape(vertex) - distances.append(dist[0]) - closest_points_a.append(dist[1][0][0][:]) - infos_a.append(dist[2][0][0:3]) - else: - shape_b = get_shape(self.mode_b, object_b) - - dist = shape.distToShape(shape_b) + shape = base_object + elif mode == 'Face': + shape = base_object.face + else: + shape = base_object.edge + return shape + + +class SvSolidDistanceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Closest point on Solids + Tooltip: Distance between solids. Closest point on a solid surface, accepts also solid faces and solid edges + """ + bl_idname = 'SvSolidDistanceNode' + bl_label = 'Solid Distance' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_SOLID_DISTANCE' + sv_category = "Solid Operators" + + modes = [ + ('Solid', 'Solid', '', 0), + ('Face', 'Face', '', 1), + ('Edge', 'Edge', '', 2), + ('Vertex', 'Vertex', '', 3), + + ] + def set_sockets(self, context): + self.update_sockets() + updateNode(self, context) + + def update_sockets(self): + self.inputs['Solid A'].hide_safe = self.mode != 'Solid' + self.inputs['Solid Face A'].hide_safe = self.mode != 'Face' + self.inputs['Solid Edge A'].hide_safe = self.mode != 'Edge' + self.inputs['Solid B'].hide_safe = self.mode_b != 'Solid' + self.inputs['Solid Face B'].hide_safe = self.mode_b != 'Face' + self.inputs['Solid Edge B'].hide_safe = self.mode_b != 'Edge' + self.inputs['Vertices'].hide_safe = self.mode_b != 'Vertex' + self.outputs['Closest Point B'].hide_safe = self.mode_b == 'Vertex' + self.outputs['Info B'].hide_safe = self.mode_b == 'Vertex' + + mode: EnumProperty( + name="Mode", + description="Algorithm used for conversion", + items=modes[:3], default="Solid", + update=set_sockets) + + mode_b: EnumProperty( + name="Mode", + description="Algorithm used for conversion", + items=modes, default="Solid", + update=set_sockets) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid A") + self.inputs.new('SvSurfaceSocket', "Solid Face A") + self.inputs.new('SvCurveSocket', "Solid Edge A") + self.inputs.new('SvSolidSocket', "Solid B") + self.inputs.new('SvSurfaceSocket', "Solid Face B") + self.inputs.new('SvCurveSocket', "Solid Edge B") + self.inputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Distance") + self.outputs.new('SvVerticesSocket', "Closest Point A") + self.outputs.new('SvStringsSocket', "Info A") + self.outputs.new('SvVerticesSocket', "Closest Point B") + self.outputs.new('SvStringsSocket', "Info B") + self.mode = "Solid" + self.mode_b = "Solid" + self.update_sockets() + + def draw_buttons(self, context, layout): + row = layout.row(align=True) + col = row.column(align=True) + col.label(text='From') + col.prop(self, "mode", text="") + col = row.column(align=True) + col.label(text='To') + col.prop(self, "mode_b", text="") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + objects_a = self.inputs[self["mode"]].sv_get() + objects_b = self.inputs[self["mode_b"] + 3].sv_get() + + distances_out = [] + closest_points_out_a = [] + infos_out_a = [] + closest_points_out_b = [] + infos_out_b = [] + + for object_a, object_b, in zip(*mlr([objects_a, objects_b])): + distances = [] + closest_points_a = [] + infos_a = [] + closest_points_b = [] + infos_b = [] + shape = get_shape(self.mode, object_a) + + if self.mode_b == 'Vertex': + for v in object_b: + vertex = Part.Vertex(Base.Vector(v)) + + dist = shape.distToShape(vertex) distances.append(dist[0]) closest_points_a.append(dist[1][0][0][:]) infos_a.append(dist[2][0][0:3]) - closest_points_b.append(dist[1][0][1][:]) - infos_b.append(dist[2][0][3:]) - - closest_points_out_b.append(closest_points_b) - infos_out_b.append(infos_b) - - - distances_out.append(distances) - closest_points_out_a.append(closest_points_a) - infos_out_a.append(infos_a) - - - self.outputs['Distance'].sv_set(distances_out) - self.outputs['Closest Point A'].sv_set(closest_points_out_a) - self.outputs['Info A'].sv_set(infos_out_a) - self.outputs['Closest Point B'].sv_set(closest_points_out_b) - self.outputs['Info B'].sv_set(infos_out_b) - + else: + shape_b = get_shape(self.mode_b, object_b) + + dist = shape.distToShape(shape_b) + distances.append(dist[0]) + closest_points_a.append(dist[1][0][0][:]) + infos_a.append(dist[2][0][0:3]) + closest_points_b.append(dist[1][0][1][:]) + infos_b.append(dist[2][0][3:]) + + closest_points_out_b.append(closest_points_b) + infos_out_b.append(infos_b) + + distances_out.append(distances) + closest_points_out_a.append(closest_points_a) + infos_out_a.append(infos_a) + + self.outputs['Distance'].sv_set(distances_out) + self.outputs['Closest Point A'].sv_set(closest_points_out_a) + self.outputs['Info A'].sv_set(infos_out_a) + self.outputs['Closest Point B'].sv_set(closest_points_out_b) + self.outputs['Info B'].sv_set(infos_out_b) def register(): diff --git a/nodes/solid/solid_edges.py b/nodes/solid/solid_edges.py index fcfad26c90..2d2d738b78 100644 --- a/nodes/solid/solid_edges.py +++ b/nodes/solid/solid_edges.py @@ -1,75 +1,75 @@ +import bpy +from bpy.props import BoolProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, map_recursive, flatten_data +from sverchok.utils.curve.freecad import SvSolidEdgeCurve from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvSolidEdgesNode', 'Solid Edges', 'FreeCAD') else: - import numpy as np - import bpy - from bpy.props import BoolProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, map_recursive, flatten_data - from sverchok.utils.curve.freecad import SvSolidEdgeCurve - import Part - class SvSolidEdgesNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Edges - Tooltip: Get Edges from Solid - """ - bl_idname = 'SvSolidEdgesNode' - bl_label = 'Solid Edges (Curves)' - bl_icon = 'EDGESEL' - solid_catergory = "Outputs" - - - flat_output: BoolProperty( - name="Flat Output", - default=False, - update=updateNode) - - nurbs_output : BoolProperty( - name = "NURBS Output", - description = "Output curves in NURBS representation", - default = False, - update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.outputs.new('SvCurveSocket', "Edges") - - def draw_buttons(self, context, layout): - layout.prop(self, 'flat_output') - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - layout.prop(self, 'nurbs_output', toggle=True) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids = self.inputs['Solid'].sv_get() - - def get_edges(solid): - edges_curves = [] - for e in solid.Edges: - try: - curve = SvSolidEdgeCurve(e) - if self.nurbs_output: - curve = curve.to_nurbs() - edges_curves.append(curve) - except TypeError: - pass - return edges_curves - - edges_out = map_recursive(get_edges, solids, data_types=(Part.Shape,)) - if self.flat_output: - edges_out = flatten_data(edges_out, data_types=(Part.Shape,)) - - self.outputs['Edges'].sv_set(edges_out) + +class SvSolidEdgesNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Edges + Tooltip: Get Edges from Solid + """ + bl_idname = 'SvSolidEdgesNode' + bl_label = 'Solid Edges (Curves)' + bl_icon = 'EDGESEL' + sv_category = "Solid Outputs" + + + flat_output: BoolProperty( + name="Flat Output", + default=False, + update=updateNode) + + nurbs_output : BoolProperty( + name = "NURBS Output", + description = "Output curves in NURBS representation", + default = False, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.outputs.new('SvCurveSocket', "Edges") + + def draw_buttons(self, context, layout): + layout.prop(self, 'flat_output') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'nurbs_output', toggle=True) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids = self.inputs['Solid'].sv_get() + + def get_edges(solid): + edges_curves = [] + for e in solid.Edges: + try: + curve = SvSolidEdgeCurve(e) + if self.nurbs_output: + curve = curve.to_nurbs() + edges_curves.append(curve) + except TypeError: + pass + return edges_curves + + edges_out = map_recursive(get_edges, solids, data_types=(Part.Shape,)) + if self.flat_output: + edges_out = flatten_data(edges_out, data_types=(Part.Shape,)) + + self.outputs['Edges'].sv_set(edges_out) + def register(): if FreeCAD is not None: diff --git a/nodes/solid/solid_faces.py b/nodes/solid/solid_faces.py index 6302c937a8..aa0160369c 100644 --- a/nodes/solid/solid_faces.py +++ b/nodes/solid/solid_faces.py @@ -1,114 +1,112 @@ - +import bpy +from bpy.props import BoolProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, flatten_data, map_unzip_recursirve +from sverchok.utils.curve.core import SvCurve +from sverchok.utils.surface.core import SvSurface +from sverchok.utils.curve.freecad import SvSolidEdgeCurve +from sverchok.utils.surface.freecad import SvSolidFaceSurface from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy from sverchok.utils.surface.nurbs import SvNurbsSurface -#from sverchok.utils.curve.core import SvConcatCurve from sverchok.utils.curve.freecad import SvFreeCadNurbsCurve, SvFreeCadCurve if FreeCAD is None: add_dummy('SvSolidFacesNode', 'Solid Faces', 'FreeCAD') else: - import numpy as np - import bpy - from bpy.props import BoolProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, flatten_data, map_unzip_recursirve - from sverchok.utils.curve.core import SvCurve - from sverchok.utils.surface.core import SvSurface - from sverchok.utils.curve.freecad import SvSolidEdgeCurve - from sverchok.utils.surface.freecad import SvSolidFaceSurface - import Part - class SvSolidFacesNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Faces - Tooltip: Get Faces from Solid - """ - bl_idname = 'SvSolidFacesNode' - bl_label = 'Solid Faces (Surfaces)' - bl_icon = 'FACESEL' - solid_catergory = "Outputs" - - - flat_output: BoolProperty( - name="Flat Output", - default=False, - update=updateNode) - - nurbs_output : BoolProperty( - name = "NURBS Output", - description = "Output curves and surfaces in NURBS representation", - default = False, - update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.outputs.new('SvSurfaceSocket', "Solid Faces") - self.outputs.new('SvCurveSocket', "Outer Wire") - self.outputs.new('SvCurveSocket', "TrimCurves") - - - def draw_buttons(self, context, layout): - layout.prop(self, 'flat_output') - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - layout.prop(self, 'nurbs_output', toggle=True) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids = self.inputs['Solid'].sv_get() - - def get_faces(solid): - face_surface = [] - outer_wires = [] - trims = [] - for f in solid.Faces: - surface = SvSolidFaceSurface(f) - if self.nurbs_output: - out_surface = SvNurbsSurface.get(surface) - if out_surface is None: - out_surface = surface - else: + +class SvSolidFacesNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Faces + Tooltip: Get Faces from Solid + """ + bl_idname = 'SvSolidFacesNode' + bl_label = 'Solid Faces (Surfaces)' + bl_icon = 'FACESEL' + sv_category = "Solid Outputs" + + + flat_output: BoolProperty( + name="Flat Output", + default=False, + update=updateNode) + + nurbs_output : BoolProperty( + name = "NURBS Output", + description = "Output curves and surfaces in NURBS representation", + default = False, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.outputs.new('SvSurfaceSocket', "Solid Faces") + self.outputs.new('SvCurveSocket', "Outer Wire") + self.outputs.new('SvCurveSocket', "TrimCurves") + + def draw_buttons(self, context, layout): + layout.prop(self, 'flat_output') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'nurbs_output', toggle=True) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids = self.inputs['Solid'].sv_get() + + def get_faces(solid): + face_surface = [] + outer_wires = [] + trims = [] + for f in solid.Faces: + surface = SvSolidFaceSurface(f) + if self.nurbs_output: + out_surface = SvNurbsSurface.get(surface) + if out_surface is None: out_surface = surface - face_surface.append(out_surface) - outer_wire = [] - face_trims = [] - for e in f.OuterWire.Edges: - try: - if self.nurbs_output: - outer_wire.append(SvSolidEdgeCurve(e).to_nurbs()) - else: - outer_wire.append(SvSolidEdgeCurve(e)) - except TypeError: - pass - trim,m,M = f.curveOnSurface(e) + else: + out_surface = surface + face_surface.append(out_surface) + outer_wire = [] + face_trims = [] + for e in f.OuterWire.Edges: + try: if self.nurbs_output: - trim = trim.toBSpline(m,M) - trim = SvFreeCadNurbsCurve(trim, ndim=2) + outer_wire.append(SvSolidEdgeCurve(e).to_nurbs()) else: - #trim = trim.trim(m, M) - trim = SvFreeCadCurve(trim, (m,M), ndim=2) - face_trims.append(trim) + outer_wire.append(SvSolidEdgeCurve(e)) + except TypeError: + pass + trim,m,M = f.curveOnSurface(e) + if self.nurbs_output: + trim = trim.toBSpline(m,M) + trim = SvFreeCadNurbsCurve(trim, ndim=2) + else: + #trim = trim.trim(m, M) + trim = SvFreeCadCurve(trim, (m,M), ndim=2) + face_trims.append(trim) + + outer_wires.append(outer_wire) + trims.append(face_trims) - outer_wires.append(outer_wire) - trims.append(face_trims) + return face_surface, outer_wires, trims - return face_surface, outer_wires, trims + faces_out, wires_out, trims_out = map_unzip_recursirve(get_faces, solids, data_types=(Part.Shape,)) + if self.flat_output: + faces_out = flatten_data(faces_out, data_types=(SvSurface,)) + wires_out = flatten_data(wires_out, target_level=2, data_types=(SvCurve,)) + trims_out = flatten_data(trims_out, target_level=2, data_types=(SvCurve,)) - faces_out, wires_out, trims_out = map_unzip_recursirve(get_faces, solids, data_types=(Part.Shape,)) - if self.flat_output: - faces_out = flatten_data(faces_out, data_types=(SvSurface,)) - wires_out = flatten_data(wires_out, target_level=2, data_types=(SvCurve,)) - trims_out = flatten_data(trims_out, target_level=2, data_types=(SvCurve,)) + self.outputs['Solid Faces'].sv_set(faces_out) + self.outputs['Outer Wire'].sv_set(wires_out) + if 'TrimCurves' in self.outputs: + self.outputs['TrimCurves'].sv_set(trims_out) - self.outputs['Solid Faces'].sv_set(faces_out) - self.outputs['Outer Wire'].sv_set(wires_out) - if 'TrimCurves' in self.outputs: - self.outputs['TrimCurves'].sv_set(trims_out) def register(): if FreeCAD is not None: diff --git a/nodes/solid/solid_select.py b/nodes/solid/solid_select.py index 16caf0e05a..6bf190684e 100644 --- a/nodes/solid/solid_select.py +++ b/nodes/solid/solid_select.py @@ -34,7 +34,7 @@ class SvSelectSolidNode(SverchCustomTreeNode, bpy.types.Node): bl_idname = 'SvSelectSolidNode' bl_label = 'Select Solid Elements' bl_icon = 'UV_SYNC_SELECT' - solid_catergory = "Operators" + sv_category = "Solid Operators" element_types = [ ('VERTS', "Vertices", "Select vertices first, and then select adjacent edges and faces", 'VERTEXSEL', 0), diff --git a/nodes/solid/solid_to_mesh_mk2.py b/nodes/solid/solid_to_mesh_mk2.py index 97b3150c73..6871aaef78 100644 --- a/nodes/solid/solid_to_mesh_mk2.py +++ b/nodes/solid/solid_to_mesh_mk2.py @@ -1,275 +1,272 @@ - - +import math +import bpy +from bpy.props import FloatProperty, EnumProperty, BoolProperty + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, has_element, match_long_repeat as mlr +from sverchok.utils.solid import mesh_from_solid_faces, mesh_from_solid_faces_MOD +from sverchok.utils.sv_bmesh_utils import recalc_normals +from sverchok.utils.sv_mesh_utils import non_redundant_faces_indices_np as clean from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy - - if FreeCAD is None: - add_dummy('SvSolidToMeshNode', 'Solid to Mesh', 'FreeCAD') + add_dummy('SvSolidToMeshNodeMk2', 'Solid to Mesh', 'FreeCAD') else: - import math - import bpy - from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty + import MeshPart - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, has_element, match_long_repeat as mlr - from sverchok.utils.solid import mesh_from_solid_faces, mesh_from_solid_faces_MOD, drop_existing_faces - from sverchok.utils.sv_bmesh_utils import recalc_normals - from sverchok.utils.sv_mesh_utils import non_redundant_faces_indices_np as clean - import MeshPart +def is_triangles_only(faces): + if has_element(faces): return all((len(f) == 3 for f in faces)) + + +class SvSolidToMeshNodeMk2(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid to Mesh + Tooltip: Generate mesh from solid + """ + bl_idname = 'SvSolidToMeshNodeMk2' + bl_label = 'Solid to Mesh' + bl_icon = 'MESH_CUBE' + sv_icon = 'SV_SOLID_TO_MESH' + sv_category = "Solid Outputs" + modes = [ + ('Basic', 'Basic', '', 0), + ('Standard', 'Standard', '', 1), + ('Mefisto', 'Mefisto', '', 2), + # ('NetGen', 'NetGen', '', 3), + ('Trivial', 'Trivial', '', 10), + ('Lenient', 'Lenient', '', 14) + ] + shape_types = [ + ('Solid', 'Solid', '', 0), + ('Face', 'Face', '', 1), + ] + + def set_sockets(self,context): + self.update_sockets() + updateNode(self, context) + def set_shape_sockets(self,context): + self.shape_sockets() + updateNode(self, context) + + def shape_sockets(self): + self.inputs['Solid'].hide_safe = self.shape_type == 'Face' + self.inputs['Face'].hide_safe = self.shape_type == 'Solid' + + def update_sockets(self): + if self.mode == 'Basic': + self.inputs['Precision'].hide_safe = False + self.inputs['Surface Deviation'].hide_safe = True + self.inputs['Angle Deviation'].hide_safe = True + self.inputs['Max Edge Length'].hide_safe = True + + elif self.mode == 'Standard': + self.inputs['Precision'].hide_safe = True + self.inputs['Surface Deviation'].hide_safe = False + self.inputs['Angle Deviation'].hide_safe = False + self.inputs['Max Edge Length'].hide_safe = True + + elif self.mode == 'Mefisto': + self.inputs['Precision'].hide_safe = True + self.inputs['Surface Deviation'].hide_safe = True + self.inputs['Angle Deviation'].hide_safe = True + self.inputs['Max Edge Length'].hide_safe = False + + elif self.mode == 'Trivial': + self.inputs['Precision'].hide_safe = True + self.inputs['Surface Deviation'].hide_safe = True + self.inputs['Angle Deviation'].hide_safe = True + self.inputs['Max Edge Length'].hide_safe = True + + precision: FloatProperty( + name="Precision", + default=0.1, + precision=4, + update=updateNode) + + mode: EnumProperty( + name="Mode", + description="Algorithm used for conversion", + items=modes, default="Basic", + update=set_sockets) + + shape_type: EnumProperty( + name="Type", + description="Algorithm used for conversion", + items=shape_types, default="Solid", + update=set_shape_sockets) + + surface_deviation: FloatProperty( + name="Surface Deviation", + default=10, + min=1e-2, + precision=4, + update=updateNode) + angle_deviation: FloatProperty( + name="Angle Deviation", + default=30, + min=5, + precision=3, + update=updateNode) + relative_surface_deviation: BoolProperty( + name='Relative Surface Deviation', + default=False, + update=updateNode) + max_edge_length: FloatProperty( + name="max_edge_length", + default=1, + soft_min=0.1, + precision=4, + update=updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "shape_type", expand=True) + layout.prop(self, "mode") + if self.mode == 'Standard': + layout.prop(self, "relative_surface_deviation") + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvSurfaceSocket', "Face") + self.inputs.new('SvStringsSocket', "Precision").prop_name = 'precision' + self.inputs.new('SvStringsSocket', "Surface Deviation").prop_name = 'surface_deviation' + self.inputs.new('SvStringsSocket', "Angle Deviation").prop_name = 'angle_deviation' + self.inputs.new('SvStringsSocket', "Max Edge Length").prop_name = 'max_edge_length' + self.shape_type = "Solid" + self.inputs['Face'].hide_safe = True + self.update_sockets() + + self.outputs.new('SvVerticesSocket', "Verts") + self.outputs.new('SvStringsSocket', "Faces") + + + def basic_mesher(self): + solids = self.inputs[self["shape_type"]].sv_get() + precisions = self.inputs["Precision"].sv_get()[0] + verts = [] + faces = [] + for solid, precision in zip(*mlr([solids, precisions])): + if self.shape_type == 'Solid': + rawdata = solid.tessellate(precision) + else: + rawdata = solid.face.tessellate(precision) + + b_verts = [(v.x, v.y, v.z) for v in rawdata[0]] + b_faces = [f for f in rawdata[1]] + b_faces = clean(b_faces).tolist() if is_triangles_only(b_faces) else b_faces + + verts.append(b_verts) + faces.append(b_faces) + + return verts, faces + + def standard_mesher(self): + solids = self.inputs[self["shape_type"]].sv_get() + surface_deviation = self.inputs["Surface Deviation"].sv_get()[0] + angle_deviation = self.inputs["Angle Deviation"].sv_get()[0] + verts = [] + faces = [] + for solid, s_dev, ang_dev in zip(*mlr([solids, surface_deviation, angle_deviation])): + if self.shape_type == 'Solid': + shape = solid + else: + shape = solid.face + + mesh = MeshPart.meshFromShape( + Shape=shape, + LinearDeflection=s_dev, + AngularDeflection=math.radians(ang_dev), + Relative=self.relative_surface_deviation) + + verts.append([v[:] for v in mesh.Topology[0]]) + + b_faces = mesh.Topology[1] + b_faces = clean(b_faces).tolist() if is_triangles_only(b_faces) else b_faces + faces.append(b_faces) + + return verts, faces + + def mefisto_mesher(self): + solids = self.inputs[self["shape_type"]].sv_get() + max_edge_length = self.inputs['Max Edge Length'].sv_get()[0] + + verts = [] + faces = [] + for solid, max_edge in zip(*mlr([solids, max_edge_length])): + if self.shape_type == 'Solid': + shape = solid + else: + shape = solid.face + mesh = MeshPart.meshFromShape( + Shape=shape, + MaxLength=max_edge + ) + + verts.append([v[:] for v in mesh.Topology[0]]) + faces.append(mesh.Topology[1]) + + return verts, faces + + def trivial_mesher(self): + """ + this mode will produce a variety of polygon types (tris, quads, ngons...) + """ + solids = self.inputs[self["shape_type"]].sv_get() + + verts = [] + faces = [] + for solid in solids: + if self.shape_type == 'Solid': + shape = solid + else: + shape = solid.face + + new_verts, new_edges, new_faces = mesh_from_solid_faces(shape) + new_verts, new_edges, new_faces = recalc_normals(new_verts, new_edges, new_faces) + + verts.append(new_verts) + faces.append(new_faces) - def is_triangles_only(faces): - if has_element(faces): return all((len(f) == 3 for f in faces)) + return verts, faces - class SvSolidToMeshNodeMk2(SverchCustomTreeNode, bpy.types.Node): + def lenient_mesher(self): """ - Triggers: Solid to Mesh - Tooltip: Generate mesh from solid + this mode will produce a variety of polygon types (tris, quads, ngons...) """ - bl_idname = 'SvSolidToMeshNodeMk2' - bl_label = 'Solid to Mesh' - bl_icon = 'MESH_CUBE' - sv_icon = 'SV_SOLID_TO_MESH' - solid_catergory = "Outputs" - modes = [ - ('Basic', 'Basic', '', 0), - ('Standard', 'Standard', '', 1), - ('Mefisto', 'Mefisto', '', 2), - # ('NetGen', 'NetGen', '', 3), - ('Trivial', 'Trivial', '', 10), - ('Lenient', 'Lenient', '', 14) - ] - shape_types = [ - ('Solid', 'Solid', '', 0), - ('Face', 'Face', '', 1), - ] - - def set_sockets(self,context): - self.update_sockets() - updateNode(self, context) - def set_shape_sockets(self,context): - self.shape_sockets() - updateNode(self, context) - - def shape_sockets(self): - self.inputs['Solid'].hide_safe = self.shape_type == 'Face' - self.inputs['Face'].hide_safe = self.shape_type == 'Solid' - - def update_sockets(self): - if self.mode == 'Basic': - self.inputs['Precision'].hide_safe = False - self.inputs['Surface Deviation'].hide_safe = True - self.inputs['Angle Deviation'].hide_safe = True - self.inputs['Max Edge Length'].hide_safe = True - - elif self.mode == 'Standard': - self.inputs['Precision'].hide_safe = True - self.inputs['Surface Deviation'].hide_safe = False - self.inputs['Angle Deviation'].hide_safe = False - self.inputs['Max Edge Length'].hide_safe = True - - elif self.mode == 'Mefisto': - self.inputs['Precision'].hide_safe = True - self.inputs['Surface Deviation'].hide_safe = True - self.inputs['Angle Deviation'].hide_safe = True - self.inputs['Max Edge Length'].hide_safe = False - - elif self.mode == 'Trivial': - self.inputs['Precision'].hide_safe = True - self.inputs['Surface Deviation'].hide_safe = True - self.inputs['Angle Deviation'].hide_safe = True - self.inputs['Max Edge Length'].hide_safe = True - - precision: FloatProperty( - name="Precision", - default=0.1, - precision=4, - update=updateNode) - - mode: EnumProperty( - name="Mode", - description="Algorithm used for conversion", - items=modes, default="Basic", - update=set_sockets) - - shape_type: EnumProperty( - name="Type", - description="Algorithm used for conversion", - items=shape_types, default="Solid", - update=set_shape_sockets) - - surface_deviation: FloatProperty( - name="Surface Deviation", - default=10, - min=1e-2, - precision=4, - update=updateNode) - angle_deviation: FloatProperty( - name="Angle Deviation", - default=30, - min=5, - precision=3, - update=updateNode) - relative_surface_deviation: BoolProperty( - name='Relative Surface Deviation', - default=False, - update=updateNode) - max_edge_length: FloatProperty( - name="max_edge_length", - default=1, - soft_min=0.1, - precision=4, - update=updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, "shape_type", expand=True) - layout.prop(self, "mode") - if self.mode == 'Standard': - layout.prop(self, "relative_surface_deviation") - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvSurfaceSocket', "Face") - self.inputs.new('SvStringsSocket', "Precision").prop_name = 'precision' - self.inputs.new('SvStringsSocket', "Surface Deviation").prop_name = 'surface_deviation' - self.inputs.new('SvStringsSocket', "Angle Deviation").prop_name = 'angle_deviation' - self.inputs.new('SvStringsSocket', "Max Edge Length").prop_name = 'max_edge_length' - self.shape_type = "Solid" - self.inputs['Face'].hide_safe = True - self.update_sockets() - - self.outputs.new('SvVerticesSocket', "Verts") - self.outputs.new('SvStringsSocket', "Faces") - - - def basic_mesher(self): - solids = self.inputs[self["shape_type"]].sv_get() - precisions = self.inputs["Precision"].sv_get()[0] - verts = [] - faces = [] - for solid, precision in zip(*mlr([solids, precisions])): - if self.shape_type == 'Solid': - rawdata = solid.tessellate(precision) - else: - rawdata = solid.face.tessellate(precision) - - b_verts = [(v.x, v.y, v.z) for v in rawdata[0]] - b_faces = [f for f in rawdata[1]] - b_faces = clean(b_faces).tolist() if is_triangles_only(b_faces) else b_faces - - verts.append(b_verts) - faces.append(b_faces) - - return verts, faces - - def standard_mesher(self): - solids = self.inputs[self["shape_type"]].sv_get() - surface_deviation = self.inputs["Surface Deviation"].sv_get()[0] - angle_deviation = self.inputs["Angle Deviation"].sv_get()[0] - verts = [] - faces = [] - for solid, s_dev, ang_dev in zip(*mlr([solids, surface_deviation, angle_deviation])): - if self.shape_type == 'Solid': - shape = solid - else: - shape = solid.face - - mesh = MeshPart.meshFromShape( - Shape=shape, - LinearDeflection=s_dev, - AngularDeflection=math.radians(ang_dev), - Relative=self.relative_surface_deviation) - - verts.append([v[:] for v in mesh.Topology[0]]) - - b_faces = mesh.Topology[1] - b_faces = clean(b_faces).tolist() if is_triangles_only(b_faces) else b_faces - faces.append(b_faces) - - return verts, faces - - def mefisto_mesher(self): - solids = self.inputs[self["shape_type"]].sv_get() - max_edge_length = self.inputs['Max Edge Length'].sv_get()[0] - - verts = [] - faces = [] - for solid, max_edge in zip(*mlr([solids, max_edge_length])): - if self.shape_type == 'Solid': - shape = solid - else: - shape = solid.face - mesh = MeshPart.meshFromShape( - Shape=shape, - MaxLength=max_edge - ) - - verts.append([v[:] for v in mesh.Topology[0]]) - faces.append(mesh.Topology[1]) - - return verts, faces - - def trivial_mesher(self): - """ - this mode will produce a variety of polygon types (tris, quads, ngons...) - """ - solids = self.inputs[self["shape_type"]].sv_get() - - verts = [] - faces = [] - for solid in solids: - if self.shape_type == 'Solid': - shape = solid - else: - shape = solid.face - - new_verts, new_edges, new_faces = mesh_from_solid_faces(shape) - new_verts, new_edges, new_faces = recalc_normals(new_verts, new_edges, new_faces) - - verts.append(new_verts) - faces.append(new_faces) - - return verts, faces - - def lenient_mesher(self): - """ - this mode will produce a variety of polygon types (tris, quads, ngons...) - """ - solids = self.inputs[self["shape_type"]].sv_get() - - verts = [] - faces = [] - for idx, solid in enumerate(solids): - if self.shape_type == 'Solid': - shape = solid - else: - shape = solid.face - new_verts, new_faces = mesh_from_solid_faces_MOD(shape, quality=1.0) - - verts.append(new_verts) - faces.append(new_faces) - - return verts, faces - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - if self.mode == 'Basic': - verts, faces = self.basic_mesher() - elif self.mode == 'Standard': - verts, faces = self.standard_mesher() - elif self.mode == 'Mefisto': - verts, faces = self.mefisto_mesher() - elif self.mode == 'Lenient': - verts, faces = self.lenient_mesher() - else: # Trivial - verts, faces = self.trivial_mesher() - - self.outputs['Verts'].sv_set(verts) - self.outputs['Faces'].sv_set(faces) + solids = self.inputs[self["shape_type"]].sv_get() + + verts = [] + faces = [] + for idx, solid in enumerate(solids): + if self.shape_type == 'Solid': + shape = solid + else: + shape = solid.face + new_verts, new_faces = mesh_from_solid_faces_MOD(shape, quality=1.0) + + verts.append(new_verts) + faces.append(new_faces) + + return verts, faces + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + if self.mode == 'Basic': + verts, faces = self.basic_mesher() + elif self.mode == 'Standard': + verts, faces = self.standard_mesher() + elif self.mode == 'Mefisto': + verts, faces = self.mefisto_mesher() + elif self.mode == 'Lenient': + verts, faces = self.lenient_mesher() + else: # Trivial + verts, faces = self.trivial_mesher() + + self.outputs['Verts'].sv_set(verts) + self.outputs['Faces'].sv_set(faces) def register(): diff --git a/nodes/solid/solid_vertices.py b/nodes/solid/solid_vertices.py index 5678b5cb22..2fbbd7334b 100644 --- a/nodes/solid/solid_vertices.py +++ b/nodes/solid/solid_vertices.py @@ -1,46 +1,43 @@ +import bpy from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy +from sverchok.node_tree import SverchCustomTreeNode if FreeCAD is None: add_dummy('SvSolidVerticesNode', 'Solid Vertices', 'FreeCAD') -else: - import bpy - from sverchok.node_tree import SverchCustomTreeNode - class SvSolidVerticesNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Vertices - Tooltip: Get Vertices from Solid - """ - bl_idname = 'SvSolidVerticesNode' - bl_label = 'Solid Vertices' - bl_icon = 'VERTEXSEL' - solid_catergory = "Outputs" +class SvSolidVerticesNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Vertices + Tooltip: Get Vertices from Solid + """ + bl_idname = 'SvSolidVerticesNode' + bl_label = 'Solid Vertices' + bl_icon = 'VERTEXSEL' + sv_category = "Solid Outputs" + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.outputs.new('SvVerticesSocket', "Vertices") - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.outputs.new('SvVerticesSocket', "Vertices") + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return + solids = self.inputs[0].sv_get() - solids = self.inputs[0].sv_get() + verts_out = [] + verts_add = verts_out.append + for solid in solids: + verts = [] + for v in solid.Vertexes: + verts.append(v.Point[:]) - verts_out = [] - verts_add = verts_out.append - for solid in solids: - verts = [] - for v in solid.Vertexes: - verts.append(v.Point[:]) + verts_add(verts) - - verts_add(verts) - - self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Vertices'].sv_set(verts_out) def register(): diff --git a/nodes/solid/solid_viewer.py b/nodes/solid/solid_viewer.py index f3816e5ef5..9c6a3fe39a 100644 --- a/nodes/solid/solid_viewer.py +++ b/nodes/solid/solid_viewer.py @@ -4,524 +4,524 @@ # # SPDX-License-Identifier: GPL3 # License-Filename: LICENSE +from math import pi +import numpy as np + +import bgl +import bpy +import gpu +from gpu_extras.batch import batch_for_shader + +from bpy.props import ( + BoolProperty, FloatVectorProperty, EnumProperty, FloatProperty, IntProperty) + +from mathutils import Vector + +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import node_id, updateNode, enum_item_5 +from sverchok.ui.bgl_callback_3dview import callback_disable, callback_enable +from sverchok.utils.sv_shader_sources import dashed_vertex_shader, dashed_fragment_shader +from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata +from sverchok.utils.modules.geom_utils import obtain_normal3 as normal from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvSolidViewerNode', 'Solid Viewer', 'FreeCAD') -else: - from math import pi - import numpy as np - - import bgl - import bpy - import gpu - from gpu_extras.batch import batch_for_shader - - from bpy.props import ( - StringProperty, BoolProperty, FloatVectorProperty, EnumProperty, FloatProperty, IntProperty) - - from mathutils import Vector, Matrix - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import node_id, updateNode, enum_item_5 - from sverchok.ui.bgl_callback_3dview import callback_disable, callback_enable - from sverchok.utils.sv_shader_sources import dashed_vertex_shader, dashed_fragment_shader - from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata - from sverchok.utils.modules.geom_utils import obtain_normal3 as normal - - - - def generate_facet_data(verts, faces, face_color, vector_light): - out_verts = [] - out_vcols = [] - concat_verts = out_verts.extend - concat_vcols = out_vcols.extend - for face in faces: - vecs = [verts[j] for j in face] - concat_verts(vecs) - - normal_no = Vector(normal(*vecs)) - normal_no = (normal_no.angle(vector_light, 0)) / pi - - r = (normal_no * face_color[0]) - 0.1 - g = (normal_no * face_color[1]) - 0.1 - b = (normal_no * face_color[2]) - 0.1 - vcol = (r+0.2, g+0.2, b+0.2, face_color[3]) - concat_vcols([vcol, vcol, vcol]) - - return out_verts, out_vcols - - def generate_smooth_data(verts, faces, face_color, vector_light): - """ this piggy backs off bmesh's automated normal calculation... """ - out_vcols = [] - concat_vcols = out_vcols.append - - bm = bmesh_from_pydata(verts, [], faces, normal_update=True) - - for vert in bm.verts: - normal_no = (vert.normal.angle(vector_light, 0)) / pi - r = (normal_no * face_color[0]) - 0.1 - g = (normal_no * face_color[1]) - 0.1 - b = (normal_no * face_color[2]) - 0.1 - vcol = (r+0.2, g+0.2, b+0.2, face_color[3]) - concat_vcols(vcol) - - return out_vcols - - def generate_normals_data(verts, faces): - bm = bmesh_from_pydata(verts, [], faces, normal_update=True) - # normals = [vert.normal[:] for vert in bm.verts] - # for normal in normals: - # vcol = (normal[0] / 2) + 0.5, (normal[1] / 2) + 0.5, (normal[2] / 2) + 0.5, 1.0 - # concat_vcols(vcol) - values = np.array([vert.normal[:] for vert in bm.verts], dtype=np.float32) - values = ((values / 2) + 0.5) - alpha = np.zeros((values.shape[0],1), dtype=np.float32) - values = np.append(values, alpha, axis=1) - return values.tolist() - - def draw_uniform(GL_KIND, coords, indices, color, width=1, dashed_data=None): - if GL_KIND == 'LINES': - bgl.glLineWidth(width) - elif GL_KIND == 'POINTS': - bgl.glPointSize(width) - - params = dict(indices=indices) if indices else {} - - if GL_KIND == 'LINES' and dashed_data: - - shader = dashed_data.dashed_shader - batch = batch_for_shader(shader, 'LINES', {"inPos" : coords}, **params) - shader.bind() - shader.uniform_float("u_mvp", dashed_data.matrix) - shader.uniform_float("u_resolution", dashed_data.u_resolution) - shader.uniform_float("u_dashSize", dashed_data.u_dash_size) - shader.uniform_float("u_gapSize", dashed_data.u_gap_size) - shader.uniform_float("m_color", dashed_data.m_color) - batch.draw(shader) - - else: - # print(GL_KIND,coords) - shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') - batch = batch_for_shader(shader, GL_KIND, {"pos" : coords}, **params) - shader.bind() - shader.uniform_float("color", color) - - batch.draw(shader) - if GL_KIND == 'LINES': - bgl.glLineWidth(1) - elif GL_KIND == 'POINTS': - bgl.glPointSize(1) +def generate_facet_data(verts, faces, face_color, vector_light): + out_verts = [] + out_vcols = [] + concat_verts = out_verts.extend + concat_vcols = out_vcols.extend + for face in faces: + vecs = [verts[j] for j in face] + concat_verts(vecs) + + normal_no = Vector(normal(*vecs)) + normal_no = (normal_no.angle(vector_light, 0)) / pi + + r = (normal_no * face_color[0]) - 0.1 + g = (normal_no * face_color[1]) - 0.1 + b = (normal_no * face_color[2]) - 0.1 + vcol = (r+0.2, g+0.2, b+0.2, face_color[3]) + concat_vcols([vcol, vcol, vcol]) + + return out_verts, out_vcols + + +def generate_smooth_data(verts, faces, face_color, vector_light): + """ this piggy backs off bmesh's automated normal calculation... """ + out_vcols = [] + concat_vcols = out_vcols.append + + bm = bmesh_from_pydata(verts, [], faces, normal_update=True) + + for vert in bm.verts: + normal_no = (vert.normal.angle(vector_light, 0)) / pi + r = (normal_no * face_color[0]) - 0.1 + g = (normal_no * face_color[1]) - 0.1 + b = (normal_no * face_color[2]) - 0.1 + vcol = (r+0.2, g+0.2, b+0.2, face_color[3]) + concat_vcols(vcol) + + return out_vcols + + +def generate_normals_data(verts, faces): + bm = bmesh_from_pydata(verts, [], faces, normal_update=True) + # normals = [vert.normal[:] for vert in bm.verts] + # for normal in normals: + # vcol = (normal[0] / 2) + 0.5, (normal[1] / 2) + 0.5, (normal[2] / 2) + 0.5, 1.0 + # concat_vcols(vcol) + values = np.array([vert.normal[:] for vert in bm.verts], dtype=np.float32) + values = ((values / 2) + 0.5) + alpha = np.zeros((values.shape[0],1), dtype=np.float32) + values = np.append(values, alpha, axis=1) + return values.tolist() + + +def draw_uniform(GL_KIND, coords, indices, color, width=1, dashed_data=None): + if GL_KIND == 'LINES': + bgl.glLineWidth(width) + elif GL_KIND == 'POINTS': + bgl.glPointSize(width) + + params = dict(indices=indices) if indices else {} + + if GL_KIND == 'LINES' and dashed_data: + + shader = dashed_data.dashed_shader + batch = batch_for_shader(shader, 'LINES', {"inPos" : coords}, **params) + shader.bind() + shader.uniform_float("u_mvp", dashed_data.matrix) + shader.uniform_float("u_resolution", dashed_data.u_resolution) + shader.uniform_float("u_dashSize", dashed_data.u_dash_size) + shader.uniform_float("u_gapSize", dashed_data.u_gap_size) + shader.uniform_float("m_color", dashed_data.m_color) + batch.draw(shader) + else: + # print(GL_KIND,coords) + shader = gpu.shader.from_builtin('3D_UNIFORM_COLOR') + batch = batch_for_shader(shader, GL_KIND, {"pos" : coords}, **params) + shader.bind() + shader.uniform_float("color", color) - def draw_smooth(coords, vcols, indices=None): - shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') - params = dict(indices=indices) if indices else {} - batch = batch_for_shader(shader, 'TRIS', {"pos" : coords, "color": vcols}, **params) batch.draw(shader) - - def draw_verts(context, args): - geom, config = args + if GL_KIND == 'LINES': + bgl.glLineWidth(1) + elif GL_KIND == 'POINTS': + bgl.glPointSize(1) + + +def draw_smooth(coords, vcols, indices=None): + shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR') + params = dict(indices=indices) if indices else {} + batch = batch_for_shader(shader, 'TRIS', {"pos" : coords, "color": vcols}, **params) + batch.draw(shader) + + +def draw_verts(context, args): + geom, config = args + draw_uniform('POINTS', geom.verts, None, config.vcol, config.point_size) + + +def pack_dashed_config(config): + dashed_config = lambda: None + dashed_config.matrix = config.matrix + dashed_config.u_resolution = config.u_resolution + dashed_config.u_dash_size = config.u_dash_size + dashed_config.u_gap_size = config.u_gap_size + dashed_config.m_color = config.line4f + dashed_config.dashed_shader = config.dashed_shader + return dashed_config + +def draw_lines_uniform(context, config, coords, indices, line_color, line_width): + if config.draw_dashed: + config.matrix = context.region_data.perspective_matrix + dashed_config = pack_dashed_config(config) + + params = dict(dashed_data=dashed_config) if config.draw_dashed else {} + draw_uniform('LINES', coords, indices, config.line4f, config.line_width, **params) + + +def face_geom(geom, config): + solids = geom.solids + precision = config.precision + f_offset = 0 + f_verts=[] + f_faces=[] + for solid in solids: + rawdata = solid.tessellate(precision) + b_verts = [] + b_faces = [] + for v in rawdata[0]: + b_verts.append((v.x, v.y, v.z)) + for f in rawdata[1]: + b_faces.append([c + f_offset for c in f]) + f_offset += len(b_verts) + f_verts.extend(b_verts) + f_faces.extend(b_faces) + geom.f_verts = f_verts + geom.f_faces = f_faces + + if config.shade == 'facet': + facet_verts, facet_verts_vcols = generate_facet_data(f_verts, f_faces, config.face4f, config.vector_light) + geom.facet_verts = facet_verts + geom.facet_verts_vcols = facet_verts_vcols + elif config.shade == 'smooth': + geom.smooth_vcols = generate_smooth_data(geom.f_verts, f_faces, config.face4f, config.vector_light) + elif config.shade == 'normals': + geom.smooth_vnorms = generate_normals_data(geom.f_verts, f_faces) + + +def draw_faces_uniform(context, args): + geom, config = args + # print(geom.f_faces, config.shade) + if config.draw_gl_wireframe: + bgl.glPolygonMode(bgl.GL_FRONT_AND_BACK, bgl.GL_LINE) + + if config.draw_gl_polygonoffset: + bgl.glEnable(bgl.GL_POLYGON_OFFSET_FILL) + bgl.glPolygonOffset(1.0, 1.0) + + if config.shade == "flat": + draw_uniform('TRIS', geom.f_verts, geom.f_faces, config.face4f) + elif config.shade == "facet": + draw_smooth(geom.facet_verts, geom.facet_verts_vcols) + elif config.shade == "smooth": + draw_smooth(geom.f_verts, geom.smooth_vcols, indices=geom.f_faces) + elif config.shade == 'normals': + draw_smooth(geom.f_verts, geom.smooth_vnorms, indices=geom.f_faces) + + if config.draw_gl_wireframe: + bgl.glPolygonMode(bgl.GL_FRONT_AND_BACK, bgl.GL_FILL) + + +def edges_geom(geom, config): + solids = geom.solids + curve_def = config.edges_steps + e_offset = 0 + ss_verts=[] + ss_edges=[] + for solid in solids: + s_verts = [] + s_edges = [] + for edge in solid.Edges: + start = edge.FirstParameter + end = edge.LastParameter + e_range = end - start + for i in range(curve_def): + v = edge.valueAt(start+ e_range*i/(curve_def-1)) + s_verts.append(v[:]) + e_idx = [(i,j) for i, j in zip(range(e_offset, e_offset + curve_def-1), range(e_offset + 1, e_offset + curve_def)) ] + + e_offset += curve_def + s_edges.extend(e_idx) + ss_verts.extend(s_verts) + ss_edges.extend(s_edges) + geom.e_vertices = ss_verts + geom.e_edges = ss_edges + + +def draw_complex(context, args): + geom, config = args + if config.draw_gl_polygonoffset: + bgl.glDisable(bgl.GL_POLYGON_OFFSET_FILL) + + if config.shade != 'normals': + bgl.glEnable(bgl.GL_BLEND) + + if config.display_edges: + draw_lines_uniform(context, config, geom.e_vertices, geom.e_edges, config.line4f, config.line_width) + if config.display_faces: + draw_faces_uniform(context, args) + if config.display_verts: draw_uniform('POINTS', geom.verts, None, config.vcol, config.point_size) - - def pack_dashed_config(config): - dashed_config = lambda: None - dashed_config.matrix = config.matrix - dashed_config.u_resolution = config.u_resolution - dashed_config.u_dash_size = config.u_dash_size - dashed_config.u_gap_size = config.u_gap_size - dashed_config.m_color = config.line4f - dashed_config.dashed_shader = config.dashed_shader - return dashed_config - - def draw_lines_uniform(context, config, coords, indices, line_color, line_width): - if config.draw_dashed: - config.matrix = context.region_data.perspective_matrix - dashed_config = pack_dashed_config(config) - - params = dict(dashed_data=dashed_config) if config.draw_dashed else {} - draw_uniform('LINES', coords, indices, config.line4f, config.line_width, **params) - - - def face_geom(geom, config): - solids = geom.solids - precision = config.precision - f_offset = 0 - f_verts=[] - f_faces=[] - for solid in solids: - rawdata = solid.tessellate(precision) - b_verts = [] - b_faces = [] - for v in rawdata[0]: - b_verts.append((v.x, v.y, v.z)) - for f in rawdata[1]: - b_faces.append([c + f_offset for c in f]) - f_offset += len(b_verts) - f_verts.extend(b_verts) - f_faces.extend(b_faces) - geom.f_verts = f_verts - geom.f_faces = f_faces - - if config.shade == 'facet': - facet_verts, facet_verts_vcols = generate_facet_data(f_verts, f_faces, config.face4f, config.vector_light) - geom.facet_verts = facet_verts - geom.facet_verts_vcols = facet_verts_vcols - elif config.shade == 'smooth': - geom.smooth_vcols = generate_smooth_data(geom.f_verts, f_faces, config.face4f, config.vector_light) - elif config.shade == 'normals': - geom.smooth_vnorms = generate_normals_data(geom.f_verts, f_faces) - - def draw_faces_uniform(context, args): - geom, config = args - # print(geom.f_faces, config.shade) - if config.draw_gl_wireframe: - bgl.glPolygonMode(bgl.GL_FRONT_AND_BACK, bgl.GL_LINE) - - if config.draw_gl_polygonoffset: - bgl.glEnable(bgl.GL_POLYGON_OFFSET_FILL) - bgl.glPolygonOffset(1.0, 1.0) - - if config.shade == "flat": - draw_uniform('TRIS', geom.f_verts, geom.f_faces, config.face4f) - elif config.shade == "facet": - draw_smooth(geom.facet_verts, geom.facet_verts_vcols) - elif config.shade == "smooth": - draw_smooth(geom.f_verts, geom.smooth_vcols, indices=geom.f_faces) - elif config.shade == 'normals': - draw_smooth(geom.f_verts, geom.smooth_vnorms, indices=geom.f_faces) - - if config.draw_gl_wireframe: - bgl.glPolygonMode(bgl.GL_FRONT_AND_BACK, bgl.GL_FILL) - - - def edges_geom(geom, config): - solids = geom.solids - curve_def = config.edges_steps - e_offset = 0 - ss_verts=[] - ss_edges=[] - for solid in solids: - s_verts = [] - s_edges = [] - for edge in solid.Edges: - start = edge.FirstParameter - end = edge.LastParameter - e_range = end - start - for i in range(curve_def): - v = edge.valueAt(start+ e_range*i/(curve_def-1)) - s_verts.append(v[:]) - e_idx = [(i,j) for i, j in zip(range(e_offset, e_offset + curve_def-1), range(e_offset + 1, e_offset + curve_def)) ] - - e_offset += curve_def - s_edges.extend(e_idx) - ss_verts.extend(s_verts) - ss_edges.extend(s_edges) - geom.e_vertices = ss_verts - geom.e_edges = ss_edges - - def draw_complex(context, args): - geom, config = args - if config.draw_gl_polygonoffset: - bgl.glDisable(bgl.GL_POLYGON_OFFSET_FILL) - - if config.shade != 'normals': - bgl.glEnable(bgl.GL_BLEND) - - if config.display_edges: - draw_lines_uniform(context, config, geom.e_vertices, geom.e_edges, config.line4f, config.line_width) - if config.display_faces: - draw_faces_uniform(context, args) - if config.display_verts: - draw_uniform('POINTS', geom.verts, None, config.vcol, config.point_size) - if config.shade != 'normals': - bgl.glDisable(bgl.GL_BLEND) - if config.draw_gl_polygonoffset: - # or restore to the state found when entering this function. TODO! - bgl.glDisable(bgl.GL_POLYGON_OFFSET_FILL) - - - class SvSolidViewerNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: solid viewer - Tooltip: drawing solids on 3d view - - manily a copy of the viewer draw with modifications to ease solid viewing - """ - - bl_idname = 'SvSolidViewerNode' - bl_label = 'Solid Viewer' - bl_icon = 'GREASEPENCIL' - sv_icon = 'SV_DRAW_VIEWER' - solid_catergory = "Outputs" - node_dict = {} - - def wrapped_update(self, context=None): - self.populate_node_with_custom_shader_from_text() - if context: - self.process_node(context) - - - #n_id: StringProperty(default='') - activate: BoolProperty(name='Show', description='Activate', default=True, update=updateNode) - - vert_color: FloatVectorProperty( - subtype='COLOR', min=0, max=1, default=(0.8, 0.8, 0.8, 1.0), - name='vert color', size=4, update=updateNode) - - edge_color: FloatVectorProperty( - subtype='COLOR', min=0, max=1, default=(0.5, 1.0, 0.5, 1.0), - name='edge color', size=4, update=updateNode) - - face_color: FloatVectorProperty( - subtype='COLOR', min=0, max=1, default=(0.14, 0.54, 0.81, 0.5), - name='face color', size=4, update=updateNode) - - vector_light: FloatVectorProperty( - name='vector light', subtype='DIRECTION', min=0, max=1, size=3, - default=(0.2, 0.6, 0.4), update=updateNode) - - extended_matrix: BoolProperty( - default=False, - description='Allows mesh.transform(matrix) operation, quite fast!') - # viewer props - precision: FloatProperty( - name="Precision", - default=1, - precision=4, - update=updateNode) - edges_steps: IntProperty( - name="Edges Resolution", - default=50, - update=updateNode) - tesselate_modes = [ - ('Standard', 'Standard', '', 1), - ('Mefisto', 'Mefisto', '', 2), - ] - tesselate_mode: EnumProperty( - name="Tessellate mode", - description="Algorithm used for conversion", - items=tesselate_modes, default="Standard", - ) - surface_deviation: FloatProperty( - name="Surface Deviation", - default=10, - min=1e-2, - precision=4, - update=updateNode) - angle_deviation: FloatProperty( - name="Angle Deviation", - default=30, - min=5, - precision=3, - update=updateNode) - relative_surface_deviation: BoolProperty( - name='Relative Surface Deviation', - default=False, - update=updateNode) - max_edge_length: FloatProperty( - name="max_edge_length", - default=1, - soft_min=0.1, - precision=4, - update=updateNode) - - # glGet with argument GL_POINT_SIZE_RANGE - point_size: FloatProperty(description="glPointSize( GLfloat size)", update=updateNode, default=4.0, min=1.0, max=15.0) - line_width: IntProperty(description="glLineWidth( GLfloat width)", update=updateNode, default=1, min=1, max=5) - - display_verts: BoolProperty(default=True, update=updateNode, name="display verts") - display_edges: BoolProperty(default=True, update=updateNode, name="display edges") - display_faces: BoolProperty(default=True, update=updateNode, name="display faces") - draw_gl_wireframe: BoolProperty(default=False, update=updateNode, name="draw gl wireframe") - draw_gl_polygonoffset: BoolProperty(default=True, update=updateNode, name="draw gl polygon offset") - - selected_draw_mode: EnumProperty( - items=enum_item_5(["flat", "facet", "smooth", "normals"], ['SNAP_VOLUME', 'ALIASED', 'ANTIALIASED', 'ORIENTATION_LOCAL']), - description="pick how the node will draw faces", - default="flat", update=updateNode + if config.shade != 'normals': + bgl.glDisable(bgl.GL_BLEND) + if config.draw_gl_polygonoffset: + # or restore to the state found when entering this function. TODO! + bgl.glDisable(bgl.GL_POLYGON_OFFSET_FILL) + + +class SvSolidViewerNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: solid viewer + Tooltip: drawing solids on 3d view + + manily a copy of the viewer draw with modifications to ease solid viewing + """ + + bl_idname = 'SvSolidViewerNode' + bl_label = 'Solid Viewer' + bl_icon = 'GREASEPENCIL' + sv_icon = 'SV_DRAW_VIEWER' + sv_category = "Solid Outputs" + node_dict = {} + + def wrapped_update(self, context=None): + self.populate_node_with_custom_shader_from_text() + if context: + self.process_node(context) + + #n_id: StringProperty(default='') + activate: BoolProperty(name='Show', description='Activate', default=True, update=updateNode) + + vert_color: FloatVectorProperty( + subtype='COLOR', min=0, max=1, default=(0.8, 0.8, 0.8, 1.0), + name='vert color', size=4, update=updateNode) + + edge_color: FloatVectorProperty( + subtype='COLOR', min=0, max=1, default=(0.5, 1.0, 0.5, 1.0), + name='edge color', size=4, update=updateNode) + + face_color: FloatVectorProperty( + subtype='COLOR', min=0, max=1, default=(0.14, 0.54, 0.81, 0.5), + name='face color', size=4, update=updateNode) + + vector_light: FloatVectorProperty( + name='vector light', subtype='DIRECTION', min=0, max=1, size=3, + default=(0.2, 0.6, 0.4), update=updateNode) + + extended_matrix: BoolProperty( + default=False, + description='Allows mesh.transform(matrix) operation, quite fast!') + # viewer props + precision: FloatProperty( + name="Precision", + default=1, + precision=4, + update=updateNode) + edges_steps: IntProperty( + name="Edges Resolution", + default=50, + update=updateNode) + tesselate_modes = [ + ('Standard', 'Standard', '', 1), + ('Mefisto', 'Mefisto', '', 2), + ] + tesselate_mode: EnumProperty( + name="Tessellate mode", + description="Algorithm used for conversion", + items=tesselate_modes, default="Standard", + ) + surface_deviation: FloatProperty( + name="Surface Deviation", + default=10, + min=1e-2, + precision=4, + update=updateNode) + angle_deviation: FloatProperty( + name="Angle Deviation", + default=30, + min=5, + precision=3, + update=updateNode) + relative_surface_deviation: BoolProperty( + name='Relative Surface Deviation', + default=False, + update=updateNode) + max_edge_length: FloatProperty( + name="max_edge_length", + default=1, + soft_min=0.1, + precision=4, + update=updateNode) + + # glGet with argument GL_POINT_SIZE_RANGE + point_size: FloatProperty(description="glPointSize( GLfloat size)", update=updateNode, default=4.0, min=1.0, max=15.0) + line_width: IntProperty(description="glLineWidth( GLfloat width)", update=updateNode, default=1, min=1, max=5) + + display_verts: BoolProperty(default=True, update=updateNode, name="display verts") + display_edges: BoolProperty(default=True, update=updateNode, name="display edges") + display_faces: BoolProperty(default=True, update=updateNode, name="display faces") + draw_gl_wireframe: BoolProperty(default=False, update=updateNode, name="draw gl wireframe") + draw_gl_polygonoffset: BoolProperty(default=True, update=updateNode, name="draw gl polygon offset") + + selected_draw_mode: EnumProperty( + items=enum_item_5(["flat", "facet", "smooth", "normals"], ['SNAP_VOLUME', 'ALIASED', 'ANTIALIASED', 'ORIENTATION_LOCAL']), + description="pick how the node will draw faces", + default="flat", update=updateNode + ) + + # dashed line props + use_dashed: BoolProperty(name='use dashes', update=updateNode) + u_dash_size: FloatProperty(default=0.12, min=0.0001, name="dash size", update=updateNode) + u_gap_size: FloatProperty(default=0.19, min=0.0001, name="gap size", update=updateNode) + u_resolution: FloatVectorProperty(default=(25.0, 18.0), size=2, min=0.01, name="resolution", update=updateNode) + + def sv_init(self, context): + inew = self.inputs.new + inew('SvSolidSocket', 'Solid') + + self.node_dict[hash(self)] = {} + + def draw_buttons(self, context, layout): + r0 = layout.row() + r0.prop(self, "activate", text="", icon="HIDE_" + ("OFF" if self.activate else "ON")) + r0.separator() + r0.prop(self, "selected_draw_mode", expand=True, text='') + + b1 = layout.column() + if b1: + inside_box = b1.row(align=True) + button_column = inside_box.column(align=True) + button_column.prop(self, "display_verts", text='', icon="UV_VERTEXSEL") + button_column.prop(self, "display_edges", text='', icon="UV_EDGESEL") + button_column.prop(self, "display_faces", text='', icon="UV_FACESEL") + + colors_column = inside_box.column(align=True) + colors_column.prop(self, "vert_color", text='') + colors_column.prop(self, "edge_color", text='') + + if not self.selected_draw_mode == 'normals': + colors_column.prop(self, "face_color", text='') + + row = layout.row(align=True) + self.wrapper_tracked_ui_draw_op(row, "node.sverchok_solid_baker_mk3", icon='OUTLINER_OB_MESH', text="B A K E") + # row.separator() + # self.wrapper_tracked_ui_draw_op(row, "node.view3d_align_from", icon='CURSOR', text='') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + self.draw_additional_props(context, layout, n_panel=True) + layout.prop(self, "use_dashed") + if self.use_dashed: + layout.prop(self, "u_dash_size") + layout.prop(self, "u_gap_size") + layout.row().prop(self, "u_resolution") + + layout.prop(self, 'vector_light', text='') + + def bake(self): + bpy.ops.node.sverchok_mesh_baker_mk3( + node_name=self.name, tree_name=self.id_data.name ) - # dashed line props - use_dashed: BoolProperty(name='use dashes', update=updateNode) - u_dash_size: FloatProperty(default=0.12, min=0.0001, name="dash size", update=updateNode) - u_gap_size: FloatProperty(default=0.19, min=0.0001, name="gap size", update=updateNode) - u_resolution: FloatVectorProperty(default=(25.0, 18.0), size=2, min=0.01, name="resolution", update=updateNode) - - def sv_init(self, context): - inew = self.inputs.new - inew('SvSolidSocket', 'Solid') - - self.node_dict[hash(self)] = {} - - def draw_buttons(self, context, layout): - r0 = layout.row() - r0.prop(self, "activate", text="", icon="HIDE_" + ("OFF" if self.activate else "ON")) - r0.separator() - r0.prop(self, "selected_draw_mode", expand=True, text='') - - b1 = layout.column() - if b1: - inside_box = b1.row(align=True) - button_column = inside_box.column(align=True) - button_column.prop(self, "display_verts", text='', icon="UV_VERTEXSEL") - button_column.prop(self, "display_edges", text='', icon="UV_EDGESEL") - button_column.prop(self, "display_faces", text='', icon="UV_FACESEL") - - colors_column = inside_box.column(align=True) - colors_column.prop(self, "vert_color", text='') - colors_column.prop(self, "edge_color", text='') - - if not self.selected_draw_mode == 'normals': - colors_column.prop(self, "face_color", text='') - - row = layout.row(align=True) - self.wrapper_tracked_ui_draw_op(row, "node.sverchok_solid_baker_mk3", icon='OUTLINER_OB_MESH', text="B A K E") - # row.separator() - # self.wrapper_tracked_ui_draw_op(row, "node.view3d_align_from", icon='CURSOR', text='') - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - self.draw_additional_props(context, layout, n_panel=True) - layout.prop(self, "use_dashed") - if self.use_dashed: - layout.prop(self, "u_dash_size") - layout.prop(self, "u_gap_size") - layout.row().prop(self, "u_resolution") - - layout.prop(self, 'vector_light', text='') - - def bake(self): - bpy.ops.node.sverchok_mesh_baker_mk3( - node_name=self.name, tree_name=self.id_data.name - ) - - def rclick_menu(self, context, layout): - self.draw_additional_props(context, layout, n_panel=False) - - def draw_additional_props(self, context, layout, n_panel=True): - layout.label(text="Solid Display") - layout.prop(self, 'precision') - layout.prop(self, 'edges_steps') - layout.separator() - - layout.prop(self, 'point_size', text='Point Size') - layout.prop(self, 'line_width', text='Edge Width') - layout.separator() - layout.prop(self, 'draw_gl_wireframe', toggle=True) - layout.prop(self, 'draw_gl_polygonoffset', toggle=True) - layout.separator() - layout.label(text="Solid Bake") - if n_panel: - layout.prop(self, 'tesselate_mode') - else: - layout.prop_menu_enum(self, "tesselate_mode") - if self.tesselate_mode == 'Standard': - layout.prop(self, "relative_surface_deviation") - layout.prop(self, 'surface_deviation') - layout.prop(self, 'angle_deviation') - else: - layout.prop(self, 'max_edge_length') - layout.separator() - - def add_gl_stuff_to_config(self, config): - config.dashed_shader = gpu.types.GPUShader(dashed_vertex_shader, dashed_fragment_shader) - - def fill_config(self): - - config = lambda: None - config.vector_light = self.vector_light[:] - config.vcol = self.vert_color[:] - config.line4f = self.edge_color[:] - config.face4f = self.face_color[:] - config.display_verts = self.display_verts - config.display_edges = self.display_edges - config.display_faces = self.display_faces - config.shade = self.selected_draw_mode - config.draw_gl_wireframe = self.draw_gl_wireframe - config.draw_gl_polygonoffset = self.draw_gl_polygonoffset - config.point_size = self.point_size - config.line_width = self.line_width - config.extended_matrix = self.extended_matrix - config.draw_dashed = self.use_dashed - config.u_dash_size = self.u_dash_size - config.u_gap_size = self.u_gap_size - config.u_resolution = self.u_resolution[:] - config.precision = self.precision - config.edges_steps = self.edges_steps - - return config - - def format_draw_data(self, func=None, args=None): - return { - 'tree_name': self.id_data.name[:], - 'custom_function': func, - 'args': args} - - def process(self): - - - if not (self.id_data.sv_show and self.activate): - callback_disable(node_id(self)) - return + def rclick_menu(self, context, layout): + self.draw_additional_props(context, layout, n_panel=False) + + def draw_additional_props(self, context, layout, n_panel=True): + layout.label(text="Solid Display") + layout.prop(self, 'precision') + layout.prop(self, 'edges_steps') + layout.separator() + + layout.prop(self, 'point_size', text='Point Size') + layout.prop(self, 'line_width', text='Edge Width') + layout.separator() + layout.prop(self, 'draw_gl_wireframe', toggle=True) + layout.prop(self, 'draw_gl_polygonoffset', toggle=True) + layout.separator() + layout.label(text="Solid Bake") + if n_panel: + layout.prop(self, 'tesselate_mode') + else: + layout.prop_menu_enum(self, "tesselate_mode") + if self.tesselate_mode == 'Standard': + layout.prop(self, "relative_surface_deviation") + layout.prop(self, 'surface_deviation') + layout.prop(self, 'angle_deviation') + else: + layout.prop(self, 'max_edge_length') + layout.separator() + + def add_gl_stuff_to_config(self, config): + config.dashed_shader = gpu.types.GPUShader(dashed_vertex_shader, dashed_fragment_shader) + + def fill_config(self): + + config = lambda: None + config.vector_light = self.vector_light[:] + config.vcol = self.vert_color[:] + config.line4f = self.edge_color[:] + config.face4f = self.face_color[:] + config.display_verts = self.display_verts + config.display_edges = self.display_edges + config.display_faces = self.display_faces + config.shade = self.selected_draw_mode + config.draw_gl_wireframe = self.draw_gl_wireframe + config.draw_gl_polygonoffset = self.draw_gl_polygonoffset + config.point_size = self.point_size + config.line_width = self.line_width + config.extended_matrix = self.extended_matrix + config.draw_dashed = self.use_dashed + config.u_dash_size = self.u_dash_size + config.u_gap_size = self.u_gap_size + config.u_resolution = self.u_resolution[:] + config.precision = self.precision + config.edges_steps = self.edges_steps + + return config + + def format_draw_data(self, func=None, args=None): + return { + 'tree_name': self.id_data.name[:], + 'custom_function': func, + 'args': args} + + def process(self): + + if not (self.id_data.sv_show and self.activate): + callback_disable(node_id(self)) + return - n_id = node_id(self) - callback_disable(n_id) + n_id = node_id(self) + callback_disable(n_id) - if not any([self.display_verts, self.display_edges, self.display_faces]): - return + if not any([self.display_verts, self.display_edges, self.display_faces]): + return - if self.inputs[0].is_linked: + if self.inputs[0].is_linked: - display_faces = self.display_faces - display_edges = self.display_edges + display_faces = self.display_faces + display_edges = self.display_edges - config = self.fill_config() - solids = self.inputs[0].sv_get() + config = self.fill_config() + solids = self.inputs[0].sv_get() - geom = lambda: None - geom.solids = solids - geom.verts = [v.Point[:] for s in solids for v in s.Vertexes] + geom = lambda: None + geom.solids = solids + geom.verts = [v.Point[:] for s in solids for v in s.Vertexes] - if self.display_verts and not any([display_edges, display_faces]): - geom.verts = [v.Point[:] for s in solids for v in s.Vertexes] - gl_instructions = self.format_draw_data(func=draw_verts, args=(geom, config)) - callback_enable(n_id, gl_instructions) - return - - if self.display_verts: - geom.verts = [v.Point[:] for s in solids for v in s.Vertexes] - if self.display_edges: - if self.use_dashed: - self.add_gl_stuff_to_config(config) - edges_geom(geom, config) - if self.display_faces: - face_geom(geom, config) - gl_instructions = self.format_draw_data(func=draw_complex, args=(geom, config)) + if self.display_verts and not any([display_edges, display_faces]): + geom.verts = [v.Point[:] for s in solids for v in s.Vertexes] + gl_instructions = self.format_draw_data(func=draw_verts, args=(geom, config)) callback_enable(n_id, gl_instructions) return - def sv_copy(self, node): - self.n_id = '' - - - def sv_free(self): - callback_disable(node_id(self)) - - def show_viewport(self, is_show: bool): - """It should be called by node tree to show/hide objects""" - if not self.activate: - # just ignore request - pass + if self.display_verts: + geom.verts = [v.Point[:] for s in solids for v in s.Vertexes] + if self.display_edges: + if self.use_dashed: + self.add_gl_stuff_to_config(config) + edges_geom(geom, config) + if self.display_faces: + face_geom(geom, config) + gl_instructions = self.format_draw_data(func=draw_complex, args=(geom, config)) + callback_enable(n_id, gl_instructions) + return + + def sv_copy(self, node): + self.n_id = '' + + def sv_free(self): + callback_disable(node_id(self)) + + def show_viewport(self, is_show: bool): + """It should be called by node tree to show/hide objects""" + if not self.activate: + # just ignore request + pass + else: + if is_show: + self.process() else: - if is_show: - self.process() - else: - callback_disable(node_id(self)) + callback_disable(node_id(self)) def register(): if FreeCAD is not None: diff --git a/nodes/solid/solidify_face.py b/nodes/solid/solidify_face.py index 6964bfae92..1e7f5b5b61 100644 --- a/nodes/solid/solidify_face.py +++ b/nodes/solid/solidify_face.py @@ -33,7 +33,7 @@ class SvSolidFaceSolidifyNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solidify Face (Solid)' bl_icon = 'EDGESEL' sv_icon = 'SV_SOLIDIFY_FACE' - solid_catergory = "Operators" + sv_category = "Solid Operators" refine_solid: BoolProperty( name="Refine Solid", diff --git a/nodes/solid/sphere_solid.py b/nodes/solid/sphere_solid.py index 6e338a2b73..0f7da0a796 100644 --- a/nodes/solid/sphere_solid.py +++ b/nodes/solid/sphere_solid.py @@ -1,95 +1,92 @@ +import bpy +from bpy.props import FloatProperty, FloatVectorProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvSphereSolidNode', 'Sphere (Solid)', 'FreeCAD') else: - import bpy - from bpy.props import FloatProperty, FloatVectorProperty - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr - import Part from FreeCAD import Base - class SvSphereSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Solid Sphere - Tooltip: Create Solid Sphere - """ - bl_idname = 'SvSphereSolidNode' - bl_label = 'Sphere (Solid)' - bl_icon = 'META_BALL' - solid_catergory = "Inputs" - - sphere_radius: FloatProperty( - name="Radius", - default=1, - precision=4, - update=updateNode) - - sphere_angle1: FloatProperty( - name="Angle 1", - description="Min Theta angle (angle with Z axis)", - default=-90, - min=-90, - max=90, - precision=4, - update=updateNode) - sphere_angle2: FloatProperty( - name="Angle 2", - description="Max Theta angle (angle with Z axis)", - default=90, - min=-90, - max=90, - precision=4, - update=updateNode) - sphere_angle3: FloatProperty( - name="Angle 3", - description="Max Pi angle (angle with X axis)", - default=360, - min=0, - max=360, - precision=4, - update=updateNode) - - origin: FloatVectorProperty( - name="Origin", - default=(0, 0, 0), - size=3, - update=updateNode) - direction: FloatVectorProperty( - name="Origin", - default=(0, 0, 1), - size=3, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvStringsSocket', "Radius").prop_name = 'sphere_radius' - self.inputs.new('SvStringsSocket', "Angle 1").prop_name = 'sphere_angle1' - self.inputs.new('SvStringsSocket', "Angle 2").prop_name = 'sphere_angle2' - self.inputs.new('SvStringsSocket', "Angle 3").prop_name = 'sphere_angle3' - self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' - self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' - - self.outputs.new('SvSolidSocket', "Solid") - - - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - p = [s.sv_get()[0] for s in self.inputs] - - solids = [] - for rad, ang1, ang2, ang3, origin, direc in zip(*mlr(p)): - sphere = Part.makeSphere(rad, Base.Vector(origin), Base.Vector(direc), ang1, ang2, ang3) - solids.append(sphere) - - self.outputs['Solid'].sv_set(solids) + +class SvSphereSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Solid Sphere + Tooltip: Create Solid Sphere + """ + bl_idname = 'SvSphereSolidNode' + bl_label = 'Sphere (Solid)' + bl_icon = 'META_BALL' + sv_category = "Solid Inputs" + + sphere_radius: FloatProperty( + name="Radius", + default=1, + precision=4, + update=updateNode) + + sphere_angle1: FloatProperty( + name="Angle 1", + description="Min Theta angle (angle with Z axis)", + default=-90, + min=-90, + max=90, + precision=4, + update=updateNode) + sphere_angle2: FloatProperty( + name="Angle 2", + description="Max Theta angle (angle with Z axis)", + default=90, + min=-90, + max=90, + precision=4, + update=updateNode) + sphere_angle3: FloatProperty( + name="Angle 3", + description="Max Pi angle (angle with X axis)", + default=360, + min=0, + max=360, + precision=4, + update=updateNode) + + origin: FloatVectorProperty( + name="Origin", + default=(0, 0, 0), + size=3, + update=updateNode) + direction: FloatVectorProperty( + name="Origin", + default=(0, 0, 1), + size=3, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', "Radius").prop_name = 'sphere_radius' + self.inputs.new('SvStringsSocket', "Angle 1").prop_name = 'sphere_angle1' + self.inputs.new('SvStringsSocket', "Angle 2").prop_name = 'sphere_angle2' + self.inputs.new('SvStringsSocket', "Angle 3").prop_name = 'sphere_angle3' + self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' + self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' + + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + p = [s.sv_get()[0] for s in self.inputs] + + solids = [] + for rad, ang1, ang2, ang3, origin, direc in zip(*mlr(p)): + sphere = Part.makeSphere(rad, Base.Vector(origin), Base.Vector(direc), ang1, ang2, ang3) + solids.append(sphere) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/split_solid.py b/nodes/solid/split_solid.py index 703926d25c..2ac56d82b4 100644 --- a/nodes/solid/split_solid.py +++ b/nodes/solid/split_solid.py @@ -49,7 +49,7 @@ class SvSplitSolidNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Split Solid by Face' bl_icon = 'EDGESEL' sv_icon = 'SV_SPLIT_SOLID' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSolidSocket', "Solid") diff --git a/nodes/solid/sweep_face.py b/nodes/solid/sweep_face.py index 36601e7361..d9b2a89df9 100644 --- a/nodes/solid/sweep_face.py +++ b/nodes/solid/sweep_face.py @@ -36,7 +36,7 @@ class SvSweepSolidFaceNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Sweep Face (Solid)' bl_icon = 'EDGESEL' sv_icon = 'SV_SWEEP_FACE' - solid_catergory = "Operators" + sv_category = "Solid Operators" use_frenet : BoolProperty( name = "Frenet", diff --git a/nodes/solid/torus_solid.py b/nodes/solid/torus_solid.py index bfb6d2be16..8197905781 100644 --- a/nodes/solid/torus_solid.py +++ b/nodes/solid/torus_solid.py @@ -1,82 +1,81 @@ +import bpy +from bpy.props import FloatProperty, FloatVectorProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvToursSolidNode', 'Torus (Solid)', 'FreeCAD') else: - - import bpy - from bpy.props import FloatProperty, FloatVectorProperty - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr import Part from FreeCAD import Base - class SvToursSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Torus Cylinder - Tooltip: Create Solid Torus - """ - bl_idname = 'SvToursSolidNode' - bl_label = 'Torus (Solid)' - bl_icon = 'MESH_TORUS' - solid_catergory = "Inputs" - - cylinder_radius: FloatProperty( - name="Radius R", - default=1, - precision=4, - update=updateNode) - cylinder_radius_top: FloatProperty( - name="Radius r", - default=0.25, - precision=4, - update=updateNode) - torus_angle: FloatProperty( - name="Angle", - description="Min Theta angle (angle with Z axis)", - default=360, - min=-0, - max=360, - precision=4, - update=updateNode) - - origin: FloatVectorProperty( - name="Origin", - default=(0, 0, 0), - size=3, - update=updateNode) - direction: FloatVectorProperty( - name="Origin", - default=(0, 0, 1), - size=3, - update=updateNode) - - - def sv_init(self, context): - self.inputs.new('SvStringsSocket', "Radius").prop_name = 'cylinder_radius' - self.inputs.new('SvStringsSocket', "Radius 2").prop_name = 'cylinder_radius_top' - - self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' - self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' - self.inputs.new('SvStringsSocket', "Angle").prop_name = 'torus_angle' - - self.outputs.new('SvSolidSocket', "Solid") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - params = [s.sv_get()[0] for s in self.inputs] - - solids = [] - for rad, rad_small, origin, direc, angle in zip(*mlr(params)): - solid = Part.makeTorus(rad, rad_small, Base.Vector(origin), Base.Vector(direc), 0, 360, angle) - solids.append(solid) - - self.outputs['Solid'].sv_set(solids) + +class SvToursSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Torus Cylinder + Tooltip: Create Solid Torus + """ + bl_idname = 'SvToursSolidNode' + bl_label = 'Torus (Solid)' + bl_icon = 'MESH_TORUS' + sv_category = "Solid Inputs" + + cylinder_radius: FloatProperty( + name="Radius R", + default=1, + precision=4, + update=updateNode) + cylinder_radius_top: FloatProperty( + name="Radius r", + default=0.25, + precision=4, + update=updateNode) + torus_angle: FloatProperty( + name="Angle", + description="Min Theta angle (angle with Z axis)", + default=360, + min=-0, + max=360, + precision=4, + update=updateNode) + + origin: FloatVectorProperty( + name="Origin", + default=(0, 0, 0), + size=3, + update=updateNode) + direction: FloatVectorProperty( + name="Origin", + default=(0, 0, 1), + size=3, + update=updateNode) + + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', "Radius").prop_name = 'cylinder_radius' + self.inputs.new('SvStringsSocket', "Radius 2").prop_name = 'cylinder_radius_top' + + self.inputs.new('SvVerticesSocket', "Origin").prop_name = 'origin' + self.inputs.new('SvVerticesSocket', "Direction").prop_name = 'direction' + self.inputs.new('SvStringsSocket', "Angle").prop_name = 'torus_angle' + + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + params = [s.sv_get()[0] for s in self.inputs] + + solids = [] + for rad, rad_small, origin, direc, angle in zip(*mlr(params)): + solid = Part.makeTorus(rad, rad_small, Base.Vector(origin), Base.Vector(direc), 0, 360, angle) + solids.append(solid) + + self.outputs['Solid'].sv_set(solids) def register(): diff --git a/nodes/solid/transform_solid.py b/nodes/solid/transform_solid.py index a464d3e724..a34cc24ecf 100644 --- a/nodes/solid/transform_solid.py +++ b/nodes/solid/transform_solid.py @@ -1,54 +1,51 @@ +import bpy +from bpy.props import FloatProperty +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import updateNode, match_long_repeat as mlr +from sverchok.utils.solid import transform_solid from sverchok.dependencies import FreeCAD from sverchok.utils.dummy_nodes import add_dummy if FreeCAD is None: add_dummy('SvTransformSolidNode', 'Transform Solid', 'FreeCAD') -else: - import bpy - from bpy.props import FloatProperty - - from sverchok.node_tree import SverchCustomTreeNode - from sverchok.data_structure import updateNode, match_long_repeat as mlr - from sverchok.utils.solid import transform_solid - from FreeCAD import Base - - class SvTransformSolidNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Apply Matrix to Solid - Tooltip: Transform Solid with Matrix - """ - bl_idname = 'SvTransformSolidNode' - bl_label = 'Transform Solid' - bl_icon = 'MESH_CUBE' - sv_icon = 'SV_TRANSFORM_SOLID' - solid_catergory = "Operators" - - precision: FloatProperty( - name="Precision", - default=0.1, - precision=4, - update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvSolidSocket', "Solid") - self.inputs.new('SvMatrixSocket', "Matrix") - self.outputs.new('SvSolidSocket', "Solid") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - solids_in = self.inputs[0].sv_get() - matrixes = self.inputs[1].sv_get() - solids = [] - for solid, matrix in zip(*mlr([solids_in, matrixes])): - solid_o = transform_solid(matrix, solid) - solids.append(solid_o) - - self.outputs['Solid'].sv_set(solids) +class SvTransformSolidNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Apply Matrix to Solid + Tooltip: Transform Solid with Matrix + """ + bl_idname = 'SvTransformSolidNode' + bl_label = 'Transform Solid' + bl_icon = 'MESH_CUBE' + sv_icon = 'SV_TRANSFORM_SOLID' + sv_category = "Solid Operators" + + precision: FloatProperty( + name="Precision", + default=0.1, + precision=4, + update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvSolidSocket', "Solid") + self.inputs.new('SvMatrixSocket', "Matrix") + self.outputs.new('SvSolidSocket', "Solid") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + solids_in = self.inputs[0].sv_get() + matrixes = self.inputs[1].sv_get() + solids = [] + for solid, matrix in zip(*mlr([solids_in, matrixes])): + solid_o = transform_solid(matrix, solid) + solids.append(solid_o) + + self.outputs['Solid'].sv_set(solids) + def register(): if FreeCAD is not None: diff --git a/nodes/solid/validate.py b/nodes/solid/validate.py index add0918712..277583ea1e 100644 --- a/nodes/solid/validate.py +++ b/nodes/solid/validate.py @@ -25,8 +25,7 @@ class SvSolidValidateNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvSolidValidateNode' bl_label = 'Validate & Fix Solid' - bl_icon = 'OUTLINER_OB_EMPTY' - solid_catergory = "Operators" + sv_category = "Solid Operators" precision : FloatProperty( name = "Precision", diff --git a/nodes/solid/volume.py b/nodes/solid/volume.py index 28ec05a1cf..f91bf05b09 100644 --- a/nodes/solid/volume.py +++ b/nodes/solid/volume.py @@ -25,9 +25,8 @@ class SvSolidVolumeNode(SverchCustomTreeNode, bpy.types.Node): """ bl_idname = 'SvSolidVolumeNode' bl_label = 'Solid Volume' - bl_icon = 'OUTLINER_OB_EMPTY' bl_icon = 'SNAP_VOLUME' - solid_catergory = "Operators" + sv_category = "Solid Operators" def sv_init(self, context): self.inputs.new('SvSolidSocket', "Solid") diff --git a/nodes/solid/wire_face.py b/nodes/solid/wire_face.py index c8cb6209c6..2d6d0fad04 100644 --- a/nodes/solid/wire_face.py +++ b/nodes/solid/wire_face.py @@ -34,7 +34,7 @@ class SvSolidWireFaceNode(SverchCustomTreeNode, bpy.types.Node): bl_label = "Face from Curves (Solid)" bl_icon = 'EDGESEL' sv_icon = 'SV_CURVES_FACE' - solid_catergory = "Inputs" + sv_category = "Solid Inputs" planar : BoolProperty( name = "Planar", diff --git a/nodes/spatial/voronoi3d.py b/nodes/spatial/voronoi3d.py index 013492d603..2bea08d742 100644 --- a/nodes/spatial/voronoi3d.py +++ b/nodes/spatial/voronoi3d.py @@ -24,231 +24,234 @@ else: from scipy.spatial import Voronoi - class SvExVoronoi3DNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Voronoi 3D - Tooltip: Generate 3D Voronoi diagram - """ - bl_idname = 'SvExVoronoi3DNode' - bl_label = 'Voronoi 3D' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_VORONOI' - - out_modes = [ - ('RIDGES', "Ridges", "Ridges", 0), - ('REGIONS', "Regions", "Regions", 1) - ] - - out_mode : EnumProperty( - name = "Output", - items = out_modes, - default = 'REGIONS', - update = updateNode) - - join : BoolProperty( - name = "Join", - default = False, - update = updateNode) - - closed_only : BoolProperty( - name = "Closed regions only", - default = True, - update = updateNode) - - normals : BoolProperty( - name = "Correct normals", - default = True, - update = updateNode) - - def update_sockets(self, context): - self.inputs['Clipping'].hide_safe = not self.do_clip - updateNode(self, context) - - do_clip : BoolProperty( - name = "Clip", - default = True, - update = update_sockets) - - clipping : FloatProperty( - name = "Clipping", - default = 1.0, - min = 0.0, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - self.inputs.new('SvStringsSocket', "Clipping").prop_name = 'clipping' - self.outputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Edges") - self.outputs.new('SvStringsSocket', "Faces") - self.update_sockets(context) - - def draw_buttons(self, context, layout): - layout.prop(self, "out_mode", expand=True) - if self.out_mode == 'REGIONS': - layout.prop(self, "closed_only") - layout.prop(self, "normals") - layout.prop(self, "do_clip") - layout.prop(self, "join") - - def make_regions(self, diagram): - faces_per_site = defaultdict(list) - nsites = len(diagram.point_region) - nridges = len(diagram.ridge_points) - open_sites = set() - for ridge_idx in range(nridges): - site_idx_1, site_idx_2 = diagram.ridge_points[ridge_idx] - face = diagram.ridge_vertices[ridge_idx] - if -1 in face: - open_sites.add(site_idx_1) - open_sites.add(site_idx_2) - continue - faces_per_site[site_idx_1].append(face) - faces_per_site[site_idx_2].append(face) - - new_verts = [] - new_edges = [] - new_faces = [] - - for site_idx in sorted(faces_per_site.keys()): - if self.closed_only and site_idx in open_sites: - continue - done_verts = dict() - bm = bmesh.new() - new_vert = bm.verts.new - new_face = bm.faces.new - for face in faces_per_site[site_idx]: - face_bm_verts = [] - for vertex_idx in face: - if vertex_idx not in done_verts: - bm_vert = new_vert(diagram.vertices[vertex_idx]) - done_verts[vertex_idx] = bm_vert - else: - bm_vert = done_verts[vertex_idx] - face_bm_verts.append(bm_vert) - new_face(face_bm_verts) - bm.verts.index_update() - bm.verts.ensure_lookup_table() - bm.faces.index_update() - bm.edges.index_update() - - if self.closed_only and any (v.is_boundary for v in bm.verts): - bm.free() - continue - - if self.normals: - bm.normal_update() - bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:]) - - region_verts, region_edges, region_faces = pydata_from_bmesh(bm) - bm.free() - new_verts.append(region_verts) - new_edges.append(region_edges) - new_faces.append(region_faces) - - return new_verts, new_edges, new_faces - - def split_ridges(self, vertices, edges, faces): - result_verts = [] - result_edges = [] - result_faces = [] - for face in faces: - bm = bmesh.new() - new_vert = bm.verts.new - new_face = bm.faces.new + +class SvExVoronoi3DNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Voronoi 3D + Tooltip: Generate 3D Voronoi diagram + """ + bl_idname = 'SvExVoronoi3DNode' + bl_label = 'Voronoi 3D' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_VORONOI' + + out_modes = [ + ('RIDGES', "Ridges", "Ridges", 0), + ('REGIONS', "Regions", "Regions", 1) + ] + + out_mode : EnumProperty( + name = "Output", + items = out_modes, + default = 'REGIONS', + update = updateNode) + + join : BoolProperty( + name = "Join", + default = False, + update = updateNode) + + closed_only : BoolProperty( + name = "Closed regions only", + default = True, + update = updateNode) + + normals : BoolProperty( + name = "Correct normals", + default = True, + update = updateNode) + + def update_sockets(self, context): + self.inputs['Clipping'].hide_safe = not self.do_clip + updateNode(self, context) + + do_clip : BoolProperty( + name = "Clip", + default = True, + update = update_sockets) + + clipping : FloatProperty( + name = "Clipping", + default = 1.0, + min = 0.0, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + self.inputs.new('SvStringsSocket', "Clipping").prop_name = 'clipping' + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvStringsSocket', "Faces") + self.update_sockets(context) + + def draw_buttons(self, context, layout): + layout.prop(self, "out_mode", expand=True) + if self.out_mode == 'REGIONS': + layout.prop(self, "closed_only") + layout.prop(self, "normals") + layout.prop(self, "do_clip") + layout.prop(self, "join") + + def make_regions(self, diagram): + faces_per_site = defaultdict(list) + nsites = len(diagram.point_region) + nridges = len(diagram.ridge_points) + open_sites = set() + for ridge_idx in range(nridges): + site_idx_1, site_idx_2 = diagram.ridge_points[ridge_idx] + face = diagram.ridge_vertices[ridge_idx] + if -1 in face: + open_sites.add(site_idx_1) + open_sites.add(site_idx_2) + continue + faces_per_site[site_idx_1].append(face) + faces_per_site[site_idx_2].append(face) + + new_verts = [] + new_edges = [] + new_faces = [] + + for site_idx in sorted(faces_per_site.keys()): + if self.closed_only and site_idx in open_sites: + continue + done_verts = dict() + bm = bmesh.new() + new_vert = bm.verts.new + new_face = bm.faces.new + for face in faces_per_site[site_idx]: face_bm_verts = [] for vertex_idx in face: - vertex = vertices[vertex_idx] - bm_vert = new_vert(vertex) + if vertex_idx not in done_verts: + bm_vert = new_vert(diagram.vertices[vertex_idx]) + done_verts[vertex_idx] = bm_vert + else: + bm_vert = done_verts[vertex_idx] face_bm_verts.append(bm_vert) new_face(face_bm_verts) - bm.verts.index_update() - bm.verts.ensure_lookup_table() - bm.faces.index_update() - bm.edges.index_update() - ridge_verts, ridge_edges, ridge_faces = pydata_from_bmesh(bm) - result_verts.append(ridge_verts) - result_edges.append(ridge_edges) - result_faces.append(ridge_faces) - return result_verts, result_edges, result_faces - - def clip_mesh(self, bounds, vertices, edges, faces, fill=False, iterate=None): - if iterate is None: - iterate = get_data_nesting_level(vertices) > 2 - if iterate: - vertices_result = [] - edges_result = [] - faces_result = [] - for vertices_item, edges_item, faces_item in zip(vertices, edges, faces): - new_vertices, new_edges, new_faces = self.clip_mesh(bounds, vertices_item, edges_item, faces_item, fill=fill, iterate=False) - if new_vertices: - vertices_result.append(new_vertices) - edges_result.append(new_edges) - faces_result.append(new_faces) - return vertices_result, edges_result, faces_result - else: - bm = bmesh_from_pydata(vertices, edges, faces) - bmesh_clip(bm, bounds, fill) - vertices, edges, faces = pydata_from_bmesh(bm) - bm.free() - return vertices, edges, faces - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - clipping_s = self.inputs['Clipping'].sv_get() - - verts_out = [] - edges_out = [] - faces_out = [] - for sites, clipping in zip_long_repeat(vertices_s, clipping_s): - if isinstance(clipping, (list, tuple)): - clipping = clipping[0] + bm.verts.index_update() + bm.verts.ensure_lookup_table() + bm.faces.index_update() + bm.edges.index_update() - diagram = Voronoi(sites) - if self.do_clip: - bounds = calc_bounds(sites, clipping) - - if self.out_mode == 'RIDGES': - new_verts = diagram.vertices.tolist() - new_faces = [e for e in diagram.ridge_vertices if not -1 in e] - new_edges = polygons_to_edges([new_faces], True)[0] - if self.join: - if self.do_clip: - new_verts, new_edges, new_faces = self.clip_mesh(bounds, new_verts, new_edges, new_faces, fill=False) - verts_out.append(new_verts) - edges_out.append(new_edges) - faces_out.append(new_faces) - else: - new_verts, new_edges, new_faces = self.split_ridges(new_verts, new_edges, new_faces) - if self.do_clip: - new_verts, new_edges, new_faces = self.clip_mesh(bounds, new_verts, new_edges, new_faces, fill=False, iterate=True) - verts_out.extend(new_verts) - edges_out.extend(new_edges) - faces_out.extend(new_faces) - else: # REGIONS - new_verts, new_edges, new_faces = self.make_regions(diagram) - if self.join: - new_verts, new_edges, new_faces = mesh_join(new_verts, new_edges, new_faces) - new_verts = [new_verts] - new_edges = [new_edges] - new_faces = [new_faces] + if self.closed_only and any (v.is_boundary for v in bm.verts): + bm.free() + continue + + if self.normals: + bm.normal_update() + bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:]) + + region_verts, region_edges, region_faces = pydata_from_bmesh(bm) + bm.free() + new_verts.append(region_verts) + new_edges.append(region_edges) + new_faces.append(region_faces) + + return new_verts, new_edges, new_faces + + def split_ridges(self, vertices, edges, faces): + result_verts = [] + result_edges = [] + result_faces = [] + for face in faces: + bm = bmesh.new() + new_vert = bm.verts.new + new_face = bm.faces.new + face_bm_verts = [] + for vertex_idx in face: + vertex = vertices[vertex_idx] + bm_vert = new_vert(vertex) + face_bm_verts.append(bm_vert) + new_face(face_bm_verts) + bm.verts.index_update() + bm.verts.ensure_lookup_table() + bm.faces.index_update() + bm.edges.index_update() + ridge_verts, ridge_edges, ridge_faces = pydata_from_bmesh(bm) + result_verts.append(ridge_verts) + result_edges.append(ridge_edges) + result_faces.append(ridge_faces) + return result_verts, result_edges, result_faces + + def clip_mesh(self, bounds, vertices, edges, faces, fill=False, iterate=None): + if iterate is None: + iterate = get_data_nesting_level(vertices) > 2 + if iterate: + vertices_result = [] + edges_result = [] + faces_result = [] + for vertices_item, edges_item, faces_item in zip(vertices, edges, faces): + new_vertices, new_edges, new_faces = self.clip_mesh(bounds, vertices_item, edges_item, faces_item, fill=fill, iterate=False) + if new_vertices: + vertices_result.append(new_vertices) + edges_result.append(new_edges) + faces_result.append(new_faces) + return vertices_result, edges_result, faces_result + else: + bm = bmesh_from_pydata(vertices, edges, faces) + bmesh_clip(bm, bounds, fill) + vertices, edges, faces = pydata_from_bmesh(bm) + bm.free() + return vertices, edges, faces + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + clipping_s = self.inputs['Clipping'].sv_get() + + verts_out = [] + edges_out = [] + faces_out = [] + for sites, clipping in zip_long_repeat(vertices_s, clipping_s): + if isinstance(clipping, (list, tuple)): + clipping = clipping[0] + + diagram = Voronoi(sites) + if self.do_clip: + bounds = calc_bounds(sites, clipping) + + if self.out_mode == 'RIDGES': + new_verts = diagram.vertices.tolist() + new_faces = [e for e in diagram.ridge_vertices if not -1 in e] + new_edges = polygons_to_edges([new_faces], True)[0] + if self.join: + if self.do_clip: + new_verts, new_edges, new_faces = self.clip_mesh(bounds, new_verts, new_edges, new_faces, fill=False) + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + else: + new_verts, new_edges, new_faces = self.split_ridges(new_verts, new_edges, new_faces) if self.do_clip: - new_verts, new_edges, new_faces = self.clip_mesh(bounds, new_verts, new_edges, new_faces, fill=True) + new_verts, new_edges, new_faces = self.clip_mesh(bounds, new_verts, new_edges, new_faces, fill=False, iterate=True) verts_out.extend(new_verts) edges_out.extend(new_edges) faces_out.extend(new_faces) + else: # REGIONS + new_verts, new_edges, new_faces = self.make_regions(diagram) + if self.join: + new_verts, new_edges, new_faces = mesh_join(new_verts, new_edges, new_faces) + new_verts = [new_verts] + new_edges = [new_edges] + new_faces = [new_faces] + if self.do_clip: + new_verts, new_edges, new_faces = self.clip_mesh(bounds, new_verts, new_edges, new_faces, fill=True) + verts_out.extend(new_verts) + edges_out.extend(new_edges) + faces_out.extend(new_faces) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['Faces'].sv_set(faces_out) - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Edges'].sv_set(edges_out) - self.outputs['Faces'].sv_set(faces_out) def register(): if scipy is not None: bpy.utils.register_class(SvExVoronoi3DNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExVoronoi3DNode) diff --git a/nodes/spatial/voronoi_sphere.py b/nodes/spatial/voronoi_sphere.py index 639ebb2e91..666730ba7f 100644 --- a/nodes/spatial/voronoi_sphere.py +++ b/nodes/spatial/voronoi_sphere.py @@ -1,19 +1,14 @@ -from collections import defaultdict import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty +from bpy.props import FloatProperty import bmesh -from mathutils import Matrix -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level +from sverchok.data_structure import updateNode, zip_long_repeat from sverchok.utils.math import project_to_sphere -from sverchok.utils.sv_mesh_utils import polygons_to_edges from sverchok.utils.sv_bmesh_utils import pydata_from_bmesh, bmesh_from_pydata -from sverchok.utils.logging import info, exception from sverchok.utils.dummy_nodes import add_dummy from sverchok.dependencies import scipy @@ -22,71 +17,74 @@ else: from scipy.spatial import SphericalVoronoi - class SvExVoronoiSphereNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Voronoi Sphere - Tooltip: Generate Voronoi diagram on the surface of the sphere - """ - bl_idname = 'SvExVoronoiSphereNode' - bl_label = 'Voronoi Sphere' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_VORONOI' - - radius: FloatProperty(name="Radius", default=1.0, min=0.0, update=updateNode) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - d = self.inputs.new('SvVerticesSocket', "Center") - d.use_prop = True - d.default_property = (0.0, 0.0, 0.0) - self.inputs.new('SvStringsSocket', "Radius").prop_name = "radius" - - self.outputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Edges") - self.outputs.new('SvStringsSocket', "Faces") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - radius_s = self.inputs['Radius'].sv_get() - center_s = self.inputs['Center'].sv_get() - - verts_out = [] - edges_out = [] - faces_out = [] - - for sites, center, radius in zip_long_repeat(vertices_s, center_s, radius_s): - if isinstance(radius, (list, tuple)): - radius = radius[0] - center = center[0] - sites = np.array([project_to_sphere(center, radius, v) for v in sites]) - - vor = SphericalVoronoi(sites, radius=radius, center=np.array(center)) - vor.sort_vertices_of_regions() - - new_verts = vor.vertices.tolist() - new_faces = vor.regions - #new_edges = polygons_to_edges([new_faces], True)[0] - - bm2 = bmesh_from_pydata(new_verts, [], new_faces, normal_update=True) - bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces) - new_verts, new_edges, new_faces = pydata_from_bmesh(bm2) - bm2.free() - - verts_out.append(new_verts) - edges_out.append(new_edges) - faces_out.append(new_faces) - - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Edges'].sv_set(edges_out) - self.outputs['Faces'].sv_set(faces_out) + +class SvExVoronoiSphereNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Voronoi Sphere + Tooltip: Generate Voronoi diagram on the surface of the sphere + """ + bl_idname = 'SvExVoronoiSphereNode' + bl_label = 'Voronoi Sphere' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_VORONOI' + + radius: FloatProperty(name="Radius", default=1.0, min=0.0, update=updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + d = self.inputs.new('SvVerticesSocket', "Center") + d.use_prop = True + d.default_property = (0.0, 0.0, 0.0) + self.inputs.new('SvStringsSocket', "Radius").prop_name = "radius" + + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvStringsSocket', "Faces") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + radius_s = self.inputs['Radius'].sv_get() + center_s = self.inputs['Center'].sv_get() + + verts_out = [] + edges_out = [] + faces_out = [] + + for sites, center, radius in zip_long_repeat(vertices_s, center_s, radius_s): + if isinstance(radius, (list, tuple)): + radius = radius[0] + center = center[0] + sites = np.array([project_to_sphere(center, radius, v) for v in sites]) + + vor = SphericalVoronoi(sites, radius=radius, center=np.array(center)) + vor.sort_vertices_of_regions() + + new_verts = vor.vertices.tolist() + new_faces = vor.regions + #new_edges = polygons_to_edges([new_faces], True)[0] + + bm2 = bmesh_from_pydata(new_verts, [], new_faces, normal_update=True) + bmesh.ops.recalc_face_normals(bm2, faces=bm2.faces) + new_verts, new_edges, new_faces = pydata_from_bmesh(bm2) + bm2.free() + + verts_out.append(new_verts) + edges_out.append(new_edges) + faces_out.append(new_faces) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['Faces'].sv_set(faces_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExVoronoiSphereNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExVoronoiSphereNode) diff --git a/nodes/surface/approximate_nurbs_surface.py b/nodes/surface/approximate_nurbs_surface.py index 97e9ae982e..3659301bad 100644 --- a/nodes/surface/approximate_nurbs_surface.py +++ b/nodes/surface/approximate_nurbs_surface.py @@ -12,149 +12,151 @@ add_dummy('SvExApproxNurbsSurfaceNode', "Approximate NURBS Surface", 'geomdl') else: from geomdl import fitting - - class SvExApproxNurbsSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: NURBS Surface - Tooltip: Approximate NURBS Surface - """ - bl_idname = 'SvExApproxNurbsSurfaceNode' - bl_label = 'Approximate NURBS Surface' - bl_icon = 'SURFACE_NSURFACE' - - input_modes = [ - ('1D', "Single list", "List of all control points (concatenated)", 1), - ('2D', "Separated lists", "List of lists of control points", 2) - ] - - def update_sockets(self, context): - self.inputs['USize'].hide_safe = self.input_mode == '2D' - self.inputs['PointsCntU'].hide_safe = not self.has_points_cnt - self.inputs['PointsCntV'].hide_safe = not self.has_points_cnt - updateNode(self, context) - - input_mode : EnumProperty( - name = "Input mode", - default = '1D', - items = input_modes, - update = update_sockets) - - u_size : IntProperty( - name = "U Size", - default = 5, - min = 3, - update = updateNode) - - degree_u : IntProperty( - name = "Degree U", - min = 2, max = 6, - default = 3, - update = updateNode) - - degree_v : IntProperty( - name = "Degree V", - min = 2, max = 6, - default = 3, - update = updateNode) - - centripetal : BoolProperty( - name = "Centripetal", - default = False, - update = updateNode) - - has_points_cnt : BoolProperty( - name = "Specify points count", - default = False, - update = update_sockets) - - points_cnt_u : IntProperty( - name = "Points count U", - min = 3, default = 5, - update = updateNode) - - points_cnt_v : IntProperty( - name = "Points count V", - min = 3, default = 5, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - self.inputs.new('SvStringsSocket', "USize").prop_name = 'u_size' - self.inputs.new('SvStringsSocket', "DegreeU").prop_name = 'degree_u' - self.inputs.new('SvStringsSocket', "DegreeV").prop_name = 'degree_v' - self.inputs.new('SvStringsSocket', "PointsCntU").prop_name = 'points_cnt_u' - self.inputs.new('SvStringsSocket', "PointsCntV").prop_name = 'points_cnt_v' - self.outputs.new('SvSurfaceSocket', "Surface") - self.outputs.new('SvVerticesSocket', "ControlPoints") - self.outputs.new('SvStringsSocket', "KnotsU") - self.outputs.new('SvStringsSocket', "KnotsV") - self.update_sockets(context) - - def draw_buttons(self, context, layout): - layout.prop(self, 'centripetal', toggle=True) - layout.prop(self, "input_mode") - layout.prop(self, 'has_points_cnt', toggle=True) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - u_size_s = self.inputs['USize'].sv_get() - degree_u_s = self.inputs['DegreeU'].sv_get() - degree_v_s = self.inputs['DegreeV'].sv_get() - points_cnt_u_s = self.inputs['PointsCntU'].sv_get() - points_cnt_v_s = self.inputs['PointsCntV'].sv_get() + + +class SvExApproxNurbsSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: NURBS Surface + Tooltip: Approximate NURBS Surface + """ + bl_idname = 'SvExApproxNurbsSurfaceNode' + bl_label = 'Approximate NURBS Surface' + bl_icon = 'SURFACE_NSURFACE' + + input_modes = [ + ('1D', "Single list", "List of all control points (concatenated)", 1), + ('2D', "Separated lists", "List of lists of control points", 2) + ] + + def update_sockets(self, context): + self.inputs['USize'].hide_safe = self.input_mode == '2D' + self.inputs['PointsCntU'].hide_safe = not self.has_points_cnt + self.inputs['PointsCntV'].hide_safe = not self.has_points_cnt + updateNode(self, context) + + input_mode : EnumProperty( + name = "Input mode", + default = '1D', + items = input_modes, + update = update_sockets) + + u_size : IntProperty( + name = "U Size", + default = 5, + min = 3, + update = updateNode) + + degree_u : IntProperty( + name = "Degree U", + min = 2, max = 6, + default = 3, + update = updateNode) + + degree_v : IntProperty( + name = "Degree V", + min = 2, max = 6, + default = 3, + update = updateNode) + + centripetal : BoolProperty( + name = "Centripetal", + default = False, + update = updateNode) + + has_points_cnt : BoolProperty( + name = "Specify points count", + default = False, + update = update_sockets) + + points_cnt_u : IntProperty( + name = "Points count U", + min = 3, default = 5, + update = updateNode) + + points_cnt_v : IntProperty( + name = "Points count V", + min = 3, default = 5, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + self.inputs.new('SvStringsSocket', "USize").prop_name = 'u_size' + self.inputs.new('SvStringsSocket', "DegreeU").prop_name = 'degree_u' + self.inputs.new('SvStringsSocket', "DegreeV").prop_name = 'degree_v' + self.inputs.new('SvStringsSocket', "PointsCntU").prop_name = 'points_cnt_u' + self.inputs.new('SvStringsSocket', "PointsCntV").prop_name = 'points_cnt_v' + self.outputs.new('SvSurfaceSocket', "Surface") + self.outputs.new('SvVerticesSocket', "ControlPoints") + self.outputs.new('SvStringsSocket', "KnotsU") + self.outputs.new('SvStringsSocket', "KnotsV") + self.update_sockets(context) + + def draw_buttons(self, context, layout): + layout.prop(self, 'centripetal', toggle=True) + layout.prop(self, "input_mode") + layout.prop(self, 'has_points_cnt', toggle=True) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + u_size_s = self.inputs['USize'].sv_get() + degree_u_s = self.inputs['DegreeU'].sv_get() + degree_v_s = self.inputs['DegreeV'].sv_get() + points_cnt_u_s = self.inputs['PointsCntU'].sv_get() + points_cnt_v_s = self.inputs['PointsCntV'].sv_get() + + if self.input_mode == '1D': + vertices_s = ensure_nesting_level(vertices_s, 3) + else: + vertices_s = ensure_nesting_level(vertices_s, 4) + + surfaces_out = [] + points_out = [] + knots_u_out = [] + knots_v_out = [] + for vertices, degree_u, degree_v, points_cnt_u, points_cnt_v, u_size in zip_long_repeat(vertices_s, degree_u_s, degree_v_s, points_cnt_u_s, points_cnt_v_s, u_size_s): + if isinstance(degree_u, (tuple, list)): + degree_u = degree_u[0] + if isinstance(degree_v, (tuple, list)): + degree_v = degree_v[0] + if isinstance(u_size, (tuple, list)): + u_size = u_size[0] + if isinstance(points_cnt_u, (tuple, list)): + points_cnt_u = points_cnt_u[0] + if isinstance(points_cnt_v, (tuple, list)): + points_cnt_v = points_cnt_v[0] if self.input_mode == '1D': - vertices_s = ensure_nesting_level(vertices_s, 3) + n_u = u_size + n_v = len(vertices) // n_u else: - vertices_s = ensure_nesting_level(vertices_s, 4) - - surfaces_out = [] - points_out = [] - knots_u_out = [] - knots_v_out = [] - for vertices, degree_u, degree_v, points_cnt_u, points_cnt_v, u_size in zip_long_repeat(vertices_s, degree_u_s, degree_v_s, points_cnt_u_s, points_cnt_v_s, u_size_s): - if isinstance(degree_u, (tuple, list)): - degree_u = degree_u[0] - if isinstance(degree_v, (tuple, list)): - degree_v = degree_v[0] - if isinstance(u_size, (tuple, list)): - u_size = u_size[0] - if isinstance(points_cnt_u, (tuple, list)): - points_cnt_u = points_cnt_u[0] - if isinstance(points_cnt_v, (tuple, list)): - points_cnt_v = points_cnt_v[0] - - if self.input_mode == '1D': - n_u = u_size - n_v = len(vertices) // n_u - else: - n_u = len(vertices[0]) - for i, verts_i in enumerate(vertices): - if len(verts_i) != n_u: - raise Exception("Number of vertices in row #{} is not the same as in the first ({} != {})!".format(i, n_u, len(verts_i))) - vertices = sum(vertices, []) - n_v = len(vertices) // n_u - - kwargs = dict(centripetal = self.centripetal) - if self.has_points_cnt: - kwargs['ctrlpts_size_u'] = points_cnt_u - kwargs['ctrlpts_size_v'] = points_cnt_v - - surf = fitting.approximate_surface(vertices, n_u, n_v, degree_u, degree_v, **kwargs) - - points_out.append(surf.ctrlpts2d) - knots_u_out.append(surf.knotvector_u) - knots_v_out.append(surf.knotvector_v) - surf = SvGeomdlSurface(surf) - surfaces_out.append(surf) - - self.outputs['Surface'].sv_set(surfaces_out) - self.outputs['ControlPoints'].sv_set(points_out) - self.outputs['KnotsU'].sv_set(knots_u_out) - self.outputs['KnotsV'].sv_set(knots_v_out) + n_u = len(vertices[0]) + for i, verts_i in enumerate(vertices): + if len(verts_i) != n_u: + raise Exception("Number of vertices in row #{} is not the same as in the first ({} != {})!".format(i, n_u, len(verts_i))) + vertices = sum(vertices, []) + n_v = len(vertices) // n_u + + kwargs = dict(centripetal = self.centripetal) + if self.has_points_cnt: + kwargs['ctrlpts_size_u'] = points_cnt_u + kwargs['ctrlpts_size_v'] = points_cnt_v + + surf = fitting.approximate_surface(vertices, n_u, n_v, degree_u, degree_v, **kwargs) + + points_out.append(surf.ctrlpts2d) + knots_u_out.append(surf.knotvector_u) + knots_v_out.append(surf.knotvector_v) + surf = SvGeomdlSurface(surf) + surfaces_out.append(surf) + + self.outputs['Surface'].sv_set(surfaces_out) + self.outputs['ControlPoints'].sv_set(points_out) + self.outputs['KnotsU'].sv_set(knots_u_out) + self.outputs['KnotsV'].sv_set(knots_v_out) + def register(): if geomdl is not None: diff --git a/nodes/surface/extremes.py b/nodes/surface/extremes.py index 4cf9d1faff..75cfa13e7e 100644 --- a/nodes/surface/extremes.py +++ b/nodes/surface/extremes.py @@ -1,14 +1,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -from mathutils import Matrix -from mathutils.kdtree import KDTree +from bpy.props import EnumProperty, BoolProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.surface import SvSurface from sverchok.utils.field.scalar import SvScalarField from sverchok.utils.dummy_nodes import add_dummy @@ -19,162 +15,165 @@ else: from scipy.optimize import minimize - class SvExSurfaceExtremesNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Surface Extremes - Tooltip: Find the point on the surface which provides the maximum or minimum for specified scalar field - """ - bl_idname = 'SvExSurfaceExtremesNode' - bl_label = 'Surface Extremes' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_SURFACE_EXTREMES' - - directions = [ - ('MIN', "Min", "Find the minimum of the field", 0), - ('MAX', "Max", "Find the maximum of the field", 1) - ] - - direction : EnumProperty( - name = "Direction", - items = directions, - default = 'MIN', - update = updateNode) - - find_global : BoolProperty( - name = "Search Best", - default = True, - update = updateNode) - - on_fail_options = [ - ('FAIL', "Fail", "Raise an exception (node becomes red)", 0), - ('SKIP', "Skip", "Skip such interval or curve - just return an empty set of points", 1) - ] - - on_fail : EnumProperty( - name = "On fail", - items = on_fail_options, - default = 'SKIP', - update = updateNode) - - methods = [ - ('L-BFGS-B', "L-BFGS-B", "L-BFGS-B algorithm", 0), - ('CG', "Conjugate Gradient", "Conjugate gradient algorithm", 1), - ('TNC', "Truncated Newton", "Truncated Newton algorithm", 2), - ('SLSQP', "SLSQP", "Sequential Least SQuares Programming algorithm", 3) - ] - method : EnumProperty( - name = "Method", - items = methods, - default = 'L-BFGS-B', - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'direction', expand=True) - layout.prop(self, 'find_global', toggle=True) - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - layout.prop(self, 'method') - layout.prop(self, 'on_fail') - - def sv_init(self, context): - self.inputs.new('SvSurfaceSocket', "Surface") - self.inputs.new('SvScalarFieldSocket', "Field") - self.inputs.new('SvVerticesSocket', "StartUV") - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvVerticesSocket', "UVPoint") - - def solve(self, surface, field, starts): - - def goal(p): - point = surface.evaluate(p[0], p[1]) - value = field.evaluate(point[0], point[1], point[2]) - if self.direction == 'MAX': - return - value - else: - return value - - u_min = surface.get_u_min() - u_max = surface.get_u_max() - v_min = surface.get_v_min() - v_max = surface.get_v_max() - - uvs = [] - values = [] - for start in starts: - if start is None: - init_u = (u_min+u_max)/2.0 - init_v = (v_min+v_max)/2.0 - else: - init_u = start[0] - init_v = start[1] - - result = minimize(goal, - x0 = np.array([init_u, init_v]), - bounds = [(u_min, u_max), (v_min, v_max)], - method = self.method - ) - if not result.success: - if self.on_fail == 'FAIL': - raise Exception(f"Can't find the extreme point of {surface} at {u_min}-{u_max}, {v_min}-{v_max}: {result.message}") - else: - #print(f"{init_u},{init_v} ==> {result.x} = {result.fun}") - uvs.append(result.x) - values.append(result.fun) - - uvs = np.array(uvs) - values = np.array(values) - - if self.find_global: - if len(values) > 0: - target = values.min() - good = (values == target) - return uvs[good] - else: - return uvs - else: - return uvs +class SvExSurfaceExtremesNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Surface Extremes + Tooltip: Find the point on the surface which provides the maximum or minimum for specified scalar field + """ + bl_idname = 'SvExSurfaceExtremesNode' + bl_label = 'Surface Extremes' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_SURFACE_EXTREMES' - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - surface_s = self.inputs['Surface'].sv_get() - surface_s = ensure_nesting_level(surface_s, 2, data_types=(SvSurface,)) - fields_s = self.inputs['Field'].sv_get() - fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) - if self.inputs['StartUV'].is_linked: - start_s = self.inputs['StartUV'].sv_get() - start_s = ensure_nesting_level(start_s, 4) - else: - start_s = [[[None]]] + directions = [ + ('MIN', "Min", "Find the minimum of the field", 0), + ('MAX', "Max", "Find the maximum of the field", 1) + ] - uv_out = [] - point_out = [] - for surfaces, fields, start_i in zip_long_repeat(surface_s, fields_s, start_s): - new_uv = [] - new_points = [] - for surface, field, starts in zip_long_repeat(surfaces, fields, start_i): - uvs = self.solve(surface, field, starts) - uv_points = [(u, v, 0) for u,v in uvs] + direction : EnumProperty( + name = "Direction", + items = directions, + default = 'MIN', + update = updateNode) - us = uvs[:,0] - vs = uvs[:,1] - points = surface.evaluate_array(us, vs) + find_global : BoolProperty( + name = "Search Best", + default = True, + update = updateNode) - new_uv.extend(uv_points) - new_points.extend(points.tolist()) - uv_out.append(new_uv) - point_out.append(new_points) + on_fail_options = [ + ('FAIL', "Fail", "Raise an exception (node becomes red)", 0), + ('SKIP', "Skip", "Skip such interval or curve - just return an empty set of points", 1) + ] + + on_fail : EnumProperty( + name = "On fail", + items = on_fail_options, + default = 'SKIP', + update = updateNode) + + methods = [ + ('L-BFGS-B', "L-BFGS-B", "L-BFGS-B algorithm", 0), + ('CG', "Conjugate Gradient", "Conjugate gradient algorithm", 1), + ('TNC', "Truncated Newton", "Truncated Newton algorithm", 2), + ('SLSQP', "SLSQP", "Sequential Least SQuares Programming algorithm", 3) + ] + + method : EnumProperty( + name = "Method", + items = methods, + default = 'L-BFGS-B', + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'direction', expand=True) + layout.prop(self, 'find_global', toggle=True) + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'method') + layout.prop(self, 'on_fail') + + def sv_init(self, context): + self.inputs.new('SvSurfaceSocket', "Surface") + self.inputs.new('SvScalarFieldSocket', "Field") + self.inputs.new('SvVerticesSocket', "StartUV") + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvVerticesSocket', "UVPoint") + + def solve(self, surface, field, starts): + + def goal(p): + point = surface.evaluate(p[0], p[1]) + value = field.evaluate(point[0], point[1], point[2]) + if self.direction == 'MAX': + return - value + else: + return value + + u_min = surface.get_u_min() + u_max = surface.get_u_max() + v_min = surface.get_v_min() + v_max = surface.get_v_max() + + uvs = [] + values = [] + for start in starts: + if start is None: + init_u = (u_min+u_max)/2.0 + init_v = (v_min+v_max)/2.0 + else: + init_u = start[0] + init_v = start[1] + + result = minimize(goal, + x0 = np.array([init_u, init_v]), + bounds = [(u_min, u_max), (v_min, v_max)], + method = self.method + ) + if not result.success: + if self.on_fail == 'FAIL': + raise Exception(f"Can't find the extreme point of {surface} at {u_min}-{u_max}, {v_min}-{v_max}: {result.message}") + else: + #print(f"{init_u},{init_v} ==> {result.x} = {result.fun}") + uvs.append(result.x) + values.append(result.fun) + + uvs = np.array(uvs) + values = np.array(values) + + if self.find_global: + if len(values) > 0: + target = values.min() + good = (values == target) + return uvs[good] + else: + return uvs + else: + return uvs + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + surface_s = self.inputs['Surface'].sv_get() + surface_s = ensure_nesting_level(surface_s, 2, data_types=(SvSurface,)) + fields_s = self.inputs['Field'].sv_get() + fields_s = ensure_nesting_level(fields_s, 2, data_types=(SvScalarField,)) + if self.inputs['StartUV'].is_linked: + start_s = self.inputs['StartUV'].sv_get() + start_s = ensure_nesting_level(start_s, 4) + else: + start_s = [[[None]]] + + uv_out = [] + point_out = [] + for surfaces, fields, start_i in zip_long_repeat(surface_s, fields_s, start_s): + new_uv = [] + new_points = [] + for surface, field, starts in zip_long_repeat(surfaces, fields, start_i): + uvs = self.solve(surface, field, starts) + uv_points = [(u, v, 0) for u,v in uvs] + + us = uvs[:,0] + vs = uvs[:,1] + points = surface.evaluate_array(us, vs) + + new_uv.extend(uv_points) + new_points.extend(points.tolist()) + uv_out.append(new_uv) + point_out.append(new_points) + + self.outputs['Point'].sv_set(point_out) + self.outputs['UVPoint'].sv_set(uv_out) - self.outputs['Point'].sv_set(point_out) - self.outputs['UVPoint'].sv_set(uv_out) def register(): if scipy is not None: bpy.utils.register_class(SvExSurfaceExtremesNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExSurfaceExtremesNode) diff --git a/nodes/surface/implicit_surface_raycast.py b/nodes/surface/implicit_surface_raycast.py index a48c93c972..4d574f6cb5 100644 --- a/nodes/surface/implicit_surface_raycast.py +++ b/nodes/surface/implicit_surface_raycast.py @@ -2,12 +2,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty, StringProperty -from mathutils import Vector, Matrix +from bpy.props import FloatProperty from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, match_long_repeat, ensure_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.field.scalar import SvScalarField from sverchok.utils.dummy_nodes import add_dummy from sverchok.dependencies import scipy @@ -17,119 +15,125 @@ else: from scipy.optimize import root_scalar - def goal(field, init, direction, iso_value): - def function(t): - p = init + t * direction - v = field.evaluate(p[0], p[1], p[2]) - return v - iso_value - return function - - def find_distance(field, init, direction, max_distance, iso_value): - init_value = field.evaluate(init[0], init[1], init[2]) - sign = (init_value - iso_value) - distance = max_distance - max_sections = 10 - i = 0 - while True: - i += 1 - if i > max_sections: - raise Exception(f"Can not find range where the field jumps over iso_value: init value at {init} = {init_value}, last value at {distance} = {value}") - p = init + direction * distance - value = field.evaluate(p[0], p[1], p[2]) - #print(value) - if (value - iso_value) * sign < 0: - break - distance /= 2.0 - - return distance - - def solve(field, init, direction, max_distance, iso_value): - distance = find_distance(field, init, direction, max_distance, iso_value) - sol = root_scalar(goal(field, init, direction, iso_value), method = 'ridder', - x0 = 0, - bracket = (0, distance) - ) - t = sol.root - p = init + t*direction - return t, p - - class SvExImplSurfaceRaycastNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Implicit Surface Raycast - Tooltip: Raycast onto implicit surface (defined by scalar field) - """ - bl_idname = 'SvExImplSurfaceRaycastNode' - bl_label = 'Implicit Surface Raycast' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_IMPL_SURF_RAYCAST' - - max_distance : FloatProperty( - name = "Max Distance", - default = 10.0, - min = 0.0, - update = updateNode) - - iso_value : FloatProperty( - name = "Iso Value", - default = 0.0, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvScalarFieldSocket', "Field") - p = self.inputs.new('SvVerticesSocket', "Vertices") - p.use_prop = True - p.default_property = (0.0, 0.0, 0.0) - p = self.inputs.new('SvVerticesSocket', "Direction") - p.use_prop = True - p.default_property = (0.0, 0.0, 1.0) - self.inputs.new('SvStringsSocket', 'IsoValue').prop_name = 'iso_value' - self.inputs.new('SvStringsSocket', 'MaxDistance').prop_name = 'max_distance' - self.outputs.new('SvVerticesSocket', 'Vertices') - self.outputs.new('SvStringsSocket', 'Distance') - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - field_s = self.inputs['Field'].sv_get() - verts_s = self.inputs['Vertices'].sv_get() - direction_s = self.inputs['Direction'].sv_get() - iso_value_s = self.inputs['IsoValue'].sv_get() - max_distance_s = self.inputs['MaxDistance'].sv_get() - - field_s = ensure_nesting_level(field_s, 2, data_types=(SvScalarField,)) - verts_s = ensure_nesting_level(verts_s, 3) - direction_s = ensure_nesting_level(direction_s, 3) - iso_value_s = ensure_nesting_level(iso_value_s, 2) - max_distance_s = ensure_nesting_level(max_distance_s, 2) - - verts_out = [] - distance_out = [] - - for fields, verts_i, directions, iso_value_i, max_distance_i in zip_long_repeat(field_s, verts_s, direction_s, iso_value_s, max_distance_s): - new_verts = [] - new_t = [] - for field, vert, direction, iso_value, max_distance in zip_long_repeat(fields, verts_i, directions, iso_value_i, max_distance_i): - direction = np.array(direction) - norm = np.linalg.norm(direction) - if norm == 0: - raise ValueError("Direction vector length is zero!") - direction = direction / norm - t, p = solve(field, np.array(vert), direction, max_distance, iso_value) - #t = t * norm - p = tuple(p) - new_verts.append(p) - new_t.append(t) - verts_out.append(new_verts) - distance_out.append(new_t) - - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Distance'].sv_set(distance_out) + +def goal(field, init, direction, iso_value): + def function(t): + p = init + t * direction + v = field.evaluate(p[0], p[1], p[2]) + return v - iso_value + return function + + +def find_distance(field, init, direction, max_distance, iso_value): + init_value = field.evaluate(init[0], init[1], init[2]) + sign = (init_value - iso_value) + distance = max_distance + max_sections = 10 + i = 0 + while True: + i += 1 + if i > max_sections: + raise Exception(f"Can not find range where the field jumps over iso_value: init value at {init} = {init_value}, last value at {distance} = {value}") + p = init + direction * distance + value = field.evaluate(p[0], p[1], p[2]) + #print(value) + if (value - iso_value) * sign < 0: + break + distance /= 2.0 + + return distance + + +def solve(field, init, direction, max_distance, iso_value): + distance = find_distance(field, init, direction, max_distance, iso_value) + sol = root_scalar(goal(field, init, direction, iso_value), method = 'ridder', + x0 = 0, + bracket = (0, distance) + ) + t = sol.root + p = init + t*direction + return t, p + + +class SvExImplSurfaceRaycastNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Implicit Surface Raycast + Tooltip: Raycast onto implicit surface (defined by scalar field) + """ + bl_idname = 'SvExImplSurfaceRaycastNode' + bl_label = 'Implicit Surface Raycast' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_IMPL_SURF_RAYCAST' + + max_distance : FloatProperty( + name = "Max Distance", + default = 10.0, + min = 0.0, + update = updateNode) + + iso_value : FloatProperty( + name = "Iso Value", + default = 0.0, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvScalarFieldSocket', "Field") + p = self.inputs.new('SvVerticesSocket', "Vertices") + p.use_prop = True + p.default_property = (0.0, 0.0, 0.0) + p = self.inputs.new('SvVerticesSocket', "Direction") + p.use_prop = True + p.default_property = (0.0, 0.0, 1.0) + self.inputs.new('SvStringsSocket', 'IsoValue').prop_name = 'iso_value' + self.inputs.new('SvStringsSocket', 'MaxDistance').prop_name = 'max_distance' + self.outputs.new('SvVerticesSocket', 'Vertices') + self.outputs.new('SvStringsSocket', 'Distance') + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + field_s = self.inputs['Field'].sv_get() + verts_s = self.inputs['Vertices'].sv_get() + direction_s = self.inputs['Direction'].sv_get() + iso_value_s = self.inputs['IsoValue'].sv_get() + max_distance_s = self.inputs['MaxDistance'].sv_get() + + field_s = ensure_nesting_level(field_s, 2, data_types=(SvScalarField,)) + verts_s = ensure_nesting_level(verts_s, 3) + direction_s = ensure_nesting_level(direction_s, 3) + iso_value_s = ensure_nesting_level(iso_value_s, 2) + max_distance_s = ensure_nesting_level(max_distance_s, 2) + + verts_out = [] + distance_out = [] + + for fields, verts_i, directions, iso_value_i, max_distance_i in zip_long_repeat(field_s, verts_s, direction_s, iso_value_s, max_distance_s): + new_verts = [] + new_t = [] + for field, vert, direction, iso_value, max_distance in zip_long_repeat(fields, verts_i, directions, iso_value_i, max_distance_i): + direction = np.array(direction) + norm = np.linalg.norm(direction) + if norm == 0: + raise ValueError("Direction vector length is zero!") + direction = direction / norm + t, p = solve(field, np.array(vert), direction, max_distance, iso_value) + #t = t * norm + p = tuple(p) + new_verts.append(p) + new_t.append(t) + verts_out.append(new_verts) + distance_out.append(new_t) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Distance'].sv_set(distance_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExImplSurfaceRaycastNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExImplSurfaceRaycastNode) diff --git a/nodes/surface/intersect_curve_surface.py b/nodes/surface/intersect_curve_surface.py index 3b13cc9e24..7719bed5b6 100644 --- a/nodes/surface/intersect_curve_surface.py +++ b/nodes/surface/intersect_curve_surface.py @@ -1,15 +1,9 @@ -import numpy as np - import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -from mathutils import Matrix -from mathutils.bvhtree import BVHTree +from bpy.props import EnumProperty, BoolProperty, IntProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level, repeat_last_for_length -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve from sverchok.utils.surface import SvSurface from sverchok.utils.manifolds import intersect_curve_surface @@ -18,109 +12,111 @@ if scipy is None: add_dummy('SvExCrossCurveSurfaceNode', "Intersect Curve with Surface", 'scipy') -else: - - class SvExCrossCurveSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Intersect Curve Surface - Tooltip: Intersect Curve Surface - """ - bl_idname = 'SvExCrossCurveSurfaceNode' - bl_label = 'Intersect Curve with Surface' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_CROSS_CURVE_SURFACE' - - raycast_samples : IntProperty( - name = "Init Surface Samples", - default = 10, - min = 3, - update = updateNode) - - curve_samples : IntProperty( - name = "Init Curve Samples", - default = 10, - min = 1, - update = updateNode) - - methods = [ - ('hybr', "Hybrd & Hybrj", "Use MINPACK’s hybrd and hybrj routines (modified Powell method)", 0), - ('lm', "Levenberg-Marquardt", "Levenberg-Marquardt algorithm", 1), - ('krylov', "Krylov", "Krylov algorithm", 2), - ('broyden1', "Broyden 1", "Broyden1 algorithm", 3), - ('broyden2', "Broyden 2", "Broyden2 algorithm", 4), - ('anderson', 'Anderson', "Anderson algorithm", 5), - ('df-sane', 'DF-SANE', "DF-SANE method", 6) - ] - - raycast_method : EnumProperty( - name = "Method", - items = methods, - default = 'hybr', - update = updateNode) - - accuracy : IntProperty( - name = "Accuracy", - description = "Accuracy level - number of exact digits after decimal points.", - default = 4, - min = 1, - update = updateNode) - - use_nurbs : BoolProperty( - name = "NURBS", - description = "Use special algorithm for NURBS curves", - default = False, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'raycast_samples') - layout.prop(self, 'curve_samples') - layout.prop(self, 'use_nurbs') - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - layout.prop(self, 'raycast_method') - layout.prop(self, 'accuracy') - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - self.inputs.new('SvSurfaceSocket', "Surface") - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvStringsSocket', "T") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - surfaces_s = self.inputs['Surface'].sv_get() - surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) - curves_s = self.inputs['Curve'].sv_get() - curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) - - tolerance = 10**(-self.accuracy) - - points_out = [] - u_out = [] - for surfaces, curves in zip_long_repeat(surfaces_s, curves_s): - for surface, curve in zip_long_repeat(surfaces, curves): - result = intersect_curve_surface(curve, surface, - init_samples = self.curve_samples, - raycast_samples = self.raycast_samples, - tolerance = tolerance, - raycast_method = self.raycast_method, - support_nurbs = self.use_nurbs - ) - new_points = [p[1] for p in result] - new_u = [p[0] for p in result] - points_out.append(new_points) - u_out.append(new_u) - - self.outputs['Point'].sv_set(points_out) - self.outputs['T'].sv_set(u_out) + + +class SvExCrossCurveSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Intersect Curve Surface + Tooltip: Intersect Curve Surface + """ + bl_idname = 'SvExCrossCurveSurfaceNode' + bl_label = 'Intersect Curve with Surface' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_CROSS_CURVE_SURFACE' + + raycast_samples : IntProperty( + name = "Init Surface Samples", + default = 10, + min = 3, + update = updateNode) + + curve_samples : IntProperty( + name = "Init Curve Samples", + default = 10, + min = 1, + update = updateNode) + + methods = [ + ('hybr', "Hybrd & Hybrj", "Use MINPACK’s hybrd and hybrj routines (modified Powell method)", 0), + ('lm', "Levenberg-Marquardt", "Levenberg-Marquardt algorithm", 1), + ('krylov', "Krylov", "Krylov algorithm", 2), + ('broyden1', "Broyden 1", "Broyden1 algorithm", 3), + ('broyden2', "Broyden 2", "Broyden2 algorithm", 4), + ('anderson', 'Anderson', "Anderson algorithm", 5), + ('df-sane', 'DF-SANE', "DF-SANE method", 6) + ] + + raycast_method : EnumProperty( + name = "Method", + items = methods, + default = 'hybr', + update = updateNode) + + accuracy : IntProperty( + name = "Accuracy", + description = "Accuracy level - number of exact digits after decimal points.", + default = 4, + min = 1, + update = updateNode) + + use_nurbs : BoolProperty( + name = "NURBS", + description = "Use special algorithm for NURBS curves", + default = False, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'raycast_samples') + layout.prop(self, 'curve_samples') + layout.prop(self, 'use_nurbs') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + layout.prop(self, 'raycast_method') + layout.prop(self, 'accuracy') + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + self.inputs.new('SvSurfaceSocket', "Surface") + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvStringsSocket', "T") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + surfaces_s = self.inputs['Surface'].sv_get() + surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) + curves_s = self.inputs['Curve'].sv_get() + curves_s = ensure_nesting_level(curves_s, 2, data_types=(SvCurve,)) + + tolerance = 10**(-self.accuracy) + + points_out = [] + u_out = [] + for surfaces, curves in zip_long_repeat(surfaces_s, curves_s): + for surface, curve in zip_long_repeat(surfaces, curves): + result = intersect_curve_surface(curve, surface, + init_samples = self.curve_samples, + raycast_samples = self.raycast_samples, + tolerance = tolerance, + raycast_method = self.raycast_method, + support_nurbs = self.use_nurbs + ) + new_points = [p[1] for p in result] + new_u = [p[0] for p in result] + points_out.append(new_points) + u_out.append(new_u) + + self.outputs['Point'].sv_set(points_out) + self.outputs['T'].sv_set(u_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExCrossCurveSurfaceNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExCrossCurveSurfaceNode) diff --git a/nodes/surface/min_surface_from_curve.py b/nodes/surface/min_surface_from_curve.py index 85e70074ce..7d6aec7602 100644 --- a/nodes/surface/min_surface_from_curve.py +++ b/nodes/surface/min_surface_from_curve.py @@ -3,13 +3,11 @@ from math import pi import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty +from bpy.props import FloatProperty, EnumProperty, IntProperty from mathutils import Matrix -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.curve import SvCurve, SvCurveOnSurface, SvCircle from sverchok.utils.surface.rbf import SvRbfSurface from sverchok.utils.dummy_nodes import add_dummy @@ -21,120 +19,122 @@ else: from scipy.interpolate import Rbf - class SvExMinSurfaceFromCurveNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Minimal Surface from Curve - Tooltip: Generate Minimal Surface from circle-like curve - """ - bl_idname = 'SvExMinSurfaceFromCurveNode' - bl_label = 'Minimal Surface from Curve' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_EX_MINSURFACE' - - function : EnumProperty( - name = "Function", - items = rbf_functions, - default = 'multiquadric', - update = updateNode) - - epsilon : FloatProperty( - name = "Epsilon", - default = 1.0, - min = 0.0, - update = updateNode) - - smooth : FloatProperty( - name = "Smooth", - default = 0.0, - min = 0.0, - update = updateNode) - - samples_t : IntProperty( - name = "Samples", - default = 50, - min = 3, - update = updateNode) - - def sv_init(self, context): - self.inputs.new('SvCurveSocket', "Curve") - self.inputs.new('SvStringsSocket', "Samples").prop_name = 'samples_t' - self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' - self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' - self.outputs.new('SvSurfaceSocket', "Surface") - self.outputs.new('SvCurveSocket', "TrimCurve") - self.outputs.new('SvCurveSocket', "Curve") - - def draw_buttons(self, context, layout): - layout.prop(self, "function") - - def make_surface(self, curve, epsilon, smooth, samples): - t_min, t_max = curve.get_u_bounds() - curve_ts = np.linspace(t_min, t_max, num=samples) - curve_points = curve.evaluate_array(curve_ts) - dvs = curve_points[1:] - curve_points[:-1] - segment_lengths = np.linalg.norm(dvs, axis=1) + +class SvExMinSurfaceFromCurveNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Minimal Surface from Curve + Tooltip: Generate Minimal Surface from circle-like curve + """ + bl_idname = 'SvExMinSurfaceFromCurveNode' + bl_label = 'Minimal Surface from Curve' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_EX_MINSURFACE' + + function : EnumProperty( + name = "Function", + items = rbf_functions, + default = 'multiquadric', + update = updateNode) + + epsilon : FloatProperty( + name = "Epsilon", + default = 1.0, + min = 0.0, + update = updateNode) + + smooth : FloatProperty( + name = "Smooth", + default = 0.0, + min = 0.0, + update = updateNode) + + samples_t : IntProperty( + name = "Samples", + default = 50, + min = 3, + update = updateNode) + + def sv_init(self, context): + self.inputs.new('SvCurveSocket', "Curve") + self.inputs.new('SvStringsSocket', "Samples").prop_name = 'samples_t' + self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' + self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' + self.outputs.new('SvSurfaceSocket', "Surface") + self.outputs.new('SvCurveSocket', "TrimCurve") + self.outputs.new('SvCurveSocket', "Curve") + + def draw_buttons(self, context, layout): + layout.prop(self, "function") + + def make_surface(self, curve, epsilon, smooth, samples): + t_min, t_max = curve.get_u_bounds() + curve_ts = np.linspace(t_min, t_max, num=samples) + curve_points = curve.evaluate_array(curve_ts) + dvs = curve_points[1:] - curve_points[:-1] + segment_lengths = np.linalg.norm(dvs, axis=1) + last_segment_length = np.linalg.norm(curve_points[0] - curve_points[-1]) + if last_segment_length < 0.001: + # curve is closed: remove the last segment to make it non-closed + segment_lengths = segment_lengths[:-1] + curve_points = curve_points[:-1] last_segment_length = np.linalg.norm(curve_points[0] - curve_points[-1]) - if last_segment_length < 0.001: - # curve is closed: remove the last segment to make it non-closed - segment_lengths = segment_lengths[:-1] - curve_points = curve_points[:-1] - last_segment_length = np.linalg.norm(curve_points[0] - curve_points[-1]) - # T=0 will correspond to the center of gap between first and last point - dt = min(last_segment_length / 2.0, segment_lengths.min()) - cum_segment_lengths = np.insert(np.cumsum(segment_lengths), 0, 0) - total_length = cum_segment_lengths[-1] + last_segment_length - ts = cum_segment_lengths + dt - ts = 2*pi * ts / total_length - - us = np.cos(ts) - vs = np.sin(ts) - - rbf = Rbf(us, vs, curve_points, - function = self.function, - epsilon = epsilon, smooth = smooth, mode = 'N-D') - surface = SvRbfSurface(rbf, 'UV', 'Z', Matrix()) - surface.u_bounds = (-1.0, 1.0) - surface.v_bounds = (-1.0, 1.0) - return surface - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - curve_s = self.inputs['Curve'].sv_get() - epsilon_s = self.inputs['Epsilon'].sv_get() - smooth_s = self.inputs['Smooth'].sv_get() - samples_s = self.inputs['Samples'].sv_get() - - if isinstance(curve_s[0], SvCurve): - curve_s = [curve_s] - epsilon_s = ensure_nesting_level(epsilon_s, 2) - smooth_s = ensure_nesting_level(smooth_s, 2) - samples_s = ensure_nesting_level(samples_s, 2) - - surface_out = [] - circle_out = [] - curve_out = [] - - inputs = zip_long_repeat(curve_s, epsilon_s, smooth_s, samples_s) - for curves, epsilons, smooths, samples_i in inputs: - for curve, epsilon, smooth, samples in zip_long_repeat(curves, epsilons, smooths, samples_i): - new_surface = self.make_surface(curve, epsilon, smooth, samples) - circle = SvCircle(Matrix(), 1.0) - new_curve = SvCurveOnSurface(circle, new_surface, axis=2) - surface_out.append(new_surface) - curve_out.append(new_curve) - circle_out.append(circle) - - self.outputs['Surface'].sv_set(surface_out) - self.outputs['TrimCurve'].sv_set(circle_out) - self.outputs['Curve'].sv_set(curve_out) + # T=0 will correspond to the center of gap between first and last point + dt = min(last_segment_length / 2.0, segment_lengths.min()) + cum_segment_lengths = np.insert(np.cumsum(segment_lengths), 0, 0) + total_length = cum_segment_lengths[-1] + last_segment_length + ts = cum_segment_lengths + dt + ts = 2*pi * ts / total_length + + us = np.cos(ts) + vs = np.sin(ts) + + rbf = Rbf(us, vs, curve_points, + function = self.function, + epsilon = epsilon, smooth = smooth, mode = 'N-D') + surface = SvRbfSurface(rbf, 'UV', 'Z', Matrix()) + surface.u_bounds = (-1.0, 1.0) + surface.v_bounds = (-1.0, 1.0) + return surface + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + curve_s = self.inputs['Curve'].sv_get() + epsilon_s = self.inputs['Epsilon'].sv_get() + smooth_s = self.inputs['Smooth'].sv_get() + samples_s = self.inputs['Samples'].sv_get() + + if isinstance(curve_s[0], SvCurve): + curve_s = [curve_s] + epsilon_s = ensure_nesting_level(epsilon_s, 2) + smooth_s = ensure_nesting_level(smooth_s, 2) + samples_s = ensure_nesting_level(samples_s, 2) + + surface_out = [] + circle_out = [] + curve_out = [] + + inputs = zip_long_repeat(curve_s, epsilon_s, smooth_s, samples_s) + for curves, epsilons, smooths, samples_i in inputs: + for curve, epsilon, smooth, samples in zip_long_repeat(curves, epsilons, smooths, samples_i): + new_surface = self.make_surface(curve, epsilon, smooth, samples) + circle = SvCircle(Matrix(), 1.0) + new_curve = SvCurveOnSurface(circle, new_surface, axis=2) + surface_out.append(new_surface) + curve_out.append(new_curve) + circle_out.append(circle) + + self.outputs['Surface'].sv_set(surface_out) + self.outputs['TrimCurve'].sv_set(circle_out) + self.outputs['Curve'].sv_set(curve_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExMinSurfaceFromCurveNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExMinSurfaceFromCurveNode) - diff --git a/nodes/surface/minimal_surface.py b/nodes/surface/minimal_surface.py index 3570cc0c62..a6524abac6 100644 --- a/nodes/surface/minimal_surface.py +++ b/nodes/surface/minimal_surface.py @@ -17,224 +17,226 @@ else: from scipy.interpolate import Rbf - class SvExMinimalSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Minimal Surface - Tooltip: Minimal Surface - """ - bl_idname = 'SvExMinimalSurfaceNode' - bl_label = 'Minimal Surface' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_EX_MINSURFACE' - - def update_sockets(self, context): - self.inputs['Matrix'].hide_safe = self.coord_mode == 'UV' - self.inputs['SrcU'].hide_safe = self.coord_mode != 'UV' or not self.explicit_src_uv - self.inputs['SrcV'].hide_safe = self.coord_mode != 'UV' or not self.explicit_src_uv - updateNode(self, context) - - coord_modes = [ - ('XY', "X Y -> Z", "XY -> Z function", 0), - ('UV', "U V -> X Y Z", "UV -> XYZ function", 1) - ] - - coord_mode : EnumProperty( - name = "Coordinates", - items = coord_modes, - default = 'XY', + +class SvExMinimalSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Minimal Surface + Tooltip: Minimal Surface + """ + bl_idname = 'SvExMinimalSurfaceNode' + bl_label = 'Minimal Surface' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_EX_MINSURFACE' + + def update_sockets(self, context): + self.inputs['Matrix'].hide_safe = self.coord_mode == 'UV' + self.inputs['SrcU'].hide_safe = self.coord_mode != 'UV' or not self.explicit_src_uv + self.inputs['SrcV'].hide_safe = self.coord_mode != 'UV' or not self.explicit_src_uv + updateNode(self, context) + + coord_modes = [ + ('XY', "X Y -> Z", "XY -> Z function", 0), + ('UV', "U V -> X Y Z", "UV -> XYZ function", 1) + ] + + coord_mode : EnumProperty( + name = "Coordinates", + items = coord_modes, + default = 'XY', + update = update_sockets) + + functions = [ + ('multiquadric', "Multi Quadric", "Multi Quadric", 0), + ('inverse', "Inverse", "Inverse", 1), + ('gaussian', "Gaussian", "Gaussian", 2), + ('cubic', "Cubic", "Cubic", 3), + ('quintic', "Quintic", "Qunitic", 4), + ('thin_plate', "Thin Plate", "Thin Plate", 5) + ] + + function : EnumProperty( + name = "Function", + items = functions, + default = 'multiquadric', + update = updateNode) + + axes = [ + ('X', "X", "X axis", 0), + ('Y', "Y", "Y axis", 1), + ('Z', "Z", "Z axis", 2) + ] + + orientation : EnumProperty( + name = "Orientation", + items = axes, + default = 'Z', + update = updateNode) + + epsilon : FloatProperty( + name = "Epsilon", + default = 1.0, + min = 0.0, + update = updateNode) + + smooth : FloatProperty( + name = "Smooth", + default = 0.0, + min = 0.0, + update = updateNode) + + explicit_src_uv : BoolProperty( + name = "Explicit source UV", + default = True, update = update_sockets) - functions = [ - ('multiquadric', "Multi Quadric", "Multi Quadric", 0), - ('inverse', "Inverse", "Inverse", 1), - ('gaussian', "Gaussian", "Gaussian", 2), - ('cubic', "Cubic", "Cubic", 3), - ('quintic', "Quintic", "Qunitic", 4), - ('thin_plate', "Thin Plate", "Thin Plate", 5) - ] - - function : EnumProperty( - name = "Function", - items = functions, - default = 'multiquadric', - update = updateNode) - - axes = [ - ('X', "X", "X axis", 0), - ('Y', "Y", "Y axis", 1), - ('Z', "Z", "Z axis", 2) - ] - - orientation : EnumProperty( - name = "Orientation", - items = axes, - default = 'Z', - update = updateNode) - - epsilon : FloatProperty( - name = "Epsilon", - default = 1.0, - min = 0.0, - update = updateNode) - - smooth : FloatProperty( - name = "Smooth", - default = 0.0, - min = 0.0, - update = updateNode) - - explicit_src_uv : BoolProperty( - name = "Explicit source UV", - default = True, - update = update_sockets) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") # 0 - self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' #2 - self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' #3 - self.inputs.new('SvStringsSocket', "SrcU") #4 - self.inputs.new('SvStringsSocket', "SrcV") #5 - self.inputs.new('SvMatrixSocket', "Matrix") #8 - self.outputs.new('SvSurfaceSocket', "Surface") - self.update_sockets(context) - - def draw_buttons(self, context, layout): - layout.label(text="Surface type:") - layout.prop(self, "coord_mode", expand=True) - if self.coord_mode == 'XY': - layout.prop(self, "orientation", expand=True) - if self.coord_mode == 'UV': - layout.prop(self, "explicit_src_uv") - layout.prop(self, "function") + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") # 0 + self.inputs.new('SvStringsSocket', "Epsilon").prop_name = 'epsilon' #2 + self.inputs.new('SvStringsSocket', "Smooth").prop_name = 'smooth' #3 + self.inputs.new('SvStringsSocket', "SrcU") #4 + self.inputs.new('SvStringsSocket', "SrcV") #5 + self.inputs.new('SvMatrixSocket', "Matrix") #8 + self.outputs.new('SvSurfaceSocket', "Surface") + self.update_sockets(context) + + def draw_buttons(self, context, layout): + layout.label(text="Surface type:") + layout.prop(self, "coord_mode", expand=True) + if self.coord_mode == 'XY': + layout.prop(self, "orientation", expand=True) + if self.coord_mode == 'UV': + layout.prop(self, "explicit_src_uv") + layout.prop(self, "function") + + def make_uv(self, vertices): + + def distance(v1, v2): + v1 = np.array(v1) + v2 = np.array(v2) + return np.linalg.norm(v1-v2) + + u = 0 + v = 0 + us, vs = [], [] + prev_row = None + rev_vs = None + for row in vertices: + u = 0 + row_vs = [] + prev_vertex = None + for j, vertex in enumerate(row): + if prev_row is not None: + dv = distance(prev_row[j], vertex) + v = prev_vs[j] + dv + if prev_vertex is not None: + du = distance(prev_vertex, vertex) + u += du + us.append(u) + vs.append(v) + row_vs.append(v) + prev_vertex = vertex + prev_row = row + prev_vs = row_vs + + return np.array(us), np.array(vs) + + def process(self): + + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + if self.coord_mode == 'UV': + vertices_s = ensure_nesting_level(vertices_s, 4) + epsilon_s = self.inputs['Epsilon'].sv_get() + smooth_s = self.inputs['Smooth'].sv_get() + src_us_s = self.inputs['SrcU'].sv_get(default = [[]]) + src_vs_s = self.inputs['SrcV'].sv_get(default = [[]]) + matrices_s = self.inputs['Matrix'].sv_get(default = [[Matrix()]]) + + verts_out = [] + edges_out = [] + faces_out = [] + surfaces_out = [] + inputs = zip_long_repeat(vertices_s, src_us_s, src_vs_s, matrices_s, epsilon_s, smooth_s) + for vertices, src_us, src_vs, matrix, epsilon, smooth in inputs: + if isinstance(epsilon, (list, int)): + epsilon = epsilon[0] + if isinstance(smooth, (list, int)): + smooth = smooth[0] + if isinstance(matrix, list): + matrix = matrix[0] + has_matrix = self.coord_mode == 'XY' and matrix is not None and matrix != Matrix() + + if self.coord_mode == 'UV' and self.explicit_src_uv: + if get_data_nesting_level(src_us) == 3: + src_us = sum(src_us, []) + if get_data_nesting_level(src_vs) == 3: + src_vs = sum(src_vs, []) - def make_uv(self, vertices): + if self.coord_mode == 'XY': + XYZ = np.array(vertices) + else: # UV + all_vertices = sum(vertices, []) + XYZ = np.array(all_vertices) + if has_matrix: + np_matrix = np.array(matrix.to_3x3()) + inv_matrix = np.linalg.inv(np_matrix) + translation = np.array(matrix.translation) + XYZ = np.matmul(inv_matrix, XYZ.T).T + translation - def distance(v1, v2): - v1 = np.array(v1) - v2 = np.array(v2) - return np.linalg.norm(v1-v2) + if self.coord_mode == 'XY': + if self.orientation == 'X': + reorder = np.array([1, 2, 0]) + XYZ = XYZ[:, reorder] + elif self.orientation == 'Y': + reorder = np.array([2, 0, 1]) + XYZ = XYZ[:, reorder] + else: # Z + pass + + #print(XYZ[:,0]) + #print(XYZ[:,1]) + #print(XYZ[:,2]) + rbf = Rbf(XYZ[:,0],XYZ[:,1],XYZ[:,2], + function=self.function, + smooth=smooth, + epsilon=epsilon, mode='1-D') + + x_min = XYZ[:,0].min() + x_max = XYZ[:,0].max() + y_min = XYZ[:,1].min() + y_max = XYZ[:,1].max() + u_bounds = (x_min, x_max) + v_bounds = (y_min, y_max) + + else: # UV + if not self.explicit_src_uv: + src_us, src_vs = self.make_uv(vertices) + else: + src_us = np.array(src_us) + src_vs = np.array(src_vs) + + #self.info("Us: %s, Vs: %s", len(src_us), len(src_vs)) + rbf = Rbf(src_us, src_vs, all_vertices, + function = self.function, + smooth = smooth, + epsilon = epsilon, mode='N-D') + + u_min = src_us.min() + v_min = src_vs.min() + u_max = src_us.max() + v_max = src_vs.max() + u_bounds = (u_min, u_max) + v_bounds = (v_min, v_max) + + surface = SvRbfSurface(rbf, self.coord_mode, self.orientation, matrix) + surface.u_bounds = u_bounds + surface.v_bounds = v_bounds + surfaces_out.append(surface) + + self.outputs['Surface'].sv_set(surfaces_out) - u = 0 - v = 0 - us, vs = [], [] - prev_row = None - rev_vs = None - for row in vertices: - u = 0 - row_vs = [] - prev_vertex = None - for j, vertex in enumerate(row): - if prev_row is not None: - dv = distance(prev_row[j], vertex) - v = prev_vs[j] + dv - if prev_vertex is not None: - du = distance(prev_vertex, vertex) - u += du - us.append(u) - vs.append(v) - row_vs.append(v) - prev_vertex = vertex - prev_row = row - prev_vs = row_vs - - return np.array(us), np.array(vs) - - def process(self): - - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - if self.coord_mode == 'UV': - vertices_s = ensure_nesting_level(vertices_s, 4) - epsilon_s = self.inputs['Epsilon'].sv_get() - smooth_s = self.inputs['Smooth'].sv_get() - src_us_s = self.inputs['SrcU'].sv_get(default = [[]]) - src_vs_s = self.inputs['SrcV'].sv_get(default = [[]]) - matrices_s = self.inputs['Matrix'].sv_get(default = [[Matrix()]]) - - verts_out = [] - edges_out = [] - faces_out = [] - surfaces_out = [] - inputs = zip_long_repeat(vertices_s, src_us_s, src_vs_s, matrices_s, epsilon_s, smooth_s) - for vertices, src_us, src_vs, matrix, epsilon, smooth in inputs: - if isinstance(epsilon, (list, int)): - epsilon = epsilon[0] - if isinstance(smooth, (list, int)): - smooth = smooth[0] - if isinstance(matrix, list): - matrix = matrix[0] - has_matrix = self.coord_mode == 'XY' and matrix is not None and matrix != Matrix() - - if self.coord_mode == 'UV' and self.explicit_src_uv: - if get_data_nesting_level(src_us) == 3: - src_us = sum(src_us, []) - if get_data_nesting_level(src_vs) == 3: - src_vs = sum(src_vs, []) - - if self.coord_mode == 'XY': - XYZ = np.array(vertices) - else: # UV - all_vertices = sum(vertices, []) - XYZ = np.array(all_vertices) - if has_matrix: - np_matrix = np.array(matrix.to_3x3()) - inv_matrix = np.linalg.inv(np_matrix) - translation = np.array(matrix.translation) - XYZ = np.matmul(inv_matrix, XYZ.T).T + translation - - if self.coord_mode == 'XY': - if self.orientation == 'X': - reorder = np.array([1, 2, 0]) - XYZ = XYZ[:, reorder] - elif self.orientation == 'Y': - reorder = np.array([2, 0, 1]) - XYZ = XYZ[:, reorder] - else: # Z - pass - - #print(XYZ[:,0]) - #print(XYZ[:,1]) - #print(XYZ[:,2]) - rbf = Rbf(XYZ[:,0],XYZ[:,1],XYZ[:,2], - function=self.function, - smooth=smooth, - epsilon=epsilon, mode='1-D') - - x_min = XYZ[:,0].min() - x_max = XYZ[:,0].max() - y_min = XYZ[:,1].min() - y_max = XYZ[:,1].max() - u_bounds = (x_min, x_max) - v_bounds = (y_min, y_max) - - else: # UV - if not self.explicit_src_uv: - src_us, src_vs = self.make_uv(vertices) - else: - src_us = np.array(src_us) - src_vs = np.array(src_vs) - - #self.info("Us: %s, Vs: %s", len(src_us), len(src_vs)) - rbf = Rbf(src_us, src_vs, all_vertices, - function = self.function, - smooth = smooth, - epsilon = epsilon, mode='N-D') - - u_min = src_us.min() - v_min = src_vs.min() - u_max = src_us.max() - v_max = src_vs.max() - u_bounds = (u_min, u_max) - v_bounds = (v_min, v_max) - - surface = SvRbfSurface(rbf, self.coord_mode, self.orientation, matrix) - surface.u_bounds = u_bounds - surface.v_bounds = v_bounds - surfaces_out.append(surface) - - self.outputs['Surface'].sv_set(surfaces_out) def register(): if scipy is not None: @@ -243,4 +245,3 @@ def register(): def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExMinimalSurfaceNode) - diff --git a/nodes/surface/nearest_point.py b/nodes/surface/nearest_point.py index 50dc1e37ee..5326e12aee 100644 --- a/nodes/surface/nearest_point.py +++ b/nodes/surface/nearest_point.py @@ -1,13 +1,8 @@ -import numpy as np - import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -from mathutils import Matrix -from mathutils.kdtree import KDTree +from bpy.props import EnumProperty, BoolProperty, IntProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.surface import SvSurface from sverchok.utils.manifolds import nearest_point_on_surface from sverchok.utils.dummy_nodes import add_dummy @@ -15,113 +10,114 @@ if scipy is None: add_dummy('SvExNearestPointOnSurfaceNode', "Nearest Point on Surface", 'scipy') -else: - - class SvExNearestPointOnSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Nearest Point on Surface - Tooltip: Find the point on the surface which is the nearest to the given point - """ - bl_idname = 'SvExNearestPointOnSurfaceNode' - bl_label = 'Nearest Point on Surface' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_NEAREST_SURFACE' - - samples : IntProperty( - name = "Init Resolution", - default = 10, - min = 3, - update = updateNode) - - precise : BoolProperty( - name = "Precise", - default = True, - update = updateNode) - - methods = [ - ('L-BFGS-B', "L-BFGS-B", "L-BFGS-B algorithm", 0), - ('CG', "Conjugate Gradient", "Conjugate gradient algorithm", 1), - ('TNC', "Truncated Newton", "Truncated Newton algorithm", 2), - ('SLSQP', "SLSQP", "Sequential Least SQuares Programming algorithm", 3) - ] - - method : EnumProperty( - name = "Method", - items = methods, - default = 'L-BFGS-B', - update = updateNode) - - sequential : BoolProperty( - name = "Sequential", - description = "If enabled, the node will use nearest point for one source point as initial guess for finding the nearest point for the next source point", - default = False, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'samples') - layout.prop(self, 'precise') - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - if self.precise: - layout.prop(self, 'method') - layout.prop(self, 'sequential') - - def sv_init(self, context): - self.inputs.new('SvSurfaceSocket', "Surface") - p = self.inputs.new('SvVerticesSocket', "Point") - p.use_prop = True - p.default_property = (0.0, 0.0, 0.0) - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvVerticesSocket', "UVPoint") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - surfaces_s = self.inputs['Surface'].sv_get() - surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) - src_point_s = self.inputs['Point'].sv_get() - src_point_s = ensure_nesting_level(src_point_s, 4) - - need_points = self.outputs['Point'].is_linked - - points_out = [] - points_uv_out = [] - for surfaces, src_points_i in zip_long_repeat(surfaces_s, src_point_s): - for surface, src_points in zip_long_repeat(surfaces, src_points_i): - - new_uv = [] - new_u = [] - new_v = [] - new_points = [] - - result = nearest_point_on_surface(src_points, surface, - init_samples = self.samples, - precise = self.precise, - method = self.method, - sequential = self.sequential, - output_points = need_points - ) - - if need_points: - new_u, new_v, new_points = result - else: - new_u, new_v = result - - new_uv = [(u,v,0) for u, v in zip(new_u, new_v)] - - points_out.append(new_points) - points_uv_out.append(new_uv) - - self.outputs['Point'].sv_set(points_out) - self.outputs['UVPoint'].sv_set(points_uv_out) + + +class SvExNearestPointOnSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Nearest Point on Surface + Tooltip: Find the point on the surface which is the nearest to the given point + """ + bl_idname = 'SvExNearestPointOnSurfaceNode' + bl_label = 'Nearest Point on Surface' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_NEAREST_SURFACE' + + samples : IntProperty( + name = "Init Resolution", + default = 10, + min = 3, + update = updateNode) + + precise : BoolProperty( + name = "Precise", + default = True, + update = updateNode) + + methods = [ + ('L-BFGS-B', "L-BFGS-B", "L-BFGS-B algorithm", 0), + ('CG', "Conjugate Gradient", "Conjugate gradient algorithm", 1), + ('TNC', "Truncated Newton", "Truncated Newton algorithm", 2), + ('SLSQP', "SLSQP", "Sequential Least SQuares Programming algorithm", 3) + ] + + method : EnumProperty( + name = "Method", + items = methods, + default = 'L-BFGS-B', + update = updateNode) + + sequential : BoolProperty( + name = "Sequential", + description = "If enabled, the node will use nearest point for one source point as initial guess for finding the nearest point for the next source point", + default = False, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'samples') + layout.prop(self, 'precise') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + if self.precise: + layout.prop(self, 'method') + layout.prop(self, 'sequential') + + def sv_init(self, context): + self.inputs.new('SvSurfaceSocket', "Surface") + p = self.inputs.new('SvVerticesSocket', "Point") + p.use_prop = True + p.default_property = (0.0, 0.0, 0.0) + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvVerticesSocket', "UVPoint") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + surfaces_s = self.inputs['Surface'].sv_get() + surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) + src_point_s = self.inputs['Point'].sv_get() + src_point_s = ensure_nesting_level(src_point_s, 4) + + need_points = self.outputs['Point'].is_linked + + points_out = [] + points_uv_out = [] + for surfaces, src_points_i in zip_long_repeat(surfaces_s, src_point_s): + for surface, src_points in zip_long_repeat(surfaces, src_points_i): + + new_uv = [] + new_u = [] + new_v = [] + new_points = [] + + result = nearest_point_on_surface(src_points, surface, + init_samples = self.samples, + precise = self.precise, + method = self.method, + sequential = self.sequential, + output_points = need_points + ) + + if need_points: + new_u, new_v, new_points = result + else: + new_u, new_v = result + + new_uv = [(u,v,0) for u, v in zip(new_u, new_v)] + + points_out.append(new_points) + points_uv_out.append(new_uv) + + self.outputs['Point'].sv_set(points_out) + self.outputs['UVPoint'].sv_set(points_uv_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExNearestPointOnSurfaceNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExNearestPointOnSurfaceNode) - diff --git a/nodes/surface/ortho_project.py b/nodes/surface/ortho_project.py index 7217c0856e..a9b2319605 100644 --- a/nodes/surface/ortho_project.py +++ b/nodes/surface/ortho_project.py @@ -2,12 +2,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty +from bpy.props import IntProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level from sverchok.utils.surface import SvSurface from sverchok.utils.dummy_nodes import add_dummy from sverchok.dependencies import scipy @@ -15,66 +13,67 @@ if scipy is None: add_dummy('SvExOrthoProjectSurfaceNode', "Ortho Project on Surface", 'scipy') -else: - - class SvExOrthoProjectSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Ortho Project Surface - Tooltip: Find the orthogonal projection of the point onto the surface - """ - bl_idname = 'SvExOrthoProjectSurfaceNode' - bl_label = 'Ortho Project on Surface' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_ORTHO_SURFACE' - - samples : IntProperty( - name = "Init Resolution", - default = 5, - min = 3, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, 'samples') - - def sv_init(self, context): - self.inputs.new('SvSurfaceSocket', "Surface") - p = self.inputs.new('SvVerticesSocket', "Point") - p.use_prop = True - p.default_property = (0.0, 0.0, 0.0) - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvVerticesSocket', "UVPoint") - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - surfaces_s = self.inputs['Surface'].sv_get() - surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) - src_point_s = self.inputs['Point'].sv_get() - src_point_s = ensure_nesting_level(src_point_s, 4) - - points_out = [] - uv_out = [] - for surfaces, src_points_i in zip_long_repeat(surfaces_s, src_point_s): - for surface, src_points in zip_long_repeat(surfaces, src_points_i): - new_points = [] - new_uv = [] - for src_point in src_points: - src_point = np.array(src_point) - u, v, point = ortho_project_surface(src_point, surface, init_samples=self.samples) - new_uv.append((u, v, 0)) - new_points.append(point) - points_out.append(new_points) - uv_out.append(new_uv) - - self.outputs['Point'].sv_set(points_out) - self.outputs['UVPoint'].sv_set(uv_out) + + +class SvExOrthoProjectSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Ortho Project Surface + Tooltip: Find the orthogonal projection of the point onto the surface + """ + bl_idname = 'SvExOrthoProjectSurfaceNode' + bl_label = 'Ortho Project on Surface' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_ORTHO_SURFACE' + + samples : IntProperty( + name = "Init Resolution", + default = 5, + min = 3, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, 'samples') + + def sv_init(self, context): + self.inputs.new('SvSurfaceSocket', "Surface") + p = self.inputs.new('SvVerticesSocket', "Point") + p.use_prop = True + p.default_property = (0.0, 0.0, 0.0) + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvVerticesSocket', "UVPoint") + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + surfaces_s = self.inputs['Surface'].sv_get() + surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) + src_point_s = self.inputs['Point'].sv_get() + src_point_s = ensure_nesting_level(src_point_s, 4) + + points_out = [] + uv_out = [] + for surfaces, src_points_i in zip_long_repeat(surfaces_s, src_point_s): + for surface, src_points in zip_long_repeat(surfaces, src_points_i): + new_points = [] + new_uv = [] + for src_point in src_points: + src_point = np.array(src_point) + u, v, point = ortho_project_surface(src_point, surface, init_samples=self.samples) + new_uv.append((u, v, 0)) + new_points.append(point) + points_out.append(new_points) + uv_out.append(new_uv) + + self.outputs['Point'].sv_set(points_out) + self.outputs['UVPoint'].sv_set(uv_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExOrthoProjectSurfaceNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExOrthoProjectSurfaceNode) - diff --git a/nodes/surface/raycast.py b/nodes/surface/raycast.py index 35085182bd..c84924f4de 100644 --- a/nodes/surface/raycast.py +++ b/nodes/surface/raycast.py @@ -13,142 +13,143 @@ if scipy is None: add_dummy('SvExRaycastSurfaceNode', "Raycast on Surface", 'scipy') -else: - - class SvExRaycastSurfaceNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Raycast on Surface - Tooltip: Raycast on Surface - """ - bl_idname = 'SvExRaycastSurfaceNode' - bl_label = 'Raycast on Surface' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_SURFACE_RAYCAST' - - samples : IntProperty( - name = "Init Resolution", - default = 10, - min = 3, - update = updateNode) - - precise : BoolProperty( - name = "Precise", - default = True, - update = updateNode) - - def update_sockets(self, context): - self.inputs['Source'].hide_safe = self.project_mode != 'CONIC' - self.inputs['Direction'].hide_safe = self.project_mode != 'PARALLEL' - updateNode(self, context) - - modes = [ - ('PARALLEL', "Along Direction", "Project points along specified direction", 0), - ('CONIC', "From Source", "Project points along the direction from the source point", 1) - ] - - project_mode : EnumProperty( - name = "Project", - items = modes, - default = 'PARALLEL', - update = update_sockets) - - methods = [ - ('hybr', "Hybrd & Hybrj", "Use MINPACK’s hybrd and hybrj routines (modified Powell method)", 0), - ('lm', "Levenberg-Marquardt", "Levenberg-Marquardt algorithm", 1), - ('krylov', "Krylov", "Krylov algorithm", 2), - ('broyden1', "Broyden 1", "Broyden1 algorithm", 3), - ('broyden2', "Broyden 2", "Broyden2 algorithm", 4), - ('anderson', 'Anderson', "Anderson algorithm", 5), - ('df-sane', 'DF-SANE', "DF-SANE method", 6) - ] - - method : EnumProperty( - name = "Method", - items = methods, - default = 'hybr', - update = updateNode) - - def draw_buttons(self, context, layout): - layout.label(text="Project:") - layout.prop(self, 'project_mode', text='') - layout.prop(self, 'precise', toggle=True) - if not self.precise: - layout.prop(self, 'samples') - - def draw_buttons_ext(self, context, layout): - self.draw_buttons(context, layout) - if self.precise: - layout.prop(self, 'samples') - layout.prop(self, 'method') - - def sv_init(self, context): - self.inputs.new('SvSurfaceSocket', "Surface") - p = self.inputs.new('SvVerticesSocket', "Source") - p.use_prop = True - p.default_property = (0.0, 0.0, 1.0) - p = self.inputs.new('SvVerticesSocket', "Point") - p.use_prop = True - p.default_property = (0.0, 0.0, 1.0) - p = self.inputs.new('SvVerticesSocket', "Direction") - p.use_prop = True - p.default_property = (0.0, 0.0, -1.0) - self.outputs.new('SvVerticesSocket', "Point") - self.outputs.new('SvVerticesSocket', "UVPoint") - self.update_sockets(context) - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - surfaces_s = self.inputs['Surface'].sv_get() - surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) - src_point_s = self.inputs['Source'].sv_get() - src_point_s = ensure_nesting_level(src_point_s, 4) - points_s = self.inputs['Point'].sv_get() - points_s = ensure_nesting_level(points_s, 4) - direction_s = self.inputs['Direction'].sv_get() - direction_s = ensure_nesting_level(direction_s, 4) - - points_out = [] - points_uv_out = [] - for surfaces, src_points_i, points_i, directions_i in zip_long_repeat(surfaces_s, src_point_s, points_s, direction_s): - for surface, src_points, points, directions in zip_long_repeat(surfaces, src_points_i, points_i, directions_i): - u_min = surface.get_u_min() - u_max = surface.get_u_max() - v_min = surface.get_v_min() - v_max = surface.get_v_max() - - new_uv = [] - new_u = [] - new_v = [] - new_points = [] - - if self.project_mode == 'PARALLEL': - directions = repeat_last_for_length(directions, len(points)) - else: # CONIC - src_points = repeat_last_for_length(src_points, len(points)) - directions = (np.array(points) - np.array(src_points)).tolist() - - result = raycast_surface(surface, points, directions, - samples = self.samples, - precise = self.precise, - calc_points = self.outputs['Point'].is_linked, - method = self.method) - - new_uv = result.uvs - new_points = result.points - - points_out.append(new_points) - points_uv_out.append(new_uv) - - self.outputs['Point'].sv_set(points_out) - self.outputs['UVPoint'].sv_set(points_uv_out) + + +class SvExRaycastSurfaceNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Raycast on Surface + Tooltip: Raycast on Surface + """ + bl_idname = 'SvExRaycastSurfaceNode' + bl_label = 'Raycast on Surface' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_SURFACE_RAYCAST' + + samples : IntProperty( + name = "Init Resolution", + default = 10, + min = 3, + update = updateNode) + + precise : BoolProperty( + name = "Precise", + default = True, + update = updateNode) + + def update_sockets(self, context): + self.inputs['Source'].hide_safe = self.project_mode != 'CONIC' + self.inputs['Direction'].hide_safe = self.project_mode != 'PARALLEL' + updateNode(self, context) + + modes = [ + ('PARALLEL', "Along Direction", "Project points along specified direction", 0), + ('CONIC', "From Source", "Project points along the direction from the source point", 1) + ] + + project_mode : EnumProperty( + name = "Project", + items = modes, + default = 'PARALLEL', + update = update_sockets) + + methods = [ + ('hybr', "Hybrd & Hybrj", "Use MINPACK’s hybrd and hybrj routines (modified Powell method)", 0), + ('lm', "Levenberg-Marquardt", "Levenberg-Marquardt algorithm", 1), + ('krylov', "Krylov", "Krylov algorithm", 2), + ('broyden1', "Broyden 1", "Broyden1 algorithm", 3), + ('broyden2', "Broyden 2", "Broyden2 algorithm", 4), + ('anderson', 'Anderson', "Anderson algorithm", 5), + ('df-sane', 'DF-SANE', "DF-SANE method", 6) + ] + + method : EnumProperty( + name = "Method", + items = methods, + default = 'hybr', + update = updateNode) + + def draw_buttons(self, context, layout): + layout.label(text="Project:") + layout.prop(self, 'project_mode', text='') + layout.prop(self, 'precise', toggle=True) + if not self.precise: + layout.prop(self, 'samples') + + def draw_buttons_ext(self, context, layout): + self.draw_buttons(context, layout) + if self.precise: + layout.prop(self, 'samples') + layout.prop(self, 'method') + + def sv_init(self, context): + self.inputs.new('SvSurfaceSocket', "Surface") + p = self.inputs.new('SvVerticesSocket', "Source") + p.use_prop = True + p.default_property = (0.0, 0.0, 1.0) + p = self.inputs.new('SvVerticesSocket', "Point") + p.use_prop = True + p.default_property = (0.0, 0.0, 1.0) + p = self.inputs.new('SvVerticesSocket', "Direction") + p.use_prop = True + p.default_property = (0.0, 0.0, -1.0) + self.outputs.new('SvVerticesSocket', "Point") + self.outputs.new('SvVerticesSocket', "UVPoint") + self.update_sockets(context) + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + surfaces_s = self.inputs['Surface'].sv_get() + surfaces_s = ensure_nesting_level(surfaces_s, 2, data_types=(SvSurface,)) + src_point_s = self.inputs['Source'].sv_get() + src_point_s = ensure_nesting_level(src_point_s, 4) + points_s = self.inputs['Point'].sv_get() + points_s = ensure_nesting_level(points_s, 4) + direction_s = self.inputs['Direction'].sv_get() + direction_s = ensure_nesting_level(direction_s, 4) + + points_out = [] + points_uv_out = [] + for surfaces, src_points_i, points_i, directions_i in zip_long_repeat(surfaces_s, src_point_s, points_s, direction_s): + for surface, src_points, points, directions in zip_long_repeat(surfaces, src_points_i, points_i, directions_i): + u_min = surface.get_u_min() + u_max = surface.get_u_max() + v_min = surface.get_v_min() + v_max = surface.get_v_max() + + new_uv = [] + new_u = [] + new_v = [] + new_points = [] + + if self.project_mode == 'PARALLEL': + directions = repeat_last_for_length(directions, len(points)) + else: # CONIC + src_points = repeat_last_for_length(src_points, len(points)) + directions = (np.array(points) - np.array(src_points)).tolist() + + result = raycast_surface(surface, points, directions, + samples = self.samples, + precise = self.precise, + calc_points = self.outputs['Point'].is_linked, + method = self.method) + + new_uv = result.uvs + new_points = result.points + + points_out.append(new_points) + points_uv_out.append(new_uv) + + self.outputs['Point'].sv_set(points_out) + self.outputs['UVPoint'].sv_set(points_uv_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExRaycastSurfaceNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExRaycastSurfaceNode) - diff --git a/old_nodes/delaunay3d.py b/old_nodes/delaunay3d.py index 0adb6d4c4e..6e745cb0f3 100644 --- a/old_nodes/delaunay3d.py +++ b/old_nodes/delaunay3d.py @@ -2,16 +2,10 @@ import numpy as np import bpy -from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty -import bmesh -from mathutils import Matrix +from bpy.props import FloatProperty, BoolProperty -import sverchok from sverchok.node_tree import SverchCustomTreeNode -from sverchok.data_structure import updateNode, zip_long_repeat, ensure_nesting_level, get_data_nesting_level -from sverchok.utils.sv_mesh_utils import polygons_to_edges, mesh_join -from sverchok.utils.sv_bmesh_utils import pydata_from_bmesh, bmesh_from_pydata -from sverchok.utils.logging import info, exception +from sverchok.data_structure import updateNode, zip_long_repeat from sverchok.utils.dummy_nodes import add_dummy from sverchok.dependencies import scipy @@ -20,113 +14,115 @@ else: from scipy.spatial import Delaunay - class SvExDelaunay3DNode(SverchCustomTreeNode, bpy.types.Node): - """ - Triggers: Delaunay 3D - Tooltip: Generate 3D Delaunay Triangulation - """ - bl_idname = 'SvExDelaunay3DNode' - bl_label = 'Delaunay 3D' - bl_icon = 'OUTLINER_OB_EMPTY' - sv_icon = 'SV_VORONOI' - - join : BoolProperty( - name = "Join", - default = False, - update = updateNode) - - threshold : FloatProperty( - name = "Threshold", - default = 1e-4, - precision = 4, - update = updateNode) - - def draw_buttons(self, context, layout): - layout.prop(self, "join", toggle=True) - - def sv_init(self, context): - self.inputs.new('SvVerticesSocket', "Vertices") - self.inputs.new('SvStringsSocket', "Threshold").prop_name = 'threshold' - self.outputs.new('SvVerticesSocket', "Vertices") - self.outputs.new('SvStringsSocket', "Edges") - self.outputs.new('SvStringsSocket', "Faces") - - def make_edges(self, idxs): - return [(i, j) for i in idxs for j in idxs if i < j] - - def make_faces(self, idxs): - return [(i, j, k) for i in idxs for j in idxs for k in idxs if i < j and j < k] - - def get_verts(self, verts, idxs): - return [verts[i] for i in idxs] - - def is_planar(self, verts, idxs, threshold): - if threshold == 0: - return True - a, b, c, d = [verts[i] for i in idxs] - a, b, c, d = np.array(a), np.array(b), np.array(c), np.array(d) - v1 = b - a - v2 = c - a - v3 = d - a - v1 = v1 / np.linalg.norm(v1) - v2 = v2 / np.linalg.norm(v2) - v3 = v3 / np.linalg.norm(v3) - volume = np.cross(v1, v2).dot(v3) / 6 - return abs(volume) < threshold - - def process(self): - if not any(socket.is_linked for socket in self.outputs): - return - - vertices_s = self.inputs['Vertices'].sv_get() - threshold_s = self.inputs['Threshold'].sv_get() - - verts_out = [] - edges_out = [] - faces_out = [] - for vertices, threshold in zip_long_repeat(vertices_s, threshold_s): - if isinstance(threshold, (list, tuple)): - threshold = threshold[0] - - tri = Delaunay(np.array(vertices)) - if self.join: - verts_new = vertices - edges_new = set() - faces_new = set() - for simplex in tri.simplices: - if self.is_planar(vertices, simplex, threshold): - continue - edges_new.update(set(self.make_edges(simplex))) - faces_new.update(set(self.make_faces(simplex))) - verts_out.append(verts_new) - edges_out.append(list(edges_new)) - faces_out.append(list(faces_new)) - else: - verts_new = [] - edges_new = [] - faces_new = [] - for simplex in tri.simplices: - if self.is_planar(vertices, simplex, threshold): - continue - verts_simplex = self.get_verts(vertices, simplex) - edges_simplex = self.make_edges([0, 1, 2, 3]) - faces_simplex = self.make_faces([0, 1, 2, 3]) - verts_new.append(verts_simplex) - edges_new.append(edges_simplex) - faces_new.append(faces_simplex) - verts_out.extend(verts_new) - edges_out.extend(edges_new) - faces_out.extend(faces_new) - - self.outputs['Vertices'].sv_set(verts_out) - self.outputs['Edges'].sv_set(edges_out) - self.outputs['Faces'].sv_set(faces_out) + +class SvExDelaunay3DNode(SverchCustomTreeNode, bpy.types.Node): + """ + Triggers: Delaunay 3D + Tooltip: Generate 3D Delaunay Triangulation + """ + bl_idname = 'SvExDelaunay3DNode' + bl_label = 'Delaunay 3D' + bl_icon = 'OUTLINER_OB_EMPTY' + sv_icon = 'SV_VORONOI' + + join : BoolProperty( + name = "Join", + default = False, + update = updateNode) + + threshold : FloatProperty( + name = "Threshold", + default = 1e-4, + precision = 4, + update = updateNode) + + def draw_buttons(self, context, layout): + layout.prop(self, "join", toggle=True) + + def sv_init(self, context): + self.inputs.new('SvVerticesSocket', "Vertices") + self.inputs.new('SvStringsSocket', "Threshold").prop_name = 'threshold' + self.outputs.new('SvVerticesSocket', "Vertices") + self.outputs.new('SvStringsSocket', "Edges") + self.outputs.new('SvStringsSocket', "Faces") + + def make_edges(self, idxs): + return [(i, j) for i in idxs for j in idxs if i < j] + + def make_faces(self, idxs): + return [(i, j, k) for i in idxs for j in idxs for k in idxs if i < j and j < k] + + def get_verts(self, verts, idxs): + return [verts[i] for i in idxs] + + def is_planar(self, verts, idxs, threshold): + if threshold == 0: + return True + a, b, c, d = [verts[i] for i in idxs] + a, b, c, d = np.array(a), np.array(b), np.array(c), np.array(d) + v1 = b - a + v2 = c - a + v3 = d - a + v1 = v1 / np.linalg.norm(v1) + v2 = v2 / np.linalg.norm(v2) + v3 = v3 / np.linalg.norm(v3) + volume = np.cross(v1, v2).dot(v3) / 6 + return abs(volume) < threshold + + def process(self): + if not any(socket.is_linked for socket in self.outputs): + return + + vertices_s = self.inputs['Vertices'].sv_get() + threshold_s = self.inputs['Threshold'].sv_get() + + verts_out = [] + edges_out = [] + faces_out = [] + for vertices, threshold in zip_long_repeat(vertices_s, threshold_s): + if isinstance(threshold, (list, tuple)): + threshold = threshold[0] + + tri = Delaunay(np.array(vertices)) + if self.join: + verts_new = vertices + edges_new = set() + faces_new = set() + for simplex in tri.simplices: + if self.is_planar(vertices, simplex, threshold): + continue + edges_new.update(set(self.make_edges(simplex))) + faces_new.update(set(self.make_faces(simplex))) + verts_out.append(verts_new) + edges_out.append(list(edges_new)) + faces_out.append(list(faces_new)) + else: + verts_new = [] + edges_new = [] + faces_new = [] + for simplex in tri.simplices: + if self.is_planar(vertices, simplex, threshold): + continue + verts_simplex = self.get_verts(vertices, simplex) + edges_simplex = self.make_edges([0, 1, 2, 3]) + faces_simplex = self.make_faces([0, 1, 2, 3]) + verts_new.append(verts_simplex) + edges_new.append(edges_simplex) + faces_new.append(faces_simplex) + verts_out.extend(verts_new) + edges_out.extend(edges_new) + faces_out.extend(faces_new) + + self.outputs['Vertices'].sv_set(verts_out) + self.outputs['Edges'].sv_set(edges_out) + self.outputs['Faces'].sv_set(faces_out) + def register(): if scipy is not None: bpy.utils.register_class(SvExDelaunay3DNode) + def unregister(): if scipy is not None: bpy.utils.unregister_class(SvExDelaunay3DNode) - diff --git a/old_nodes/item.py b/old_nodes/item.py index 2f9cbd25e5..60595461dc 100644 --- a/old_nodes/item.py +++ b/old_nodes/item.py @@ -34,7 +34,6 @@ class ListItem2Node(SverchCustomTreeNode, bpy.types.Node): ''' List item ''' bl_idname = 'ListItem2Node' bl_label = 'List Item' - bl_icon = 'OUTLINER_OB_EMPTY' replacement_nodes = [('SvListItemNode', {"Item":"Index"}, None)] diff --git a/old_nodes/solid_to_mesh.py b/old_nodes/solid_to_mesh.py index 160cb117a5..212bd81195 100644 --- a/old_nodes/solid_to_mesh.py +++ b/old_nodes/solid_to_mesh.py @@ -25,7 +25,7 @@ class SvSolidToMeshNode(SverchCustomTreeNode, bpy.types.Node): bl_label = 'Solid to Mesh' bl_icon = 'MESH_CUBE' sv_icon = 'SV_SOLID_TO_MESH' - solid_catergory = "Outputs" + sv_category = "Solid Outputs" replacement_nodes = [('SvSolidToMeshNodeMk2', None, None)] diff --git a/settings.py b/settings.py index 239b2607c6..77e78eb8e8 100644 --- a/settings.py +++ b/settings.py @@ -301,31 +301,6 @@ def update_theme(self, context): over_sized_buttons: BoolProperty( default=False, name="Big buttons", description="Very big buttons") - node_panel_modes = [ - ("X", "Do not show", "Do not show node buttons", 0), - ("T", "T panel", "Show node buttons under the T panel", 1), - ("N", "N panel", "Show node under the N panel", 2) - ] - - node_panels: EnumProperty( - items = node_panel_modes, - name = "Display node buttons", - description = "Where to show node insertion buttons. Restart Blender to apply changes.", - default = "T") - - node_panels_icons_only : BoolProperty( - name = "Display icons only", - description = "Show node icon only when icon has an icon, otherwise show it's name", - default = True - ) - - node_panels_columns : IntProperty( - name = "Columns", - description = "Number of icon panels per row; Set to 0 for automatic selection", - default = 0, - min = 0, max = 12 - ) - input_links_options = [ ('NONE', "Do not show", "Do not show", 0), ('QUICKLINK', "Show single option only", @@ -426,14 +401,6 @@ def general_tab(self, layout): col_split = col.split(factor=0.5) col1 = col_split.column() - toolbar_box = col1.box() - toolbar_box.label(text="Node toolbars") - toolbar_box.prop(self, "node_panels") - if self.node_panels != "X": - toolbar_box.prop(self, "node_panels_icons_only") - if self.node_panels_icons_only: - toolbar_box.prop(self, "node_panels_columns") - col1.prop(self, "external_editor", text="Ext Editor") col1.prop(self, "real_sverchok_path", text="Src Directory") diff --git a/ui/__init__.py b/ui/__init__.py index 6f6f559aca..7a03a2f4cd 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -1,6 +1,6 @@ ui_modules = [ "color_def", "sv_IO_panel", "sv_examples_menu", 'sv_3d_panel', 'nodeview_operators', - "sv_panels", "nodeview_rclick_menu", "nodeview_space_menu","nodeview_solids_menu", + "sv_panels", "nodeview_rclick_menu", "nodeview_space_menu", "sv_icons", "presets", "nodes_replacement", "sv_panel_display_nodes", "sv_temporal_viewers", "sv_vep_connector", "zoom_to_node", # bgl modules @@ -8,4 +8,5 @@ # show git info "development", "nodeview_keymaps", + "add_nodes_panel", ] diff --git a/ui/add_nodes_panel.py b/ui/add_nodes_panel.py new file mode 100644 index 0000000000..2a806f703a --- /dev/null +++ b/ui/add_nodes_panel.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### +from collections import defaultdict + +import bpy + +import sverchok.ui.nodeview_space_menu as sm # import other way breaks showing custom icons +from bpy.props import PointerProperty, EnumProperty, StringProperty, BoolProperty, IntProperty + + +class AddNodeToolPanel(bpy.types.Panel): + """Nodes panel under the T panel""" + bl_idname = 'SV_PT_AddNodeToolPanel' + bl_space_type = "NODE_EDITOR" + bl_region_type = "TOOLS" + bl_label = "Sverchok Nodes" + + _items: list[sm.AddNode] = [] + _categories: dict[sm.Category, sm.AddNode] = dict() + + def node_search_update(self, context): + request = context.scene.sv_add_node_panel_settings.node_search + if not request: + AddNodeToolPanel._categories = dict() + return + + categories = defaultdict(list) + for cat in sm.add_node_menu.walk_categories(): + for add_node in cat: + if not isinstance(add_node, sm.AddNode): + continue + if add_node.search_match(request): + categories[cat].append(add_node) + + AddNodeToolPanel._categories = categories + + def select_category_update(self, context): + cat_name = context.scene.sv_add_node_panel_settings.selected_category + for cat in sm.add_node_menu.walk_categories(): + if cat.menu_cls.__name__ == cat_name: + items = [n for n in cat if isinstance(n, sm.AddNode)] + AddNodeToolPanel._items = items + return + + @property + def categories(self): + if not self._categories: + self.node_search_update(bpy.context) + return self._categories + + @property + def items(self): + """After reloading the items will be none. They can't be updated from + registration function. So do this on demand""" + if not self._items: + self.select_category_update(bpy.context) + return self._items + + @classmethod + def poll(cls, context): + try: + return context.space_data.node_tree.bl_idname == 'SverchCustomTreeType' + except: + return False + + def draw(self, context): + layout = self.layout + row = layout.row(align=True) + row.prop(context.scene.sv_add_node_panel_settings, "node_search", text="") + if context.scene.sv_add_node_panel_settings.node_search: + for cat, add_nodes in self.categories.items(): + icon_prop = sm.icon(cat.icon) if cat.icon else {} + layout.label(text=cat.name, **icon_prop) + if context.scene.sv_add_node_panel_settings.icons_only: + num = context.scene.sv_add_node_panel_settings.columns_number + grid = layout.grid_flow(row_major=True, align=True, columns=num) + grid.scale_x = 1.5 + for add_node in add_nodes: + add_node.draw(grid, only_icon=True) + else: + col = layout.column(align=True) + for add_node in add_nodes: + add_node.draw(col) + else: + layout.prop(context.scene.sv_add_node_panel_settings, "selected_category", text="") + if context.scene.sv_add_node_panel_settings.icons_only: + num = context.scene.sv_add_node_panel_settings.columns_number + grid = layout.grid_flow(row_major=True, align=True, columns=num) + grid.scale_x = 1.5 + for add_node in self.items: + add_node.draw(grid, only_icon=True) + else: + col = layout.column(align=True) + for add_node in self.items: + add_node.draw(col) + + col = layout.column() + col.use_property_split = True + col.use_property_decorate = False + col.prop(context.scene.sv_add_node_panel_settings, 'icons_only') + if context.scene.sv_add_node_panel_settings.icons_only: + col.prop(context.scene.sv_add_node_panel_settings, 'columns_number') + + +class AddNodePanelSettings(bpy.types.PropertyGroup): + def search_tooltip(self, context, edit_text): + for cat in sm.add_node_menu.walk_categories(): + for add_node in cat: + if not hasattr(add_node, 'search_match'): + continue + if add_node.search_match(edit_text): + yield add_node.label, "Some editional text" + + def categories(self, context): + # this should be a function because new categories can be added + # by Sverchok's extensions after the registration + for i, category in enumerate(sm.add_node_menu.walk_categories()): + if any(isinstance(add_node, sm.AddNode) for add_node in category): + identifier = category.menu_cls.__name__ + yield identifier, category.name, category.name, i + + selected_category: EnumProperty( + name="Category", + description="Select nodes category", + items=categories, + default=1, # it through errors in console without this option + update=AddNodeToolPanel.select_category_update, + ) + + search_prop = dict(search=search_tooltip) if bpy.app.version >= (3, 3) else {} + node_search: StringProperty( + name="Search", + description="Enter search term and press Enter to search; clear the" + " field to return to selection of node category.", + update=AddNodeToolPanel.node_search_update, + **search_prop + ) + + icons_only: BoolProperty( + name="Icons only", + description="Show node icon only when icon has an icon, otherwise show it's name", + default=True, + ) + + columns_number: IntProperty( + name="Columns", + description="Number of icon panels per row; Set to 0 for automatic selection", + default=5, + min=1, + max=12, + ) + + +classes = [AddNodeToolPanel, AddNodePanelSettings] + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.Scene.sv_add_node_panel_settings = PointerProperty( + type=AddNodePanelSettings) + + +def unregister(): + del bpy.types.Scene.sv_add_node_panel_settings + for cls in classes[::-1]: + bpy.utils.unregister_class(cls) diff --git a/ui/color_def.py b/ui/color_def.py index 6db5231f9a..d5f465cea7 100644 --- a/ui/color_def.py +++ b/ui/color_def.py @@ -20,10 +20,10 @@ import bpy from bpy.props import StringProperty -from sverchok.menu import make_node_cats from sverchok.utils.logging import debug import sverchok from sverchok.utils.handle_blender_data import BlTrees +from sverchok.ui.nodeview_space_menu import add_node_menu colors_cache = {} @@ -102,14 +102,16 @@ def sv_colors_definition(): } else: sv_node_colors = default_theme - sv_node_cats = make_node_cats() sv_cats_node = {} - for ca, no in sv_node_cats.items(): - for n in no: + + for cat in add_node_menu.walk_categories(): + for elem in cat: + if not hasattr(elem, 'bl_idname'): + continue try: - sv_cats_node[n[0]] = sv_node_colors[ca] + sv_cats_node[elem.bl_idname] = sv_node_colors[cat.name] except: - sv_cats_node[n[0]] = False + sv_cats_node[elem.bl_idname] = False return sv_cats_node def rebuild_color_cache(): diff --git a/ui/development.py b/ui/development.py index 04d55d98b8..816eee405f 100644 --- a/ui/development.py +++ b/ui/development.py @@ -27,7 +27,7 @@ # global variables in tools import sverchok -from sverchok.utils.sv_help import remapper +import sverchok.ui.nodeview_space_menu as sm from sverchok.utils.context_managers import sv_preferences from sverchok.utils import get_node_class_reference from sverchok.utils.development import get_branch @@ -108,11 +108,13 @@ class SvViewHelpForNode(bpy.types.Operator): def execute(self, context): n = context.active_node - string_dir = remapper.get(n.bl_idname) - if not string_dir: #external node + cat = sm.add_node_menu.get_category(n.bl_idname) + if not cat: # external node print('external_node') return external_node_docs(self, n, self.kind) + string_dir = cat.name.lower().replace('_', ' ') + filename = n.__module__.split('.')[-1] if filename in ('mask','mask_convert','mask_join'): string_dir = 'list_masks' @@ -189,7 +191,6 @@ class SvViewSourceForNode(bpy.types.Operator): def execute(self, context): n = context.active_node fpath = self.get_filepath_from_node(n) - string_dir = remapper.get(n.bl_idname) with sv_preferences() as prefs: diff --git a/ui/nodeview_keymaps.py b/ui/nodeview_keymaps.py index 1090caf042..9c0ef58ddc 100644 --- a/ui/nodeview_keymaps.py +++ b/ui/nodeview_keymaps.py @@ -137,29 +137,29 @@ def add_keymap(): # Shift + A | show custom menu kmi = km.keymap_items.new('wm.call_menu', 'A', 'PRESS', shift=True) - kmi.properties.name = "NODEVIEW_MT_Dynamic_Menu" + kmi.properties.name = "NODEVIEW_MT_SvCategoryAllCategories" # ui/nodeview_space_menu.py:add_node_menu nodeview_keymaps.append((km, kmi)) # numbers 1 to 5 for partial menus - kmi = km.keymap_items.new('wm.call_menu', 'ONE', 'PRESS') - kmi.properties.name = "NODEVIEW_MT_Basic_Data_Partial_Menu" + kmi = km.keymap_items.new('node.call_partial_menu', 'ONE', 'PRESS') + kmi.properties.menu_name = 'BasicDataPartialMenu' nodeview_keymaps.append((km, kmi)) - kmi = km.keymap_items.new('wm.call_menu', 'TWO', 'PRESS') - kmi.properties.name = "NODEVIEW_MT_Mesh_Partial_Menu" + kmi = km.keymap_items.new('node.call_partial_menu', 'TWO', 'PRESS') + kmi.properties.menu_name = "MeshPartialMenu" nodeview_keymaps.append((km, kmi)) - kmi = km.keymap_items.new('wm.call_menu', 'THREE', 'PRESS') - kmi.properties.name = "NODEVIEW_MT_Advanced_Objects_Partial_Menu" + kmi = km.keymap_items.new('node.call_partial_menu', 'THREE', 'PRESS') + kmi.properties.menu_name = "AdvancedObjectsPartialMenu" nodeview_keymaps.append((km, kmi)) - kmi = km.keymap_items.new('wm.call_menu', 'FOUR', 'PRESS') - kmi.properties.name = "NODEVIEW_MT_Connection_Partial_Menu" + kmi = km.keymap_items.new('node.call_partial_menu', 'FOUR', 'PRESS') + kmi.properties.menu_name = "ConnectionPartialMenu" nodeview_keymaps.append((km, kmi)) - kmi = km.keymap_items.new('wm.call_menu', 'FIVE', 'PRESS') - kmi.properties.name = "NODEVIEW_MT_UI_tools_Partial_Menu" + kmi = km.keymap_items.new('node.call_partial_menu', 'FIVE', 'PRESS') + kmi.properties.menu_name = "UiToolsPartialMenu" nodeview_keymaps.append((km, kmi)) # Shift + S | show custom menu kmi = km.keymap_items.new('wm.call_menu', 'S', 'PRESS', shift=True) - kmi.properties.name = "NODEVIEW_MT_Solids_Special_Menu" + kmi.properties.name = "NODEVIEW_MT_node_category_menu" nodeview_keymaps.append((km, kmi)) # alt + Space | enter extra search operator diff --git a/ui/nodeview_rclick_menu.py b/ui/nodeview_rclick_menu.py index 3d3d9e4274..4bf2783059 100644 --- a/ui/nodeview_rclick_menu.py +++ b/ui/nodeview_rclick_menu.py @@ -7,8 +7,8 @@ import bpy +import sverchok.ui.nodeview_space_menu as sm from sverchok.utils.sv_node_utils import frame_adjust -from sverchok.menu import draw_add_node_operator from sverchok.ui.presets import node_supports_presets, apply_default_preset from sverchok.core.sockets import SvCurveSocket, SvSurfaceSocket, SvStringsSocket, SvSolidSocket @@ -24,6 +24,8 @@ ['---', 'NodeReroute', 'ListLengthNode'] ] +common_nodes = [sm.Separator() if n == '---' else sm.AddNode(n) for ns in common_nodes for n in ns] + def connect_idx_viewer(tree, existing_node, new_node): # get connections going into vdmk2 and make a new idxviewer and connect the same sockets to that. @@ -291,20 +293,15 @@ def draw(self, context): col.prop(node, 'shrink') layout.separator() - layout.menu("NODEVIEW_MT_Dynamic_Menu", text='node menu') + layout.menu("NODEVIEW_MT_SvCategoryAllCategories", text='node menu') # layout.operator("node.duplicate_move") self.draw_conveniences(context, node) def draw_conveniences(self, context, node): layout = self.layout layout.separator() - for nodelist in common_nodes: - for named_node in nodelist: - if named_node == '---': - layout.separator() - else: - draw_add_node_operator(layout, named_node) - + for menu_elem in common_nodes: + menu_elem.draw(layout) def register(): diff --git a/ui/nodeview_solids_menu.py b/ui/nodeview_solids_menu.py deleted file mode 100644 index e1851889a9..0000000000 --- a/ui/nodeview_solids_menu.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -''' -zeffii 2014. - -borrows heavily from insights provided by Dynamic Space Bar! -but massively condensed for sanity. -''' - -import bpy - -from sverchok.menu import make_node_cats, draw_add_node_operator -from sverchok.utils import get_node_class_reference -from sverchok.utils.extra_categories import get_extra_categories -from sverchok.ui.sv_icons import node_icon, icon, get_icon_switch, custom_icon -from sverchok.ui import presets -# from nodeitems_utils import _node_categories - -sv_tree_types = {'SverchCustomTreeType', } -node_cats = make_node_cats() - -def category_has_nodes(cat_name): - cat = node_cats[cat_name] - for item in cat: - rna = get_node_class_reference(item[0]) - if rna and not item[0] == 'separator': - return True - return False -#menu_prefs = {} - -# _items_to_remove = {} -def layout_draw_categories(layout, node_details): - - for node_info in node_details: - - if node_info[0] == 'separator': - layout.separator() - continue - - if not node_info: - print(repr(node_info), 'is incomplete, or unparsable') - continue - - bl_idname = node_info[0] - - # this is a node bl_idname that can be registered but shift+A can drop it from showing. - if bl_idname == 'ScalarMathNode': - continue - - node_ref = get_node_class_reference(bl_idname) - - if hasattr(node_ref, "bl_label"): - layout_params = dict(text=node_ref.bl_label, **node_icon(node_ref)) - elif bl_idname == 'NodeReroute': - layout_params = dict(text='Reroute',icon_value=custom_icon('SV_REROUTE')) - else: - continue - - node_op = draw_add_node_operator(layout, bl_idname, params=layout_params) - -def layout_draw_solid_categories(layout, node_details, sub_category): - - for node_info in node_details: - - if node_info[0] == 'separator': - layout.separator() - continue - - if not node_info: - print(repr(node_info), 'is incomplete, or unparsable') - continue - - bl_idname = node_info[0] - - # this is a node bl_idname that can be registered but shift+A can drop it from showing. - if bl_idname == 'ScalarMathNode': - continue - - node_ref = get_node_class_reference(bl_idname) - - if hasattr(node_ref, "bl_label"): - layout_params = dict(text=node_ref.bl_label, **node_icon(node_ref)) - - else: - continue - - if hasattr(node_ref, "solid_catergory") and node_ref.solid_catergory == sub_category: - node_op = draw_add_node_operator(layout, bl_idname, params=layout_params) - -# does not get registered -class NodeViewMenuSolidTemplate(bpy.types.Menu): - bl_label = "" - - def draw(self, context): - layout_draw_solid_categories(self.layout, node_cats["Solids"], self.bl_label) - layout_draw_solid_categories(self.layout, node_cats["Exchange"], self.bl_label) - # layout_draw_categories(self.layout, node_cats[self.bl_label]) - # prop_menu_enum(data, property, text="", text_ctxt="", icon='NONE') - - -# quick class factory. -def make_solids_class(name, bl_label): - name = 'NODEVIEW_MT_Add_Solids' + name - return type(name, (NodeViewMenuSolidTemplate,), {'bl_label': bl_label}) - - -class NODEVIEW_MT_Solids_Input_Menu(bpy.types.Menu): - bl_label = "Inputs" - @classmethod - def poll(cls, context): - tree_type = context.space_data.tree_type - if tree_type in sv_tree_types: - #menu_prefs['show_icons'] = get_icon_switch() - # print('showing', menu_prefs['show_icons']) - return True - def draw(self, context): - layout = self.layout - layout.operator_context = 'INVOKE_REGION_WIN' - - layout_draw_solid_categories(self.layout, node_cats["Solids"], self.bl_label) - - -class NODEVIEW_MT_Solids_Special_Menu(bpy.types.Menu): - bl_label = "Solids" - @classmethod - def poll(cls, context): - tree_type = context.space_data.tree_type - if tree_type in sv_tree_types: - return True - def draw(self, context): - layout = self.layout - layout.operator_context = 'INVOKE_REGION_WIN' - - layout.menu('NODEVIEW_MT_Add_SolidsInputs') - layout.menu('NODEVIEW_MT_Add_SolidsOperators') - layout.menu('NODEVIEW_MT_Add_SolidsOutputs') - - -classes = [ - make_solids_class('Inputs', 'Inputs'), - make_solids_class('Operators', 'Operators'), - make_solids_class('Outputs', 'Outputs'), - # NODEVIEW_MT_Solids_Input_Menu, - NODEVIEW_MT_Solids_Special_Menu -] - -def register(): - - for class_name in classes: - bpy.utils.register_class(class_name) - -def unregister(): - for class_name in classes: - bpy.utils.unregister_class(class_name) diff --git a/ui/nodeview_space_menu.py b/ui/nodeview_space_menu.py index 488a26e233..e914251308 100644 --- a/ui/nodeview_space_menu.py +++ b/ui/nodeview_space_menu.py @@ -16,135 +16,406 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### +from abc import ABC, abstractmethod +from collections import defaultdict +from pathlib import Path +from typing import Iterator, Union, TypeVar, Optional -''' -zeffii 2014. - -borrows heavily from insights provided by Dynamic Space Bar! -but massively condensed for sanity. -''' - +import bl_operators import bpy +from bpy.props import StringProperty -from sverchok.menu import ( - make_node_cats, - draw_add_node_operator, - is_submenu_call, - get_submenu_call_name, - compose_submenu_name, -) - -from sverchok.utils import get_node_class_reference -from sverchok.utils.extra_categories import get_extra_categories, extra_category_providers -from sverchok.ui.sv_icons import node_icon, icon, custom_icon +from sverchok.ui.sv_icons import node_icon, icon, get_icon_switch from sverchok.ui import presets +from sverchok.ui.presets import apply_default_preset +from sverchok.utils import yaml_parser +from sverchok.utils.modules_inspection import iter_classes_from_module +from sverchok.utils.dummy_nodes import dummy_nodes_dict + + +""" +The module is used for generating Shift+A / Add(Node) menu. +The structure of the menu also used for searching nodes and Add Node Tool panel. +`add_node_menu` is instance of the panel which can be used to access its data. + +Sverchok's extensions can use this module to add their nodes to the menu. +For this two steps should be done: + +- Pass config file to `Category.append_from_config` method of `add_node_menu` + object. +- Call `Category.register` method during registration of an extension. +Registration is needed because menus which were added in Sverchok are already +registered. And un-registration is not needed because they will be reloaded +during reloading Sverchok add-on (extensions can't be reloaded without reloading +Sverchok) +The module parses `index.yaml` file and creates tre like data structure `Category` +which is used for adding nodes in different areas of user interface. Also it +contains `CallPartialMenu` which shows alternative menus by pressing 1, 2, 3, 4, 5. +It's possible to add categories to the menus by adding `- extra_menu: menu_name` +attribute to a category in `index.yaml` file where menu_name is one of possible +menu names: + +- BasicDataPartialMenu +- MeshPartialMenu +- AdvancedObjectsPartialMenu +- ConnectionPartialMenu +- UiToolsPartialMenu +""" + + +CutSelf = TypeVar("CutSelf", bound="Category") sv_tree_types = {'SverchCustomTreeType', } -node_cats = make_node_cats() -menu_class_by_title = dict() -def category_has_nodes(cat_name): - cat = node_cats[cat_name] - for item in cat: - rna = get_node_class_reference(item[0]) - if rna and not item[0] == 'separator': - return True - return False -#menu_prefs = {} - -# _items_to_remove = {} -menu_structure = [ - ["separator"], - ["NODEVIEW_MT_AddGenerators", 'OBJECT_DATAMODE'], - ["NODEVIEW_MT_AddCurves", 'OUTLINER_OB_CURVE'], - ["NODEVIEW_MT_AddSurfaces", 'SURFACE_DATA'], - ["NODEVIEW_MT_AddFields", 'OUTLINER_OB_FORCE_FIELD'], - ["NODEVIEW_MT_AddSolids", 'MESH_CUBE'], - ["NODEVIEW_MT_AddSpatial", 'POINTCLOUD_DATA'], - ["NODEVIEW_MT_AddTransforms", 'ORIENTATION_LOCAL'], - ["NODEVIEW_MT_AddAnalyzers", 'VIEWZOOM'], - ["NODEVIEW_MT_AddModifiers", 'MODIFIER'], - ["NODEVIEW_MT_AddCAD", 'TOOL_SETTINGS'], - ["separator"], - ["NODEVIEW_MT_AddNumber", "SV_NUMBER"], - ["NODEVIEW_MT_AddVector", "SV_VECTOR"], - ["NODEVIEW_MT_AddMatrix", 'EMPTY_AXIS'], - ["NODEVIEW_MT_AddQuaternion", 'SV_QUATERNION'], - ["NODEVIEW_MT_AddColor", 'COLOR'], - ["NODEVIEW_MT_AddLogic", "SV_LOGIC"], - ["NODEVIEW_MT_AddListOps", 'NLA'], - ["NODEVIEW_MT_AddDictionary", 'OUTLINER_OB_FONT'], - ["separator"], - ["NODEVIEW_MT_AddViz", 'RESTRICT_VIEW_OFF'], - ["NODEVIEW_MT_AddText", 'TEXT'], - ["NODEVIEW_MT_AddScene", 'SCENE_DATA'], - ["NODEVIEW_MT_AddExchange", 'ARROW_LEFTRIGHT'], - ["NODEVIEW_MT_AddLayout", 'NODETREE'], - ["NODEVIEW_MT_AddBPYData", "BLENDER"], - ["separator"], - ["NODEVIEW_MT_AddScript", "WORDWRAP_ON"], - ["NODEVIEW_MT_AddNetwork", "SYSTEM"], - ["NODEVIEW_MT_AddPulgaPhysics", "MOD_PHYSICS"], - ["NODEVIEW_MT_AddSVG", "SV_SVG"], - ["NODEVIEW_MT_AddBetas", "SV_BETA"], - ["NODEVIEW_MT_AddAlphas", "SV_ALPHA"], - ["separator"], - ["NODE_MT_category_SVERCHOK_GROUP", "NODETREE"], - ["NODEVIEW_MT_AddPresetOps", "SETTINGS"], -] -def layout_draw_categories(layout, category_name, node_details): +class MenuItem(ABC): + _node_classes = dict() - global menu_class_by_title + @property + def node_classes(self) -> dict[str, type]: # todo can be removed after removing dummy nodes + if not self._node_classes: + import sverchok # not available during initialization of the module + for cls_ in iter_classes_from_module(sverchok.nodes, [bpy.types.Node]): + self._node_classes[cls_.bl_idname] = cls_ + return self._node_classes - for node_info in node_details: + @property + def icon_prop(self): + if hasattr(self, 'icon'): + return icon(self.icon) + return {} - if node_info[0] == 'separator': - layout.separator() - continue + @abstractmethod + def draw(self, layout): + pass - if not node_info: - print(repr(node_info), 'is incomplete, or unparsable') - continue + def __repr__(self): + name = getattr(self, 'name', '') + name = f' "{name}"' if name else '' + return f"<{type(self).__name__}{name}>" - bl_idname = node_info[0] - if is_submenu_call(bl_idname): - submenu_title = get_submenu_call_name(bl_idname) - menu_title = compose_submenu_name(category_name, bl_idname) - menu_class = menu_class_by_title[menu_title] - layout.menu(menu_class.__name__, text=submenu_title) - continue +class Separator(MenuItem): + def draw(self, layout): + layout.separator() - # this is a node bl_idname that can be registered but shift+A can drop it from showing. - if bl_idname == 'ScalarMathNode': - continue - node_ref = get_node_class_reference(bl_idname) +class Operator(MenuItem): + def __init__(self, name, operator, icon_name=''): # it's possible to add extra options for operator call + self.name = name + self.bl_idname = operator + self.icon = icon_name + + def draw(self, layout): + layout.operator_context = 'INVOKE_REGION_WIN' # I'm not sure that all operators need this + layout.operator(self.bl_idname, text=self.name, **self.icon_prop) + + @classmethod + def from_config(cls, config: dict): + name = list(config.keys())[0] + values = list(config.values())[0] + props = dict() + for prop in values: + key = list(prop.keys())[0] + value = list(prop.values())[0] + props[key] = value + return cls(name, **props) + + @classmethod + def is_operator_config(cls, config: dict): + for prop in list(config.values())[0]: + if isinstance(prop, dict): + prop_name = list(prop.keys())[0] + if prop_name.lower() == 'operator': + return True + return False + + +class CustomMenu(MenuItem): + def __init__(self, name, custom_menu, icon_name=''): + self.name = name + self.bl_idname = custom_menu + self.icon = icon_name + + def draw(self, layout): + layout.menu(self.bl_idname, text=self.name, **self.icon_prop) + + @classmethod + def from_config(cls, config: dict): + name = list(config.keys())[0] + values = list(config.values())[0] + props = dict() + for prop in values: + key = list(prop.keys())[0] + value = list(prop.values())[0] + props[key] = value + return cls(name, **props) - if hasattr(node_ref, "bl_label"): - layout_params = dict(text=node_ref.bl_label, **node_icon(node_ref)) - elif bl_idname == 'NodeReroute': - layout_params = dict(text='Reroute',icon_value=custom_icon('SV_REROUTE')) + @classmethod + def is_custom_menu_config(cls, config: dict): + for prop in list(config.values())[0]: + if isinstance(prop, dict): + prop_name = list(prop.keys())[0] + if prop_name.lower() == 'custom_menu': + return True + return False + + +class AddNode(MenuItem): + def __init__(self, id_name): + self.bl_idname = id_name + self._label = None + self._icon_prop = None + + @property + def label(self): + """This and other properties of the class can't be accessed during + module initialization and registration""" + if self._label is None: + node_cls = bpy.types.Node.bl_rna_get_subclass_py(self.bl_idname) + if self.bl_idname == 'NodeReroute': + self._label = "Reroute" + # todo check labels of dependent classes after their refactoring + elif node_cls is not None: + self._label = node_cls.bl_label + else: # todo log missing nodes? + self._label = f'{self.bl_idname} (not found)' + return self._label + + @property + def icon_prop(self): + if self._icon_prop is None: + node_cls = bpy.types.Node.bl_rna_get_subclass_py(self.bl_idname) + if self.bl_idname == 'NodeReroute': + self._icon_prop = icon('SV_REROUTE') + elif self.dependency: + if cls := self.node_classes.get(self.bl_idname): + self._icon_prop = node_icon(cls) + else: + self._icon_prop = {'icon': 'ERROR'} + elif node_cls is not None: # can be dummy class here + self._icon_prop = node_icon(node_cls) + else: + self._icon_prop = {'icon': 'ERROR'} + return self._icon_prop + + @property + def dependency(self): + _, dep = dummy_nodes_dict.get(self.bl_idname, (None, '')) + return dep + + def draw(self, layout, only_icon=False): + node_cls = bpy.types.Node.bl_rna_get_subclass_py(self.bl_idname) + if only_icon: + icon_prop = self.icon_prop or {'icon': 'OUTLINER_OB_EMPTY'} else: - continue + icon_prop = self.icon_prop if get_icon_switch() else {} - node_op = draw_add_node_operator(layout, bl_idname, params=layout_params) + if not self.dependency and node_cls is None: + layout.label(text=self.label, **icon_prop) + return -# does not get registered -class NodeViewMenuTemplate(bpy.types.Menu): - bl_label = "" + op = ShowMissingDependsOperator if self.dependency else SvNodeAddOperator + default_context = bpy.app.translations.contexts.default + add = layout.operator(op.bl_idname, + text=self.label if not only_icon else '', + text_ctxt=default_context, + **icon_prop) + add.type = self.bl_idname + add.use_transform = True + add.dependency = self.dependency + + def search_match(self, request: str) -> bool: + """Return True if the request satisfies to node search tags""" + request = request.upper() + if request in self.label.upper(): + return True + node_class = bpy.types.Node.bl_rna_get_subclass_py(self.bl_idname) + if not node_class or not hasattr(node_class, 'docstring'): + return False + if request in node_class.docstring.get_shorthand(): + return True + if request in node_class.docstring.get_tooltip(): + return True + return False + + +class Category(MenuItem): + """It keeps the whole structure of Add Node menu. Instancing the class with + `Category.from_config` method generates menu classes. They should be + registered by calling `Category.register` + """ + def __init__(self, name, menu_cls, icon_name='BLANK1', extra_menu=''): + self.name = name + self.icon = icon_name + self.extra_menu = extra_menu + self.menu_cls: CategoryMenuTemplate = menu_cls + + def draw(self, layout): + icon_prop = icon(self.icon) if get_icon_switch() else {} + layout.menu(self.menu_cls.__name__, **icon_prop) # text=submenu_title) + + def __iter__(self) -> Iterator[Union[Separator, AddNode, 'Category']]: + return iter(e for e in self.menu_cls.draw_data) + + def walk_categories(self) -> 'Category': + """Iterate over all nested categories. The current category is also included.""" + yield self + for elem in self.menu_cls.draw_data: + if hasattr(elem, 'walk_categories'): + yield from elem.walk_categories() + + def get_category(self, node_idname) -> Optional['Category']: + """The search is O(len(sverchok_nodes))""" + for cat in self.walk_categories(): + for elem in cat: + if isinstance(elem, AddNode): + if elem.bl_idname == node_idname: + return cat + + def register(self): + """Register itself and all its elements. + Can be called several times by Sverchok's extensions""" + if not self.is_registered: + bpy.utils.register_class(self.menu_cls) + for elem in self.walk_categories(): + if not elem.is_registered: + elem.register() + + @property + def is_registered(self): + return hasattr(bpy.types, self.menu_cls.__name__) + + def unregister(self): + """Register itself and all its elements. Should be called only once by + Sverchok's unregister function.""" + bpy.utils.unregister_class(self.menu_cls) + for elem in self.menu_cls.draw_data: + if hasattr(elem, 'unregister'): + elem.unregister() + + def __repr__(self): + return f'' - def draw(self, context): - layout_draw_categories(self.layout, self.bl_label, node_cats[self.bl_label]) - # prop_menu_enum(data, property, text="", text_ctxt="", icon='NONE') + @classmethod + def from_config(cls, conf: list, menu_name, **extra_props) -> CutSelf: + """ + It creates category from given config of category items. The format + of config is next: + + [option, option, ..., menu item, menu item, ...] + + Menu item can be one of next elements: + + - `'---'` - String of a dotted line to define separator. + - `'SomeNode'` - String of node `bl_idname` to define Add Node operator. + - `{'Sub category name': [option, menu_item, ...]}` - Dictionary of + a subcategory. + - `{'Operator name': [option, ...]}` - Dictionary of a custom opeartor + to call. Options for operator call are not supported currently. + - `{'Menu name': [option, ...]}` - Custom menu to show. + + Options have such format - `{'option_name': value}`. They can be added + to sub categories, operators and custom menus. + + Categories can have next options: + + - `{'icon_name': 'SOME_ICON'}` - Value can be a string of standard Blender + icons or Sverchok icon. + - `{'extra_menu': 'menu_name'}` - Values is a name of one of extra menus. + Possible values: + + - "BasicDataPartialMenu" + - "MeshPartialMenu" + - "AdvancedObjectsPartialMenu" + - "ConnectionPartialMenu" + - "UiToolsPartialMenu" + + Operators options: + + - `{"icon_name": "SOME_ICON"}` - Icon to show in the menu. + - `{"operator": "operator id name"}` - `bl_idname` of operator to call. + + Custom menus options: + + - `{"icon_name": "SOME_CION"}` - Icon to show in menu. + - `{"custom_menu": "menu id name"}` - `bl_idname` of menu to show. + + The example of format can be found in the `sverchok/index.yaml` file. + + Extra_props should have keys oly from the __init__ method of `MenuItem` + subclasses. + """ + parsed_items = [] + + # parsing menu elements + for elem in conf: + if isinstance(elem, dict): + if Operator.is_operator_config(elem): + parsed_items.append(Operator.from_config(elem)) + continue + if CustomMenu.is_custom_menu_config(elem): + parsed_items.append(CustomMenu.from_config(elem)) + continue + + name = list(elem.keys())[0] + value = list(elem.values())[0] + props = dict() + if isinstance(value, list): # sub menu + + # pars sub menu properties + for prop in value: + if not isinstance(prop, dict): # some node or separator + continue + prop_value = list(prop.values())[0] + if isinstance(prop_value, list): # some sub menu + continue + props.update(prop) + + elif value is None: # empty category + value = [] + else: # menu property + continue # was already handled + parsed_items.append(cls.from_config(value, name, **props)) + + else: # some value, separator? + if all('-' == ch for ch in elem): # separator + parsed_items.append(Separator()) + else: + parsed_items.append(AddNode(elem)) + # generate menu of current list + cls_name = 'NODEVIEW_MT_SvCategory' + menu_name.title().replace(' ', '') + menu_cls = type(cls_name, + (CategoryMenuTemplate, bpy.types.Menu), + {'bl_label': menu_name, 'draw_data': parsed_items}) -class SV_NodeTree_Poll(): - """ - mixin to detect if the current nodetree is a Sverchok type nodetree, if not poll returns False - """ + return cls(menu_name, menu_cls, **extra_props) + + def append_from_config(self, config: list): + """ + This method is expected to be used by Sverchok's extensions to add + extra items to the Add Node menu. See the format of config in + `Category.from_config` documentation. Example: + + import sverchok.ui.nodeview_space_menu as sm + sm.add_node_menu.append_from_config(config) + + def register(): + sm.add_node_menu.register() + + It should be called before registration functions + """ + new_categories = [] + for cat in config: + cat_name = list(cat.keys())[0] + items = list(cat.values())[0] + new_categories.append(self.from_config(items, cat_name)) + self.menu_cls.draw_data.extend(new_categories) + + +class SverchokContext: @classmethod def poll(cls, context): tree_type = context.space_data.tree_type @@ -152,110 +423,148 @@ def poll(cls, context): return True -# quick class factory. -def make_class(name, bl_label): - global menu_class_by_title - name = 'NODEVIEW_MT_Add' + name - clazz = type(name, (NodeViewMenuTemplate,), {'bl_label': bl_label}) - menu_class_by_title[bl_label] = clazz - return clazz +class CategoryMenuTemplate(SverchokContext): + bl_label = '' + draw_data = [] # items to draw + def draw(self, context): + # it would be better to have the condition only for the root menu + if not getattr(context.space_data, 'edit_tree', None): + # todo also it's possible to give choice of picking one of existing node trees + self.layout.operator("node.new_node_tree", + text="New Sverchok Node Tree", + icon="RNA_ADD") + return + for elem in self.draw_data: + elem.draw(self.layout) -class NODEVIEW_MT_Dynamic_Menu(bpy.types.Menu, SV_NodeTree_Poll): - bl_label = "Sverchok Nodes" - def draw(self, context): +menu_file = Path(__file__).parents[1] / 'index.yaml' +add_node_menu = Category.from_config(yaml_parser.load(menu_file), 'All Categories', icon_name='RNA') - # dont show up in other tree menu (needed because we bypassed poll by appending manually) - tree_type = context.space_data.tree_type - if not tree_type in sv_tree_types: - return - layout = self.layout - layout.operator_context = 'INVOKE_REGION_WIN' +class AddNodeOp(bl_operators.node.NodeAddOperator): + dependency: StringProperty() - # if self.bl_idname == 'NODEVIEW_MT_Dynamic_Menu': - layout.operator("node.sv_extra_search", text="Search", icon='OUTLINER_DATA_FONT') + _node_classes = dict() - for item in menu_structure: - if item[0] == 'separator': - layout.separator() - else: - if "Add" in item[0]: - name = item[0].split("Add")[1] - if name in node_cats: - if category_has_nodes(name): - layout.menu(item[0], **icon(item[1])) - - else: - layout.menu(item[0], **icon(item[1])) - else: - # print('AA', globals()[item[0]].bl_label) - layout.menu(item[0], **icon(item[1])) + @classmethod + def node_classes(cls) -> dict[str, type]: + if not cls._node_classes: + import sverchok # not available during initialization of the module + for cls_ in iter_classes_from_module(sverchok.nodes, [bpy.types.Node]): + cls._node_classes[cls_.bl_idname] = cls_ + return cls._node_classes - if extra_category_providers: - for provider in extra_category_providers: - if hasattr(provider, 'use_custom_menu') and provider.use_custom_menu: - layout.menu(provider.custom_menu) - else: - for category in provider.get_categories(): - layout.menu("NODEVIEW_MT_EX_" + category.identifier) + @classmethod + def description(cls, _context, properties): + node_type = properties["type"] + tooltip = '' + if node_type in dummy_nodes_dict: + if node_cls := cls.node_classes().get(node_type): + tooltip = node_cls.docstring.get_tooltip() + gap = "\n\n" if tooltip else '' + tooltip = tooltip + f"{gap}Dependency: {properties.dependency}" + else: + if node_cls := bpy.types.Node.bl_rna_get_subclass_py(node_type): + tooltip = node_cls.docstring.get_tooltip() + return tooltip -class NodePatialMenuTemplate(bpy.types.Menu, SV_NodeTree_Poll): - bl_label = "" - items = [] +class SvNodeAddOperator(AddNodeOp, bpy.types.Operator): + """Operator to show as menu item to add available node""" + bl_idname = "node.sv_add_node" + bl_label = "Add SV node" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} - def draw(self, context): - layout = self.layout - layout.operator_context = 'INVOKE_REGION_WIN' - for i in self.items: - item = menu_structure[i] - layout.menu(item[0], **icon(item[1])) + def execute(self, context): + node = self.create_node(context) + apply_default_preset(node) + return {'FINISHED'} -# quick class factory. -def make_partial_menu_class(name, bl_label, items): - name = f'NODEVIEW_MT_{name}_Partial_Menu' - clazz = type(name, (NodePatialMenuTemplate,), {'bl_label': bl_label, 'items':items}) - return clazz + # if node is not available the operator is noe used - there is no need in poll -class NODEVIEW_MT_AddGenerators(bpy.types.Menu): - bl_label = "Generator" - def draw(self, context): - layout = self.layout - layout_draw_categories(self.layout, self.bl_label, node_cats[self.bl_label]) - layout.menu("NODEVIEW_MT_AddGeneratorsExt", **icon('PLUGIN')) +class ShowMissingDependsOperator(AddNodeOp, bpy.types.Operator): + """Operator as menu item to show that a node is not available""" + bl_idname = 'node.show_missing_dependencies' + bl_label = 'Show missing dependencies' + bl_options = {'REGISTER', 'UNDO'} -class NODEVIEW_MT_AddBPYData(bpy.types.Menu): - bl_label = "BPY Data" + @classmethod + def poll(cls, context): + # the message can be customized if to generate separate class for each node + if hasattr(cls, 'poll_message_set'): # Bl 2.93 does not have the method + cls.poll_message_set('The library is not installed') + return False - def draw(self, context): - layout = self.layout - layout_draw_categories(self.layout, self.bl_label, node_cats['BPY Data']) - layout_draw_categories(self.layout, self.bl_label, node_cats['Objects']) -class NODEVIEW_MT_AddModifiers(bpy.types.Menu): - bl_label = "Modifiers" +class CallPartialMenu(SverchokContext, bpy.types.Operator): + """It calls dynamic menus to show alternative set of node categories.""" + bl_idname = "node.call_partial_menu" + bl_label = "Call partial menu" + bl_options = {'REGISTER', 'UNDO'} - def draw(self, context): - layout = self.layout - layout.menu("NODEVIEW_MT_AddModifierChange") - layout.menu("NODEVIEW_MT_AddModifierMake") + menu_name: StringProperty() + + def execute(self, context): + + def draw(_self, context): + _self.layout.prop_menu_enum(self, 'test') + for cat in add_node_menu.walk_categories(): + if cat.extra_menu == self.menu_name: + cat.draw(_self.layout) + context.window_manager.popup_menu(draw, title=self.menu_name, icon='BLANK1') + return {'FINISHED'} -class NODEVIEW_MT_AddListOps(bpy.types.Menu): - bl_label = "List" + +class NodeCategoryMenu(SverchokContext, bpy.types.Menu): + """ It's a dynamic menu with submenus - https://blender.stackexchange.com/a/269716 + It scans `sv_category` attribute of all nodes and builds categories according + the collected data. + """ + bl_label = "Node Categories" + bl_idname = "NODEVIEW_MT_node_category_menu" + + @property + def categories(self): + if not self._categories: + import sverchok + cats = defaultdict(list) + # todo replace with `bpy.types.Node.bl_rna_get_subclass_py` after dummy nodes refactoring + for cls in iter_classes_from_module(sverchok.nodes, [bpy.types.Node]): + if name := getattr(cls, 'sv_category', None): + cats[name].append(AddNode(cls.bl_idname)) + self._categories = cats + return self._categories + + _categories = dict() + _layout_values = dict() def draw(self, context): layout = self.layout - layout.menu("NODEVIEW_MT_AddListmain") - layout.menu("NODEVIEW_MT_AddListstruct") - layout_draw_categories(self.layout, "List Masks", node_cats["List Masks"]) - layout_draw_categories(self.layout, "List Mutators", node_cats["List Mutators"]) + if not self.categories: + layout.label(text='Nodes was not found') + return + + parent_id = getattr(context, 'CONTEXT_ID', None) + + if parent_id is None: # root + NodeCategoryMenu._layout_values.clear() # protect from overflow + for cat_name in self.categories.keys(): + row = layout.row() + row.context_pointer_set('CONTEXT_ID', row) + NodeCategoryMenu._layout_values[row] = cat_name + row.menu(NodeCategoryMenu.bl_idname, text=str(cat_name)) + else: + for n in self._categories[NodeCategoryMenu._layout_values[parent_id]]: + n.draw(layout) + preset_category_menus = dict() + def make_preset_category_menu(category): global preset_category_menus if category in preset_category_menus: @@ -277,7 +586,9 @@ def draw(self, context): preset_category_menus[category] = SvPresetCategorySubmenu return SvPresetCategorySubmenu -class NODEVIEW_MT_AddPresetOps(bpy.types.Menu): + +class AddPresetMenu(bpy.types.Menu): + bl_idname = 'NODEVIEW_MT_AddPresetMenu' bl_label = "Presets" def draw(self, context): @@ -290,7 +601,8 @@ def draw(self, context): layout.menu(class_name) -class NODE_MT_category_SVERCHOK_GROUP(bpy.types.Menu): +class SverchokGroupMenu(bpy.types.Menu): + bl_idname = 'NODE_MT_SverchokGroupMenu' bl_label = "Group" def draw(self, context): @@ -301,101 +613,24 @@ def draw(self, context): layout.operator('node.add_group_tree_from_selected') -extra_category_menu_classes = dict() - -def make_extra_category_menus(): - global extra_category_menu_classes - extra_categories = get_extra_categories() - menu_classes = [] - for category in extra_categories: - if category.identifier in extra_category_menu_classes: - clazz = extra_category_menu_classes[category.identifier] - menu_classes.append(clazz) - else: - class NODEVIEW_MT_ExtraCategoryMenu(bpy.types.Menu): - bl_label = category.name - - def draw(self, context): - nodes = [[item.nodetype] for item in self.category_items] - layout_draw_categories(self.layout, category.name, nodes) - - class_name = "NODEVIEW_MT_EX_" + category.identifier - items = list(category.items(None)) - menu_class = type(class_name, (NODEVIEW_MT_ExtraCategoryMenu,), {'category_items': items}) - menu_classes.append(menu_class) - extra_category_menu_classes[category.identifier] = menu_class - bpy.utils.register_class(menu_class) - return menu_classes - classes = [ - NODEVIEW_MT_Dynamic_Menu, - NODEVIEW_MT_AddListOps, - NODEVIEW_MT_AddModifiers, - NODEVIEW_MT_AddGenerators, - NODEVIEW_MT_AddBPYData, - NODEVIEW_MT_AddPresetOps, - NODE_MT_category_SVERCHOK_GROUP, - # like magic. - # make | NODEVIEW_MT_Add + class name , menu name - make_class('GeneratorsExt', "Generators Extended"), - make_class('CurvePrimitives', "Curves @ Primitives"), - make_class('BezierCurves', "Curves @ Bezier"), - make_class('NurbsCurves', "Curves @ NURBS"), - make_class('Curves', "Curves"), - make_class('NurbsSurfaces', "Surfaces @ NURBS"), - make_class('Surfaces', "Surfaces"), - make_class('Fields', "Fields"), - make_class('MakeSolidFace', "Solids @ Make Face"), - make_class('AnalyzeSolid', "Solids @ Analyze"), - make_class('Solids', "Solids"), - make_class('Transforms', "Transforms"), - make_class('Spatial', "Spatial"), - make_class('Analyzers', "Analyzers"), - make_class('Viz', "Viz"), - make_class('Text', "Text"), - make_class('Scene', "Scene"), - make_class('Layout', "Layout"), - make_class('Listmain', "List Main"), - make_class('Liststruct', "List Struct"), - make_class('Dictionary', "Dictionary"), - make_class('Number', "Number"), - make_class('Vector', "Vector"), - make_class('Matrix', "Matrix"), - make_class('Quaternion', "Quaternion"), - make_class('Color', "Color"), - make_class('CAD', "CAD"), - make_class('ModifierChange', "Modifier Change"), - make_class('ModifierMake', "Modifier Make"), - make_class('Logic', "Logic"), - make_class('Script', "Script"), - make_class('Network', "Network"), - make_class('Exchange', "Exchange"), - make_class('PulgaPhysics', "Pulga Physics"), - make_class('SVG', "SVG"), - make_class('Betas', "Beta Nodes"), - make_class('Alphas', "Alpha Nodes"), - - # make | NODEVIEW_MT_ + class name +_Partial_Menu , menu name, menu items - make_partial_menu_class('Basic_Data', 'Basic Data Types (1)', range(12, 20)), - make_partial_menu_class('Mesh', 'Mesh (2)', [1, 7, 8, 9, 10]), - make_partial_menu_class('Advanced_Objects', 'Advanced Objects (3)', [2, 3, 4, 5, 6, 28, 30, 32, 33]), - make_partial_menu_class('Connection', 'Connection (4)', [21, 22, 23, 24, 26, 29, 31]), - make_partial_menu_class('UI_tools', 'SV Interface (5)', [25, 35, 36]) - + AddPresetMenu, + SverchokGroupMenu, + SvNodeAddOperator, + ShowMissingDependsOperator, + CallPartialMenu, + NodeCategoryMenu, ] -def sv_draw_menu(self, context): + +def sv_draw_menu(self, context): + """This is drawn in ADD menu of the header of a tree editor""" tree_type = context.space_data.tree_type if not tree_type in sv_tree_types: return - layout = self.layout - layout.operator_context = "INVOKE_DEFAULT" - if not any([(g.bl_idname in sv_tree_types) for g in bpy.data.node_groups]): - layout.operator("node.new_node_tree", text="New Sverchok Node Tree", icon="RNA_ADD") - return + self.layout.menu_contents(add_node_menu.menu_cls.__name__) - NODEVIEW_MT_Dynamic_Menu.draw(self, context) def register(): @@ -404,14 +639,14 @@ def register(): for class_name in classes: bpy.utils.register_class(class_name) bpy.types.NODE_MT_add.append(sv_draw_menu) + add_node_menu.register() -def unregister(): - global menu_class_by_title +def unregister(): + add_node_menu.unregister() for class_name in classes: bpy.utils.unregister_class(class_name) for category in presets.get_category_names(): if category in preset_category_menus: bpy.utils.unregister_class(preset_category_menus[category]) bpy.types.NODE_MT_add.remove(sv_draw_menu) - menu_class_by_title = dict() diff --git a/ui/presets.py b/ui/presets.py index 91f1545055..d1ee1137f3 100644 --- a/ui/presets.py +++ b/ui/presets.py @@ -884,6 +884,7 @@ def draw_presets_ops(layout, category=None, id_tree=None, presets=None, context= class SV_PT_UserPresetsPanel(bpy.types.Panel): bl_idname = "SV_PT_UserPresetsPanel" bl_label = "Presets" + bl_options = {'DEFAULT_CLOSED'} # to improve performance bl_space_type = 'NODE_EDITOR' bl_region_type = 'TOOLS' # bl_category = 'Presets' diff --git a/ui/sv_icons.py b/ui/sv_icons.py index 86eb46f3aa..907fb61f64 100644 --- a/ui/sv_icons.py +++ b/ui/sv_icons.py @@ -63,7 +63,14 @@ def load_custom_icons(): iconName = os.path.splitext(iconFile)[0] iconID = iconName.upper() preview = custom_icons.load(iconID, os.path.join(iconsDir, iconFile), "IMAGE") - #debug(f"{iconID} => {tuple(preview.image_size)}") + + # there is a problem of loading images (at least in Blender 3.3.1 on Windows) + # some images for some reason are not displayed in UI, though ImagePreviews + # return some icon_id. Such problem can be detected by checking the + # icon_pixels, but iterating over them right after registration of a preview + # demolish the problem + any(preview.icon_pixels) + # print(f"{iconID} => {any(preview.icon_pixels)}") for provider in _icon_providers.values(): provider.init(custom_icons) @@ -84,30 +91,28 @@ def get_icon_switch(): if addon and hasattr(addon, "preferences"): return addon.preferences.show_icons + def icon(display_icon): '''returns empty dict if show_icons is False, else the icon passed''' kws = {} - if get_icon_switch(): - if display_icon.startswith('SV_'): - kws = {'icon_value': custom_icon(display_icon)} - elif display_icon != 'OUTLINER_OB_EMPTY': - kws = {'icon': display_icon} + if display_icon.startswith('SV_'): + kws = {'icon_value': custom_icon(display_icon)} + else: + kws = {'icon': display_icon} return kws def node_icon(node_ref): - '''returns empty dict if show_icons is False, else the icon passed''' - if not get_icon_switch(): - return {} + """Returns empty dict if show_icons is False, else the icon passed.""" + if ic := getattr(node_ref, 'sv_icon', None): + iconID = custom_icon(ic) + return {'icon_value': iconID} if iconID else {} + elif hasattr(node_ref, 'bl_icon'): + iconID = node_ref.bl_icon + return {'icon': iconID} if iconID else {} else: - if hasattr(node_ref, 'sv_icon'): - iconID = custom_icon(node_ref.sv_icon) - return {'icon_value': iconID} if iconID else {} - elif hasattr(node_ref, 'bl_icon') and node_ref.bl_icon != 'OUTLINER_OB_EMPTY': - iconID = node_ref.bl_icon - return {'icon': iconID} if iconID else {} - else: - return {} + return {} + def register(): load_custom_icons() diff --git a/ui/sv_panel_display_nodes.py b/ui/sv_panel_display_nodes.py index d49c19400e..7fcad472eb 100644 --- a/ui/sv_panel_display_nodes.py +++ b/ui/sv_panel_display_nodes.py @@ -17,16 +17,16 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -from bpy.props import IntProperty, StringProperty, BoolProperty, FloatProperty, EnumProperty, PointerProperty +from bpy.props import IntProperty, EnumProperty, PointerProperty from sverchok.utils.context_managers import sv_preferences -from sverchok.menu import make_node_cats from sverchok.settings import get_dpi_factor from sverchok.utils.dummy_nodes import is_dependent -from pprint import pprint from sverchok.utils.logging import debug from collections import namedtuple +from sverchok.ui.nodeview_space_menu import add_node_menu + _node_category_cache = {} # cache for the node categories _spawned_nodes = {} # cache for the spawned nodes @@ -95,8 +95,7 @@ def binpack(nodes, max_bin_height, spacing=0): def should_display_node(name): - if name == "separator" or '@' in name or name == "SvFormulaNodeMk5": - # if name == "separator" or is_dependent(name) or '@' in name: + if name == "SvFormulaNodeMk5": return False else: return True @@ -117,22 +116,18 @@ def cache_node_categories(): if _node_category_cache: return - node_categories = make_node_cats() - categories = node_categories.keys() - - debug("categories = %s" % list(categories)) + categories = [c.name for c in add_node_menu.walk_categories()] _node_category_cache["categories"] = {} _node_category_cache["categories"]["names"] = list(categories) _node_category_cache["categories"]["names"].append("All") _node_category_cache["categories"]["All"] = {} _node_category_cache["categories"]["All"]["nodes"] = [] - for category in categories: - debug("ADDING category: %s" % category) - nodes = [n for l in node_categories[category] for n in l] + for cat in add_node_menu.walk_categories(): + nodes = [n.bl_idname for n in cat if hasattr(n, 'bl_idname')] nodes = list(filter(lambda node: should_display_node(node), nodes)) - _node_category_cache["categories"][category] = {} - _node_category_cache["categories"][category]["nodes"] = nodes + _node_category_cache["categories"][cat.name] = {} + _node_category_cache["categories"][cat.name]["nodes"] = nodes _node_category_cache["categories"]["All"]["nodes"].extend(nodes) category_items = [] diff --git a/utils/__init__.py b/utils/__init__.py index 39d2b8873e..7d51c9de78 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -140,7 +140,7 @@ def app_handler_ops(append=None, remove=None): "sv_IO_pointer_helpers", "sv_operator_mixins", "console_print", "sv_gist_tools", "sv_IO_panel_tools", "sv_load_archived_blend", - "sv_help", "sv_default_macros", "sv_macro_utils", "sv_extra_search", "sv_3dview_tools", + "sv_default_macros", "sv_macro_utils", "sv_extra_search", "sv_3dview_tools", "sv_update_utils", "sv_obj_helper", "sv_batch_primitives", "sv_idx_viewer28_draw", "sv_texture_utils", # geom 2d tools diff --git a/utils/extra_categories.py b/utils/extra_categories.py index 94356fdc13..7dd03ef277 100644 --- a/utils/extra_categories.py +++ b/utils/extra_categories.py @@ -43,12 +43,6 @@ def unregister_extra_category_provider(identifier): raise Exception(f"Provider {identifier} was not registered") del extra_category_providers[idx] -def get_extra_categories(): - global extra_category_providers - result = [] - for provider in extra_category_providers: - result.extend(provider.get_categories()) - return result def external_node_docs(operator, node, kind): diff --git a/utils/sv_extra_search.py b/utils/sv_extra_search.py index 23c2358402..4458c9a5bc 100644 --- a/utils/sv_extra_search.py +++ b/utils/sv_extra_search.py @@ -21,17 +21,13 @@ import bpy import sverchok -from sverchok.menu import make_node_cats from sverchok.utils import get_node_class_reference from sverchok.utils.logging import error from sverchok.utils.docstring import SvDocstring from sverchok.utils.sv_default_macros import macros, DefaultMacros -from nodeitems_utils import _node_categories -from sverchok.utils.extra_categories import get_extra_categories -# pylint: disable=c0326 +from sverchok.ui.nodeview_space_menu import add_node_menu -node_cats = make_node_cats() addon_name = sverchok.__name__ loop = {} @@ -98,27 +94,20 @@ def fx_extend(idx, datastorage): datastorage.append((func_name, format_macro_item(func_name, func_descriptor), '', idx)) idx +=1 -def gather_extra_nodes(idx, datastorage, context): - extra_categories = get_extra_categories() - for cat in extra_categories: - for node in cat.items(context): - if node.nodetype == 'separator': - continue - description = SvDocstring(node.get_node_class().__doc__).get_shorthand() - showstring = node.label + ensure_short_description(description) - datastorage.append((str(idx), showstring,'',idx)) - loop_reverse[node.label] = node.nodetype - idx +=1 def gather_items(context): fx = [] idx = 0 - for _, node_list in node_cats.items(): - for item in node_list: - if item[0] in {'separator', 'NodeReroute'}: + + for cat in add_node_menu.walk_categories(): + for item in cat: + if not hasattr(item, 'bl_idname'): + continue + + if item.bl_idname == 'NodeReroute': continue - nodetype = get_node_class_reference(item[0]) + nodetype = get_node_class_reference(item.bl_idname) if not nodetype: continue @@ -133,7 +122,6 @@ def gather_items(context): fx.append((k, format_item(k, v), '', idx)) idx += 1 - gather_extra_nodes(idx, fx, context) fx_extend(idx, fx) return fx diff --git a/utils/sv_help.py b/utils/sv_help.py deleted file mode 100644 index e6ab9ff51f..0000000000 --- a/utils/sv_help.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -remapper = {} - -def build_help_remap(fdict): - for k, v in fdict.items(): - for bl_idname in v: - remapper[bl_idname[0]] = k.lower().replace('_', ' ') diff --git a/utils/yaml_parser.py b/utils/yaml_parser.py new file mode 100644 index 0000000000..d8da4d3fa0 --- /dev/null +++ b/utils/yaml_parser.py @@ -0,0 +1,113 @@ +""" +Limited implementation of reading yaml files. Should be replaced when such +library is available in build-in Python source. +""" + + +def load(file): + with open(file) as yaml_lines: + stack: list = [] + for raw_line in yaml_lines: + line = YamlLine(raw_line) + if line.is_blank or line.is_comment: + continue + while len(stack) > line.indent_level: + stack.pop() + current = stack[-1] if stack else ... + + if line.is_list_value: + + # init root list + if not stack: + data = [] + stack.append(data) + current = data + + # replace None dict value with a list + if current is None: # all dicts can keep only dicts for now + new_list = [] + for key in stack[-2].keys(): # should have only one key + stack[-2][key] = new_list + break + stack[-1] = new_list + current = new_list + + if line.is_dict_value: # it is list item and new dictionary + if not isinstance(current, list): + raise TypeError("A list was expected here") + new_dict = {line.key: line.dict_value} + current.append(new_dict) + stack.append(new_dict) + stack.append(line.dict_value) + + else: # new list item to a list + stack[-1].append(line.list_value) + + elif line.is_dict_value: + raise TypeError(f'Dictionary values are excepted only as part of some list - "{raw_line}"') + else: + raise TypeError(f'Any value should be either list of dictionary key - "{raw_line}"') + return data + + +class YamlLine: + def __init__(self, line): + self._line: str = self._remove_comment(line) + + @property + def is_comment(self): + return self._line.strip().startswith('#') + + @property + def is_blank(self): + return not self._line.strip() + + @property + def is_list_value(self): + return self._line.strip().startswith('-') + + @property + def is_dict_value(self): + line = self._line.expandtabs(1).strip() + return ': ' in line or ':' == line[-1] + + @property + def indent_level(self): + # https://docs.python.org/3/reference/lexical_analysis.html#indentation + first_letter, *_ = self._line.strip() + indention = self._line.expandtabs(4).index(first_letter) + return (indention // 4) * 2 + 1 # fragile + + @property + def key(self): + line = self._line.expandtabs(1).strip() + if line.startswith('-'): + line = line[1:].strip() + if ':' == line[-1]: + return line[:-1].strip() + key, value = line.split(': ') + return key.strip() + + @property + def list_value(self): + line = self._line.expandtabs(1).strip() + if self.is_list_value: + return line.split('-', 1)[1].strip() + + @property + def dict_value(self): + line = self._line.expandtabs(1).strip() + if ':' == line[-1]: + return None + key, value = line.split(': ') + return value.strip().strip("'\"") # always return as a string for now + + @staticmethod + def _remove_comment(line): + line, _, comment = line.partition(' #') # can be incorrect inside string values + return line + + +if __name__ == '__main__': + from pprint import pprint + pprint(load('../index.yaml'))