Complex ModelComponent on RealityKit Entity - ios

I'm trying to use RealityKits Entity Component system to it's fullest extent but I'm having trouble fitting together a few pieces. Specifically around the HasModel Component.
In Reality Composer I make a simple model composed of three basic objects. I export it as a .USDZ file and drop it into my Xcode project.
I then load the model from disk like this:
guard let basicLabelFileURL = Bundle.main.url(forResource: "label", withExtension: "usdz") else {
fatalError("Could not find label file")
}
let basicLabel = try ModelEntity.loadModel(contentsOf: basicLabelFileURL)
I then have custom Entity called LabelEntity
class LabelEntity: Entity, HasAnchoring, HasModel {
required public init() {
super.init()
}
public init(entity: Entity) {
super.init()
self.model = ??? entity?
}
}
Which gets Initialized with the model from disk.
let newLabelEntity = LabelEntity(entity: basicLabel)
As you can see I wan't to make the model loaded from disk the ModelComponent of my Custom Entity. However the ModelComponent initalizer only accepts a single mesh and then array of material.
Where is my knowledge gap? How can I make a custom Entity using the ModelComponent from a complex hierarchy of meshes (other models)?

Related

Why would there be inconsistency when saving NSManagedObjectContext after adding NSSecureUnarchiveFromDataTransformer?

I have an app that uses Core Data to persist a store of Events.
An Event has an optional location (stored as a CLLocation) as one of its attributes. In my model, this location attribute has the type Transformable:
My app has been in production for several years and everything's been working reliably, but some time in the past year I started getting an error in the Xcode console telling me I should switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead.
After doing some research (I'd consider myself a novice at Core Data) I determined I should write a NSSecureUnarchiveFromDataTransformer subclass and put the name of that class in the Transformer field for the location attribute, which was blank, with the Value Transformer Name placeholder text:
From what I found online, the subclass could be pretty straightforward for a Transformable attribute that contains a CLLocation:
#objc(CLLocationValueTransformer)
final class CLLocationValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: CLLocationValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [CLLocation.self]
}
public static func register() {
let transformer = CLLocationValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
So, I made this subclass in my project and put the class name in the Transformer field for the location attribute:
But now, here's the problem:
Once I started using my app after implementing the Transformer, I started getting unpredictable results.
Sometimes, when I created a new Event, it disappeared at the next app launch. It was not persisted by Core Data across app launches, like it was before the change.
Sometimes Events were saved, but sometimes they were not.
I couldn't figure out a clear pattern. They were not always saved if the location was included when it was first created, but sometimes they were. It seemed like other times the original Event was saved, but without location if the location was added later.
I've left out the boilerplate Core Data code, but basically I have baseManagedObjectContext, which is the layer that's connected to the NSPersistentStoreCoordinator to save data to disk.
baseManagedObjectContext is a parent to mainObjectContext, which is used by most of my UI. Then I create private contexts to write changes to first before saving them.
Here is example code to create a new Event with a possible location and save it, which was working consistently for years before adding the NSSecureUnarchiveFromDataTransformer subclass as the Transformer on location. I added fatalError for debugging, but it was never called, even when my data didn't fully save to disk:
private func addEvent(location: CLLocation?) {
let privateLocalContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateLocalContext.parent = coreDataStack.mainObjectContext
privateLocalContext.undoManager = nil
let entity = NSEntityDescription.entity(forEntityName: "Event",
in: privateLocalContext)
let newEvent = Event(entity: entity!,
insertInto: privateLocalContext)
if let location = location {
newEvent.location = location
}
privateLocalContext.performAndWait {
do {
try privateLocalContext.save()
}
catch { fatalError("error saving privateLocalContext") }
}
coreDataStack.mainObjectContext.performAndWait {
do {
try coreDataStack.mainObjectContext.save()
}
catch { fatalError("error saving mainObjectContext") }
}
coreDataStack.baseManagedObjectContext.perform {
do {
try coreDataStack.baseManagedObjectContext.save()
}
catch { fatalError("error saving baseManagedObjectContext") }
}
}
With further debugging, I found that sometimes, even if the change was making it to mainObjectContext, it was not making it all the way to baseManagedObjectContext, or to disk. I created a whole separate Core Data stack for testing to read directly from disk.
This issue with the saves not propogating (like they did before, for years) is what was causing the data to not persist across app launches, but I do not understand why this would suddenly start happening after my addition of the Transformer on location.
What am I missing here with how Core Data works?
I did not think I was fundamentally changing anything when I switched from a blank Transformer field to a subclass of NSSecureUnarchiveFromDataTransformer, but clearly something is going on that I don't understand.
I'd like to adopt NSSecureUnarchiveFromDataTransformer, since Apple is recommending it. How can I change what I'm doing to be able to adopt it and have data save consistently?
For now, I've switched back to the blank Transformer field to keep things working like they did before.

How to specify two-sided material for RealityKit / ARView?

I'm trying to load a model and texture in RealityKit (set up in an ARView instance), but I can't seem to figure out how to specify the material should be two-sided.
I have the model loaded up as a ModelEntity, the texture loaded up as a TextureResource. The model and texture are loading up, but are rending one-sided. Since the model is open (i.e., back faces are visible), there are gaps on how it is rendered.
So far, I have,
let entity: ModelEntity = try .loadModel(named: "model.obj")
var material = SimpleMaterial()
material.baseColor = try .texture(.load(named: "texture.png"))
entity.model?.materials = [material]
I was hoping to find a property such as
material.twoSided = true
but so far, I have not found the equivalent thing in RealityKit.
Anyone know how to set two-sided materials in RealityKit?
There doesn't seem to be any way to do this programmatically at the moment via the RealityKit APIs.
Can you change your model definition so it doesn't do back face culling? For example in a USDZ file I am importing it defines one part of the mesh as:
def Mesh "Plane_1"
{
uniform bool doubleSided = 1
You might be able to convert your obj file to a used file using usdzconvert first (https://developer.apple.com/download/more/?=USDPython) then edit the file manually, then import that in to your scene.
It might also be depending on how the model is setup that you can pass in more than one material to the materials array that are applied to different parts of the model, you can see how many materials the model expects:
entity.model?.mesh.expectedMaterialCount
As others already answered you can't do it programmatically.
You can however do it manually for each model via the inspection panel.
See the image below. Near the bottom, you have "Double Sided" checkbox.
I don't think there is a way to do this. RealityKit is still in early days. Material support in RealityKit is very limited right now. I think there are plans to change this in iOS 14 or beyond. There are comments in the documentation that describe features that don't yet exist such as Material protocol says "Describes a material (colors, shaders, textures, etc.) that define the look of a mesh part." There currently is no way to define custom shaders. If you look at the RealityKit framework bundle, there are shader graph definitions and new material features that are not yet exposed in the public API. I suspect there will be a shader graph editor, support for custom shaders, and double-sided materials coming.
It can be done, but it takes a change of perspective: instead of making a double sided material, you create a double sided mesh. This is accomplished by taking every part in every model and creating a double with the normals inverted (and the triangles reversed). Using the code below, the solution to the stated question becomes:
do {
let entity: ModelEntity = try .loadModel(named: "model.obj")
if let model = entity.model {
try model.mesh.addInvertedNormals()
// or alternatively, since this model isn't onscreen yet:
// model.mesh = try model.mesh.addingInvertedNormals()
var material = SimpleMaterial()
material.baseColor = try .texture(.load(named: "texture.png"))
model.materials = [material]
entity.model = model
}
} catch {}
And the entity will now display the material on both sides. Here is the code to do it:
import Foundation
import RealityKit
public extension MeshResource {
// call this to create a 2-sided mesh that will then be displayed
func addingInvertedNormals() throws -> MeshResource {
return try MeshResource.generate(from: contents.addingInvertedNormals())
}
// call this on a mesh that is already displayed to make it 2 sided
func addInvertedNormals() throws {
try replace(with: contents.addingInvertedNormals())
}
static func generateTwoSidedPlane(width: Float, depth: Float, cornerRadius: Float = 0) -> MeshResource {
let plane = generatePlane(width: width, depth: depth, cornerRadius: cornerRadius)
let twoSided = try? plane.addingInvertedNormals()
return twoSided ?? plane
}
}
public extension MeshResource.Contents {
func addingInvertedNormals() -> MeshResource.Contents {
var newContents = self
newContents.models = .init(models.map { $0.addingInvertedNormals() })
return newContents
}
}
public extension MeshResource.Model {
func partsWithNormalsInverted() -> [MeshResource.Part] {
return parts.map { $0.normalsInverted() }.compactMap { $0 }
}
func addingParts(additionalParts: [MeshResource.Part]) -> MeshResource.Model {
let newParts = parts.map { $0 } + additionalParts
var newModel = self
newModel.parts = .init(newParts)
return newModel
}
func addingInvertedNormals() -> MeshResource.Model {
return addingParts(additionalParts: partsWithNormalsInverted())
}
}
public extension MeshResource.Part {
func normalsInverted() -> MeshResource.Part? {
if let normals, let triangleIndices {
let newNormals = normals.map { $0 * -1.0 }
var newPart = self
newPart.normals = .init(newNormals)
// ordering of points in the triangles must be reversed,
// or the inversion of the normal has no effect
newPart.triangleIndices = .init(triangleIndices.reversed())
// id must be unique, or others with that id will be discarded
newPart.id = id + " with inverted normals"
return newPart
} else {
print("No normals to invert, returning nil")
return nil
}
}
}
So, calling addingInvertedNormals() creates a mesh that will show the same material on both sides. I have used this to create a 2-sided grid plane.
With a little extra work (left as an exercise!), you could give the created parts different material indexes, and show different materials on each side.
What you describe is called culling. Check MTLCullMode for example. From there you can jump to various points where you can set culling mode (you are interested in no culling).

ios swift models structure with realm database

I am developing my iOs App and I am using Realm database. As I am totally new to ios developing (also swift and xcode) I have question about structuring data (I've already read some general project structure guidelines but couldn't find the answer). My thinking is connected with Java structures
For Realm databases (RealmSiwft) I created a model like this:
#objcMembers class Patient: Object {
dynamic var patientId:Int = 0
dynamic var refNumber:String = ""
convenience init(id:Int, refNumber:String){
self.init()
self.patinetID = id
self.refNumber = refNumber
}
}
Now, it looks just like a POJO class in Java. But as I learned, this model structure is made that way so it can be able to use Realm.
So the question is, if I need somewhere else in my project to use Patient objects, is this Realm-POJO-model good to use? I mean, should I use it just like a normal Model even when I dont need to make database operations on it? Or should I make this Realm model alike DAO class for databases operations and make another model class like Patient.swift for whenever I want to play with Patient without using databases (I hope not, cause it's so much code duplicating)
And what if I need variables in that Patient Model that won't be stored in database? Can I make it without dynamic? What about init than? That blows my mind, as far as I learn swift it seems so ugly and unstructured, or I just can't switch to it yet...
if I need somewhere else in my project to use Patient objects, is this
Realm-POJO-model good to use?
even when I dont need to make database operations on it?
You can use your Patient object without savings to the DB, move them to different controllers and so on.
what if I need variables in that Patient Model that won't be stored
in database?
Look to ignoredProperties() method.
Can I make it without dynamic?
No you can't because of Realm based on Objective-C object, so this is necessary type.
What about init than?
You can create different Constructors methods, look to the Initialization doc. In case with Realm you should setup values to noticed variables (if you don't give them Default Property Values)
Your class should look like this:
class Patient: Object {
// MARK: - Properties
#objc dynamic var patientId: Int = 0
#objc dynamic var refNumber: String = ""
// MARK: - Meta
// to set the model’s primary key
override class func primaryKey() -> String? {
return "patientId"
}
//Ignoring properties
override static func ignoredProperties() -> [String] {
return ["tmpID"]
}
//It's ok
convenience init(id:Int, refNumber:String){
self.init()
self.patientId = id
self.refNumber = refNumber
}
}
All other detail information you can find in: realm docs
Also you can extend you base code with swift extension:
extension Patient {
var info: String {
return "\(patientId) " + refNumber
}
func isAvailableRefNumber() -> Bool {
return refNumber.length > 6
}
}

Use core data in swift 2 without using tables

I'm learning to be an iOS app developer and I want to make an app which stores core data. I know how to do it using tables but is there a way I can store data without using tables? Like I'm trying to make an app which saves about a 100 different variables but m not using tables in it. Can someone please direct me to a full tutorial of how it's done? I came across one tutorial on Ray weindervich but it was done on swift 1.2 and it didn't work for me. Thanks
Core data depends on entities, now something that might help to share (probably you knew this already) is that Core data entity is not a table it represents a thing that can be identify and quantify for example a fruit regardless what your back end is; with that been said, now I have a question, when you say table do do you mean the entities or an actual database table? If you mean entity, with Core data you can say use SQLite as backend or xml file as back end but regardless how you store the data you will need to create at least one entity.
What was suggested in the comments still using entities. So my suggestion would be just create one entity using one of this options:
1. Entity
variable1 datatype
variable2 datatype
...
...
variable n datatype
or
2. Entity
key String
Value object
With option one you will have to know all the possible variables that your application will use and one good advantage is that you won't have to do down casting neither unwrapping.
With option two you don't need to know all the possible variables and also your data can grow without changing the app, the only downside is you will have to wrap and unwrap the data from each record.
These are my suggestions for you.
Hope this help
UPDATE :
So here are the steps that I think you need to follow to achieve your request (important: this a simple sample):
Make sure your project has enable since creation Core Data.
In the Model add the Entity as the picture shows:
Then add the attributes as the picture shows:
Add the add the subclass; this part is not mandatory but makes it easy to handle each entity and its properties.
Then you should have something similar to the following code for the entity:
import Foundation
import CoreData
class Generic: NSManagedObject {
#NSManaged var key: String?
#NSManaged var value: NSObject?
}
And your view controller should have something like this in order to read and save the data:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var txtVariable: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = delegate.managedObjectContext
let request = NSFetchRequest(entityName: "Generic")
let filter = NSPredicate(format: "key = %#", "xyz")
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filter])
do {
let records = try context.executeFetchRequest(request) as! [Generic]
if (records.count>0)
{
txtVariable.text = (records[0].value as! String)
}
}
catch let error as NSError{
NSLog(error.localizedDescription)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnSave_Click(sender: AnyObject) {
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = delegate.managedObjectContext
let record = NSEntityDescription.insertNewObjectForEntityForName("Generic", inManagedObjectContext: context) as? Generic
record?.key = "xyz"
record?.value = txtVariable.text
do {
try context.save()
}
catch let error as NSError{
NSLog(error.localizedDescription)
}
}}

Mac OSX - Core data isn't stored

In my app I use two table views that are bound to each their NSArrayController and the array controllers are set to use Core Data entities. When data is generated, an NSObject is created and values are stored in it with obj.setValue(_:forKey:). After this the object is simply added to the array controller with ac.addObject().
Shouldn't this suffice to have Core Data taking care of persistent storage of the data?
In any case, if I try to save the data by calling saveAction() it tells me that the MOC has no changes (moc.hasChanges = false) so it doesn't even begin to save the data with this method.
What else do I need to take care of to make Core Data store the data properly and acknowledge changes?
The array controllers are set in Interface Builder as follows:
Mode: Entity Name
Entity Name: 'name of entity in data model'
Prepares Content is checked
They are also correctly bound to the managed object context.
Simplified, relevant code from my app:
/* Clear existing data. */
let range:NSRange = NSMakeRange(0, arrayController.arrangedObjects.count);
let indexSet:NSIndexSet = NSIndexSet(indexesInRange: range);
arrayController.removeObjectsAtArrangedObjectIndexes(indexSet);
let array = generateData();
/* Generate data. */
for i in 0 ..< array.count
{
let data = array[i];
/* Create new data object. */
var obj:NSObject = arrayController.newObject() as! NSObject;
obj.setValue(data.name, forKey: "name");
obj.setValue(data.type, forKey: "type");
obj.setValue(data.category, forKey: "category");
/* Add it to the array controller's contentArray. */
arrayController.addObject(obj);
}
UPDATE:
It looks like my app is instantiating four MOCs when it launches. I suspect that the way how I add them in the Storyboard for the two array controllers is wrong. I added an NSObject to the two table view controllers (which also contain their array controllers) and set their base classes to be my CoreDataDelegate (which is my class for the core data code that is normally in AppDelegate). I suspect this is where the multiple instances of CoreDataDelegate are created. The question is: How should I do this right so that the array controllers can reach my CoreDataDelegate class?
I made my core data delegate class a Singleton which solved the problem for me...
class CoreDataDelegate : NSObject
{
static let instance = CoreDataDelegate();
...
}
Then I have a reference to it in my AppDelegate (which is also a singleton) ...
#NSApplicationMain
class AppDelegate : NSObject, NSApplicationDelegate
{
static let instance = AppDelegate();
let coreData:CoreDataDelegate;
override init()
{
coreData = CoreDataDelegate.instance;
super.init();
}
...
}
Then in the storyboard I added an NSObject to my two table view controllers and set the base class to AppDelegate. And then bound the ArrayControllers to the moc via AppDelegate/coreData.moc.
Now only one instance of CoreDataDelegate is created (and therefore only one moc) and I can happily report that saving works now!

Resources