How to close an open Exceldocument in F#? - excel

I'searching for a function to close an opened Exceldocument.
That's how I open it:
let xlApp = new Excel.ApplicationClass()
let xlWorkBookInput = xlApp.Workbooks.Open(#"C:\Projects\Tool\Versuch\VersuchZuEditieren.xlsx")
let viewStateMachine state event =
match state, event with
| {State = _}, ConsumablesClicked vm ->
view.HandleModelChanged { Title = "My Title"; Comment = sprintf "" }
let TrackPosition = vm.TrackPosition
let xlWorkSheetInput = xlWorkBookInput.Worksheets.["Consumables"] :?> Excel.Worksheet
let array = vm.TubePosition.Split([|','|])
let arrayLength = array.Length
let addIndex = sprintf "%s%d"
let randomNumber = System.Random()
for i in 0 .. arrayLength - 1 do
xlWorkSheetInput.Cells.[startindex + i + 2, 1] <- vm.TrackPosition
xlWorkSheetInput.Cells.[startindex + i + 2, 2] <- array.[i]
let randomBarcodePart2 = randomNumber.Next(000000, 999999)
xlWorkSheetInput.Cells.[startindex+ i + 2, 3] <- sprintf "%s%06d" vm.FixBarcodePart randomBarcodePart2
startindex <- startindex + arrayLength
{ State = "Consumables" }
And here I will close it, but how?

From comments in the discussion, Excel documents can be closed with
xlApp.Workbooks.Close()

Related

Office Scripts (Sum Cells By Color)

I'm trying convert one code that it make a sum of cells by color in VBA, but i need to use the same code or action from code in Office Scripts, i dont know how is the structure in this plataform, maybe, can you help me to do it?
the code in VBA is this:
code
Function SumByColor(Cellcolor As Range, RangeSum As Range) As Double
Dim cell As Range
For Each cell In RangeSum
If celda.Interior.ColorIndex = Celdacolor.Cells(1, 1).Interior.ColorIndex Then SumByColor = SumByColor+ cell
Next cell
Set cell = Nothing
End Function
So i need to use this code in office scripts
Here is one way to write your function in OfficeScript and how to call it -
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getActiveWorksheet();
console.log (sumByColor(sheet.getRange("E41"), workbook.getSelectedRange()))
}
function sumByColor(cellColor:ExcelScript.Range, rangeSum:ExcelScript.Range):number {
let value = 0;
let rowCount = rangeSum.getRowCount();
let columnCount = rangeSum.getColumnCount();
let colorToCheck = cellColor.getFormat().getFill().getColor();
// loop through each cell in rangeSum
for (let row=0; row<rowCount; row++)
for (let column = 0; column < columnCount; column++)
{
if (rangeSum.getCell(row,column).getFormat().getFill().getColor() == colorToCheck)
value += rangeSum.getCell(row, column).getValue() as number
}
return value;
}
Thanks for your help. My final code is it :
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getActiveWorksheet();
var cont = 2;
const celdas = ['B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10', 'I10', 'J10', 'K10', 'L10', 'M10'];
celdas.forEach(celda => {
let valCel = celda;
let startingCell = sheet.getRange(valCel);
var ranguito = "O" + cont.toString();
let rangeDataValue = sheet.getRange(ranguito).getValue() as string;
console.log(sumByColor(sheet.getRange("Q3"), sheet.getRange(rangeDataValue), startingCell))
cont = cont + 1;
});
}
function sumByColor(cellColor: ExcelScript.Range, rangeSum: ExcelScript.Range, writeCell: ExcelScript.Range) {
let value = 0;
let rowCount = rangeSum.getRowCount();
let columnCount = rangeSum.getColumnCount();
let colorToCheck = cellColor.getFormat().getFill().getColor();
// loop through each cell in rangeSum
for (let row = 0; row < rowCount; row++)
for (let column = 0; column < columnCount; column++) {
if (rangeSum.getCell(row, column).getFormat().getFill().getColor() == colorToCheck) {
var total = rangeSum.getCell(row, column).getValue() as string;
value = value + parseFloat(total);
}
}
writeCell.setValue(value);
console.log(value)
}

export generated excel variable to real excel file

this is how I generated Excel variable:
Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook excelworkBook;
Microsoft.Office.Interop.Excel.Worksheet excelSheet;
Microsoft.Office.Interop.Excel.Range excelCellrange;
excelworkBook = excel.Application.Workbooks.Add(Type.Missing);
excelSheet = (Microsoft.Office.Interop.Excel.Worksheet)excelworkBook.ActiveSheet;
//proper data from xml data field to store in excel
// ************************
//generate a data table and fill excell cell
excelCellrange = excelSheet.Range[excelSheet.Cells[1, 1],
excelSheet.Cells[theDataSet.Tables[0].Rows.Count+1, theDataSet.Tables[0].Columns.Count]];
excelCellrange.Font.ThemeColor =
Microsoft.Office.Interop.Excel.XlThemeColor.xlThemeColorAccent1;
excelCellrange.EntireColumn.AutoFit();
Microsoft.Office.Interop.Excel.Borders border = excelCellrange.Borders;
border.LineStyle = Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous;
border.Weight = 3d;
excel.Columns.AutoFit();
excel.DisplayAlerts = false;
excel.Visible = true;
now the question is how save this excel variable to real excel file to specific destination ?
best regards.
this is how I figure out this problem that maybe help someone else:
Microsoft.Office.Interop.Excel.Application excel = new
Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook excelworkBook;
Microsoft.Office.Interop.Excel.Worksheet excelSheet;
Microsoft.Office.Interop.Excel.Range excelCellrange;
excelworkBook = excel.Application.Workbooks.Add(Type.Missing);
excelSheet = (Microsoft.Office.Interop.Excel.Worksheet)excelworkBook.ActiveSheet;
XmlDocument xml = new XmlDocument();
xml.LoadXml(dtAll.Rows[gridEX1.CurrentRow.RowIndex]["Data"].ToString());
StringReader theReader = new StringReader(xml.InnerXml);
DataSet theDataSet = new DataSet();
theDataSet.ReadXml(theReader);
excel.Caption = dtAll.Rows[gridEX1.CurrentRow.RowIndex]["docID"].ToString() + "_" + dtAll.Rows[gridEX1.CurrentRow.RowIndex]["DocTitle"].ToString() + "_" + dtAll.Rows[gridEX1.CurrentRow.RowIndex]["DocDetails"].ToString();
excel.StandardFont = "B Nazanin";
excel.StatusBar = "col count: " + theDataSet.Tables[0].Rows.Count;
for (int i = 0; i < theDataSet.Tables[0].Columns.Count; i++)
{
excel.Cells[1, i + 1] = theDataSet.Tables[0].Columns[i].ColumnName;
excel.Cells[1, i + 1].Font.Color = System.Drawing.Color.Blue;
for (int j = 0; j < theDataSet.Tables[0].Rows.Count; j++)
{
excel.Cells[j + 2, i + 1].Font.Color = System.Drawing.Color.Black;
excel.Cells[j + 2, i + 1] = theDataSet.Tables[0].Rows[j][i].ToString();
}
}
excelCellrange = excelSheet.Range[excelSheet.Cells[1, 1], excelSheet.Cells[theDataSet.Tables[0].Rows.Count + 1, theDataSet.Tables[0].Columns.Count]];
excelCellrange.Font.ThemeColor = Microsoft.Office.Interop.Excel.XlThemeColor.xlThemeColorAccent1;
excelCellrange.EntireColumn.AutoFit();
Microsoft.Office.Interop.Excel.Borders border = excelCellrange.Borders;
border.LineStyle = Microsoft.Office.Interop.Excel.XlLineStyle.xlContinuous;
border.Weight = 3d;
byte[] output = Encoding.UTF8.GetBytes(xml.InnerXml);
string strfn = GroupTitle + ".xlsx";
if (File.Exists(strfn))
{
File.Delete(strfn);
}
FileStream fs = new FileStream(strfn, FileMode.CreateNew, FileAccess.Write);
fs.Write(output, 0, output.Length);
fs.Flush();
fs.Close();
string dest = Global.DocOutputPath.Substring(0, Global.DocOutputPath.Length - 1) + GroupTitle + ".xlsx";
if (File.Exists(dest))
{
DialogResult dialogResult = MessageBox.Show("are you sure about rewrite on file?", "warnning", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
File.Copy(strfn, dest);
File.Delete(dest);
}
else if (dialogResult == DialogResult.No)
{
return;
}
}
else
{
File.Copy(strfn, dest);
}

Find all indices of a search term in a string

I need a fast method to find all indices of a search term that might occur in a string. I tried this 'brute force' String extension method:
// Note: makes use of ExSwift
extension String
{
var length: Int { return count(self) }
func indicesOf(searchTerm:String) -> [Int] {
var indices = [Int]()
for i in 0 ..< self.length {
let segment = self[i ... (i + searchTerm.length - 1)]
if (segment == searchTerm) {
indices.append(i)
}
}
return indices;
}
}
... But it's ridiculously slow, especially the shorter the search term is. What would be a better method to find all indices fast?
As Martin said you can implement some of the well known fastest algorithms in String Matching, The Knuth–Morris–Pratt string searching algorithm (or KMP algorithm) searches for occurrences of a "word" W within a main "text string" S.
The algorithm has complexity O(n), where n is the length of S and the O is big-O notation.
extension String {
// Build pi function of prefixes
private func build_pi(str: String) -> [Int] {
var n = count(str)
var pi = Array(count: n + 1, repeatedValue: 0)
var k = -1
pi[0] = -1
for (var i = 0; i < n; ++i) {
while (k >= 0 && str[k] != str[i]) {
k = pi[k]
}
pi[i + 1] = ++k
}
return pi
}
// Knuth-Morris Pratt algorithm
func searchPattern(pattern: String) -> [Int] {
var matches = [Int]()
var n = count(self)
var m = count(pattern)
var k = 0
var pi = build_pi(pattern)
for var i = 0; i < n; ++i {
while (k >= 0 && (k == m || pattern[k] != self[i])) {
k = pi[k]
}
if ++k == m {
matches.append(i - m + 1)
}
}
return matches
}
subscript (i: Int) -> Character {
return self[advance(self.startIndex, i)]
}
}
Then you can use it in the following way:
var string = "apurba mandal loves ayoshi loves"
var pattern = "loves"
println(string.searchPattern(pattern))
An the output should be :
[14, 27]
That belong to the start index of the pattern occurrences inside the the string. I hope this help you.
EDIT:
As Martin said in his comment you need to avoid the use of the advance function to index an String by an Int because it's O(position to index).
One possible solution is to convert the String to an array of Character and then access to the indexes is O(1).
Then the extension can be changed to this one :
extension String {
// Build pi function of prefixes
private func build_pi(str: [Character]) -> [Int] {
var n = count(str)
var pi = Array(count: n + 1, repeatedValue: 0)
var k = -1
pi[0] = -1
for (var i = 0; i < n; ++i) {
while (k >= 0 && str[k] != str[i]) {
k = pi[k]
}
pi[i + 1] = ++k
}
return pi
}
// Knuth-Morris Pratt algorithm
func searchPattern(pattern: String) -> [Int] {
// Convert to Character array to index in O(1)
var patt = Array(pattern)
var S = Array(self)
var matches = [Int]()
var n = count(self)
var m = count(pattern)
var k = 0
var pi = build_pi(patt)
for var i = 0; i < n; ++i {
while (k >= 0 && (k == m || patt[k] != S[i])) {
k = pi[k]
}
if ++k == m {
matches.append(i - m + 1)
}
}
return matches
}
}
Instead of checking for the search term at each position of the string
you could use rangeOfString() to find the next occurrence (hoping
that rangeOfString() uses more advanced algorithms):
extension String {
func indicesOf(searchTerm:String) -> [Int] {
var indices = [Int]()
var pos = self.startIndex
while let range = self.rangeOfString(searchTerm, range: pos ..< self.endIndex) {
indices.append(distance(self.startIndex, range.startIndex))
pos = range.startIndex.successor()
}
return indices
}
}
Generally, it depends on the size of the input string and the size
of the search string which algorithm is "the fastest". You'll find
an overview with links to various algorithms in
String searching algorithm.
Update for Swift 3:
extension String {
func indices(of searchTerm:String) -> [Int] {
var indices = [Int]()
var pos = self.startIndex
while let range = range(of: searchTerm, range: pos ..< self.endIndex) {
indices.append(distance(from: startIndex, to: range.lowerBound))
pos = index(after: range.lowerBound)
}
return indices
}
}
Using NSRegularExpression in Swift 4, you can do it like this. NSRegularExpression has been around forever and is probably a better choice than rolling your own algorithm for most cases.
let text = "The quieter you become, the more you can hear."
let searchTerm = "you"
let regex = try! NSRegularExpression(pattern: searchTerm, options: [])
let range: NSRange = NSRange(text.startIndex ..< text.endIndex, in: text)
let matches: [NSTextCheckingResult] = regex.matches(in: text, options: [], range: range)
let ranges: [NSRange] = matches.map { $0.range }
let indices: [Int] = ranges.map { $0.location }
let swiftRanges = ranges.map { Range($0, in: text) }
let swiftIndices: [String.Index] = swiftRanges.flatMap { $0?.lowerBound }

How to get SubstringValues from Textbox

len = int.Parse(tE1Clt.Text);
front=int.Prase(tE_Fstng.text);
string result = tE3_Series.Text.Substring(front, len);
tE1_Sub.Text = result.ToString();
I'm getting error
[index and length must refer to a location within the string. Parameter name: `length`]
if (TeInput.Text.Length > 1)
{
string Input = TeInput.Text, Store1 = string.Empty, store2 = string.Empty;
int Start = 0, End = 0;
Start = int.Parse(TeStart.Text);
End = int.Parse(TeEnd.Text);
string Output = TeOutput.Text;
Store1 = Input.Remove(0, Start).Trim();
store2 = Store1.Remove(Store1.Length-End).Trim();
TeOutput.Text = store2;
You can do like this,
var str = "----takemeout----";
var start = str.IndexOf('t'); // start index of first t
var length = 9; // length of takemeout string
var result = str.Substring(start, length);
Value or result will be "takemeout"

Extracting vertices from scenekit

I'm having a problem with understanding scenekit geometery.
I have the default cube from Blender, and I export as collada (DAE), and can bring it into scenekit.... all good.
Now I want to see the vertices for the cube. In the DAE I can see the following for the "Cube-mesh-positions-array",
"1 1 -1 1 -1 -1 -1 -0.9999998 -1 -0.9999997 1 -1 1 0.9999995 1 0.9999994 -1.000001 1 -1 -0.9999997 1 -1 1 1"
Now what I'd like to do in scenekit, is get the vertices back, using something like the following:
SCNGeometrySource *vertexBuffer = [[cubeNode.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex] objectAtIndex:0];
If I process the vertexBuffer (I've tried numerous methods of looking at the data), it doesn't seem correct.
Can somebody explain what "SCNGeometrySourceSemanticVertex" is giving me, and how to extract the vertex data properly? What I'd like to see is:
X = "float"
Y = "float"
Z = "float"
Also I was investigating the following class / methods, which looked promising (some good data values here), but the data from gmpe appears empty, is anybody able to explain what the data property of "SCNGeometryElement" contains?
SCNGeometryElement *gmpe = [theCurrentNode.geometry geometryElementAtIndex:0];
Thanks, assistance much appreciated,
D
The geometry source
When you call geometrySourcesForSemantic: you are given back an array of SCNGeometrySource objects with the given semantic in your case the sources for the vertex data).
This data could have been encoded in many different ways and a multiple sources can use the same data with a different stride and offset. The source itself has a bunch of properties for you to be able to decode the data like for example
dataStride
dataOffset
vectorCount
componentsPerVector
bytesPerComponent
You can use combinations of these to figure out which parts of the data to read and make vertices out of them.
Decoding
The stride tells you how many bytes you should step to get to the next vector and the offset tells you how many bytes offset from the start of that vector you should offset before getting to the relevant pars of the data for that vector. The number of bytes you should read for each vector is componentsPerVector * bytesPerComponent
Code to read out all the vertices for a single geometry source would look something like this
// Get the vertex sources
NSArray *vertexSources = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
// Get the first source
SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources
NSInteger stride = vertexSource.dataStride; // in bytes
NSInteger offset = vertexSource.dataOffset; // in bytes
NSInteger componentsPerVector = vertexSource.componentsPerVector;
NSInteger bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
NSInteger vectorCount = vertexSource.vectorCount;
SCNVector3 vertices[vectorCount]; // A new array for vertices
// for each vector, read the bytes
for (NSInteger i=0; i<vectorCount; i++) {
// Assuming that bytes per component is 4 (a float)
// If it was 8 then it would be a double (aka CGFloat)
float vectorData[componentsPerVector];
// The range of bytes for this vector
NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
bytesPerVector); // and read the lenght of one vector
// Read into the vector data buffer
[vertexSource.data getBytes:&vectorData range:byteRange];
// At this point you can read the data from the float array
float x = vectorData[0];
float y = vectorData[1];
float z = vectorData[2];
// ... Maybe even save it as an SCNVector3 for later use ...
vertices[i] = SCNVector3Make(x, y, z);
// ... or just log it
NSLog(#"x:%f, y:%f, z:%f", x, y, z);
}
The geometry element
This will give you all the vertices but won't tell you how they are used to construct the geometry. For that you need the geometry element that manages the indices for the vertices.
You can get the number of geometry elements for a piece of geometry from the geometryElementCount property. Then you can get the different elements using geometryElementAtIndex:.
The element can tell you if the vertices are used a individual triangles or a triangle strip. It also tells you the bytes per index (the indices may have been ints or shorts which will be necessary to decode its data.
Here is an extension method if the data isn't contiguous (the vector size isn't equal to the stride) which can be the case when the geometry is loaded from a DAE file. It also doesn't use copyByte function.
extension SCNGeometry{
/**
Get the vertices (3d points coordinates) of the geometry.
- returns: An array of SCNVector3 containing the vertices of the geometry.
*/
func vertices() -> [SCNVector3]? {
let sources = self.sources(for: .vertex)
guard let source = sources.first else{return nil}
let stride = source.dataStride / source.bytesPerComponent
let offset = source.dataOffset / source.bytesPerComponent
let vectorCount = source.vectorCount
return source.data.withUnsafeBytes { (buffer : UnsafePointer<Float>) -> [SCNVector3] in
var result = Array<SCNVector3>()
for i in 0...vectorCount - 1 {
let start = i * stride + offset
let x = buffer[start]
let y = buffer[start + 1]
let z = buffer[start + 2]
result.append(SCNVector3(x, y, z))
}
return result
}
}
}
The Swift Version
The Objective-C version and this are essentially identical.
let planeSources = _planeNode?.geometry?.geometrySourcesForSemantic(SCNGeometrySourceSemanticVertex)
if let planeSource = planeSources?.first {
let stride = planeSource.dataStride
let offset = planeSource.dataOffset
let componentsPerVector = planeSource.componentsPerVector
let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent
let vectors = [SCNVector3](count: planeSource.vectorCount, repeatedValue: SCNVector3Zero)
let vertices = vectors.enumerate().map({
(index: Int, element: SCNVector3) -> SCNVector3 in
var vectorData = [Float](count: componentsPerVector, repeatedValue: 0)
let byteRange = NSMakeRange(index * stride + offset, bytesPerVector)
planeSource.data.getBytes(&vectorData, range: byteRange)
return SCNVector3Make(vectorData[0], vectorData[1], vectorData[2])
})
// You have your vertices, now what?
}
Here's a Swift 5.3 version, based on the other answers, and that also supports a bytesPerComponent different from 4 (untested for size different from 4 though):
extension SCNGeometrySource {
var vertices: [SCNVector3] {
let stride = self.dataStride
let offset = self.dataOffset
let componentsPerVector = self.componentsPerVector
let bytesPerVector = componentsPerVector * self.bytesPerComponent
func vectorFromData<FloatingPoint: BinaryFloatingPoint>(_ float: FloatingPoint.Type, index: Int) -> SCNVector3 {
assert(bytesPerComponent == MemoryLayout<FloatingPoint>.size)
let vectorData = UnsafeMutablePointer<FloatingPoint>.allocate(capacity: componentsPerVector)
defer {
vectorData.deallocate()
}
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
let rangeStart = index * stride + offset
self.data.copyBytes(to: buffer, from: rangeStart..<(rangeStart + bytesPerVector))
return SCNVector3(
CGFloat.NativeType(vectorData[0]),
CGFloat.NativeType(vectorData[1]),
CGFloat.NativeType(vectorData[2])
)
}
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: self.vectorCount)
return vectors.indices.map { index -> SCNVector3 in
switch bytesPerComponent {
case 4:
return vectorFromData(Float32.self, index: index)
case 8:
return vectorFromData(Float64.self, index: index)
case 16:
return vectorFromData(Float80.self, index: index)
default:
return SCNVector3Zero
}
}
}
}
// call this function _ = vertices(node: mySceneView.scene!.rootNode)
// I have get the volume in Swift 4.2 :--- this function
func vertices(node:SCNNode) -> [SCNVector3] {
let planeSources1 = node.childNodes.first?.geometry
let planeSources = planeSources1?.sources(for: SCNGeometrySource.Semantic.vertex)
if let planeSource = planeSources?.first {
let stride = planeSource.dataStride
let offset = planeSource.dataOffset
let componentsPerVector = planeSource.componentsPerVector
let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: planeSource.vectorCount)
let vertices = vectors.enumerated().map({
(index: Int, element: SCNVector3) -> SCNVector3 in
let vectorData = UnsafeMutablePointer<Float>.allocate(capacity: componentsPerVector)
let nsByteRange = NSMakeRange(index * stride + offset, bytesPerVector)
let byteRange = Range(nsByteRange)
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
planeSource.data.copyBytes(to: buffer, from: byteRange)
return SCNVector3Make(buffer[0], buffer[1], buffer[2])
})
var totalVolume = Float()
var x1 = Float(),x2 = Float(),x3 = Float(),y1 = Float(),y2 = Float(),y3 = Float(),z1 = Float(),z2 = Float(),z3 = Float()
var i = 0
while i < vertices.count{
x1 = vertices[i].x;
y1 = vertices[i].y;
z1 = vertices[i].z;
x2 = vertices[i + 1].x;
y2 = vertices[i + 1].y;
z2 = vertices[i + 1].z;
x3 = vertices[i + 2].x;
y3 = vertices[i + 2].y;
z3 = vertices[i + 2].z;
totalVolume +=
(-x3 * y2 * z1 +
x2 * y3 * z1 +
x3 * y1 * z2 -
x1 * y3 * z2 -
x2 * y1 * z3 +
x1 * y2 * z3);
i = i + 3
}
totalVolume = totalVolume / 6;
volume = "\(totalVolume)"
print("Volume Volume Volume Volume Volume Volume Volume :\(totalVolume)")
lbl_valume.text = "\(clean(String(totalVolume))) cubic mm"
}
return[]
}
With swift 3.1 you can extract vertices from SCNGeometry in a much faster and shorter way:
func vertices(node:SCNNode) -> [SCNVector3] {
let vertexSources = node.geometry?.getGeometrySources(for: SCNGeometrySource.Semantic.vertex)
if let vertexSource = vertexSources?.first {
let count = vertexSource.data.count / MemoryLayout<SCNVector3>.size
return vertexSource.data.withUnsafeBytes {
[SCNVector3](UnsafeBufferPointer<SCNVector3>(start: $0, count: count))
}
}
return []
}
...
Today i've noted that on osx this not going to work correct. This happens because on iOS SCNVector3 build with Float and on osx CGFloat (only apple good do smth simple so suffering). So I had to tweak the code for osx but this not gonna work as fast as on iOS.
func vertices() -> [SCNVector3] {
let vertexSources = sources(for: SCNGeometrySource.Semantic.vertex)
if let vertexSource = vertexSources.first {
let count = vertexSource.vectorCount * 3
let values = vertexSource.data.withUnsafeBytes {
[Float](UnsafeBufferPointer<Float>(start: $0, count: count))
}
var vectors = [SCNVector3]()
for i in 0..<vertexSource.vectorCount {
let offset = i * 3
vectors.append(SCNVector3Make(
CGFloat(values[offset]),
CGFloat(values[offset + 1]),
CGFloat(values[offset + 2])
))
}
return vectors
}
return []
}
For someone like me want to extract data of face from SCNGeometryElement.
Notice I only consider primtive type is triangle and index size is 2 or 4.
void extractInfoFromGeoElement(NSString* scenePath){
NSURL *url = [NSURL fileURLWithPath:scenePath];
SCNScene *scene = [SCNScene sceneWithURL:url options:nil error:nil];
SCNGeometry *geo = scene.rootNode.childNodes.firstObject.geometry;
SCNGeometryElement *elem = geo.geometryElements.firstObject;
NSInteger componentOfPrimitive = (elem.primitiveType == SCNGeometryPrimitiveTypeTriangles) ? 3 : 0;
if (!componentOfPrimitive) {//TODO: Code deals with triangle primitive only
return;
}
for (int i=0; i<elem.primitiveCount; i++) {
void *idxsPtr = NULL;
int stride = 3*i;
if (elem.bytesPerIndex == 2) {
short *idxsShort = malloc(sizeof(short)*3);
idxsPtr = idxsShort;
}else if (elem.bytesPerIndex == 4){
int *idxsInt = malloc(sizeof(int)*3);
idxsPtr = idxsInt;
}else{
NSLog(#"unknow index type");
return;
}
[elem.data getBytes:idxsPtr range:NSMakeRange(stride*elem.bytesPerIndex, elem.bytesPerIndex*3)];
if (elem.bytesPerIndex == 2) {
NSLog(#"triangle %d : %d, %d, %d\n",i,*(short*)idxsPtr,*((short*)idxsPtr+1),*((short*)idxsPtr+2));
}else{
NSLog(#"triangle %d : %d, %d, %d\n",i,*(int*)idxsPtr,*((int*)idxsPtr+1),*((int*)idxsPtr+2));
}
//Free
free(idxsPtr);
}
}
The Swift 3 version:
// `plane` is some kind of `SCNGeometry`
let planeSources = plane.geometry.sources(for: SCNGeometrySource.Semantic.vertex)
if let planeSource = planeSources.first {
let stride = planeSource.dataStride
let offset = planeSource.dataOffset
let componentsPerVector = planeSource.componentsPerVector
let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: planeSource.vectorCount)
let vertices = vectors.enumerated().map({
(index: Int, element: SCNVector3) -> SCNVector3 in
let vectorData = UnsafeMutablePointer<Float>.allocate(capacity: componentsPerVector)
let nsByteRange = NSMakeRange(index * stride + offset, bytesPerVector)
let byteRange = Range(nsByteRange)
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
planeSource.data.copyBytes(to: buffer, from: byteRange)
let vector = SCNVector3Make(buffer[0], buffer[1], buffer[2])
})
// Use `vertices` here: vertices[0].x, vertices[0].y, vertices[0].z
}
OK, here is another Swift 5.5 version based on Oliver's answer.
extension SCNGeometry{
/**
Get the vertices (3d points coordinates) of the geometry.
- returns: An array of SCNVector3 containing the vertices of the geometry.
*/
func vertices() -> [SCNVector3]? {
let sources = self.sources(for: .vertex)
guard let source = sources.first else{return nil}
let stride = source.dataStride / source.bytesPerComponent
let offset = source.dataOffset / source.bytesPerComponent
let vectorCount = source.vectorCount
return source.data.withUnsafeBytes { dataBytes in
let buffer: UnsafePointer<Float> = dataBytes.baseAddress!.assumingMemoryBound(to: Float.self)
var result = Array<SCNVector3>()
for i in 0...vectorCount - 1 {
let start = i * stride + offset
let x = buffer[start]
let y = buffer[start + 1]
let z = buffer[start + 2]
result.append(SCNVector3(x, y, z))
}
return result
}
}
}
To use it you simply create a standard shape from which you can extract the vertex and rebuild the index.
let g = SCNSphere(radius: 1)
let newNode = SCNNode(geometry: g)
let vectors = newNode.geometry?.vertices()
var indices:[Int32] = []
for i in stride(from: 0, to: vectors!.count, by: 1) {
indices.append(Int32(i))
indices.append(Int32(i+1))
}
return self.createGeometry(
vertices:vectors!, indices: indices,
primitiveType: SCNGeometryPrimitiveType.line)
The createGeometry extension can be found here
It draws this...

Resources